junkfood 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +11 -0
- data/.gitignore +6 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +76 -0
- data/LICENSE +202 -0
- data/NOTICE +4 -0
- data/README.md +375 -0
- data/Rakefile +51 -0
- data/VERSION +1 -0
- data/junkfood.gemspec +147 -0
- data/lib/junkfood/adler32.rb +102 -0
- data/lib/junkfood/adler32_pure.rb +112 -0
- data/lib/junkfood/assert.rb +75 -0
- data/lib/junkfood/base32.rb +198 -0
- data/lib/junkfood/ceb/base_command.rb +62 -0
- data/lib/junkfood/ceb/base_event.rb +42 -0
- data/lib/junkfood/ceb/bus.rb +152 -0
- data/lib/junkfood/ceb/executors/command_executor.rb +44 -0
- data/lib/junkfood/ceb/executors/delayed_job_command_executor.rb +61 -0
- data/lib/junkfood/ceb/executors/event_executor.rb +35 -0
- data/lib/junkfood/ceb/executors.rb +25 -0
- data/lib/junkfood/ceb.rb +27 -0
- data/lib/junkfood/one_time.rb +247 -0
- data/lib/junkfood/paperclip_string_io.rb +66 -0
- data/lib/junkfood/settings.rb +67 -0
- data/lib/junkfood.rb +29 -0
- data/spec/.rspec +1 -0
- data/spec/junkfood/adler32_pure_spec.rb +16 -0
- data/spec/junkfood/adler32_spec.rb +16 -0
- data/spec/junkfood/assert_spec.rb +84 -0
- data/spec/junkfood/base32_spec.rb +39 -0
- data/spec/junkfood/ceb/base_command_spec.rb +73 -0
- data/spec/junkfood/ceb/base_event_spec.rb +67 -0
- data/spec/junkfood/ceb/bus_spec.rb +153 -0
- data/spec/junkfood/ceb/executors/command_executor_spec.rb +24 -0
- data/spec/junkfood/ceb/executors/delayed_job_command_executor_spec.rb +5 -0
- data/spec/junkfood/ceb/executors/event_executor_spec.rb +18 -0
- data/spec/junkfood/one_time_spec.rb +167 -0
- data/spec/junkfood/paperclip_string_io_spec.rb +40 -0
- data/spec/junkfood/settings_spec.rb +7 -0
- data/spec/junkfood_spec.rb +4 -0
- data/spec/spec_helper.rb +20 -0
- metadata +372 -0
@@ -0,0 +1,112 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# Copyright 2009 Benjamin Yu <http://benjaminyu.org/>
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
module Junkfood
|
17
|
+
|
18
|
+
##
|
19
|
+
# A pure Ruby implementation of the Adler-32 checksum algorithm.
|
20
|
+
#
|
21
|
+
# This Ruby implementation is a port of the pure Python reference
|
22
|
+
# implementation found in the pysync project. The Python reference
|
23
|
+
# implementation, itself, was a port from zlib's adler32.c file.
|
24
|
+
#
|
25
|
+
# @example
|
26
|
+
# adler32 = Junkfood::Adler32Pure.new('Wikipedia')
|
27
|
+
# puts adler32.digest #=> 300286872
|
28
|
+
#
|
29
|
+
# @see http://zlib.net/
|
30
|
+
# @see http://freshmeat.net/projects/pysync/
|
31
|
+
#
|
32
|
+
class Adler32Pure
|
33
|
+
|
34
|
+
# largest prime smaller than 65536
|
35
|
+
BASE = 65521
|
36
|
+
# largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1
|
37
|
+
NMAX = 5552
|
38
|
+
# default initial s1 offset
|
39
|
+
OFFS = 1
|
40
|
+
|
41
|
+
##
|
42
|
+
# @param data (String) initial block of data to digest.
|
43
|
+
#
|
44
|
+
def initialize(data='')
|
45
|
+
@count = 0
|
46
|
+
@s2 = 0
|
47
|
+
@s1 = OFFS
|
48
|
+
self.update(data)
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# Adds another block of data to digest.
|
53
|
+
#
|
54
|
+
# @param data (String) block of data to digest.
|
55
|
+
# @return (Fixnum) the updated digest.
|
56
|
+
#
|
57
|
+
def update(data)
|
58
|
+
i = 0
|
59
|
+
while i < data.length
|
60
|
+
data[i,i+NMAX].each_byte do |b|
|
61
|
+
@s1 = @s1 + b
|
62
|
+
@s2 = @s2 + @s1
|
63
|
+
end
|
64
|
+
@s1 = @s1 % BASE
|
65
|
+
@s2 = @s2 % BASE
|
66
|
+
i = i + NMAX
|
67
|
+
end
|
68
|
+
@count = @count + data.length
|
69
|
+
return self.digest
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# @param x1 (Byte)
|
74
|
+
# @param xn (Byte)
|
75
|
+
# @return (Fixnum) the updated digest.
|
76
|
+
#
|
77
|
+
def rotate(x1, xn)
|
78
|
+
@s1 = (@s1 - x1 + xn) % BASE
|
79
|
+
@s2 = (@s2 - (@count * x1) + @s1 - OFFS) % BASE
|
80
|
+
return self.digest
|
81
|
+
end
|
82
|
+
|
83
|
+
##
|
84
|
+
# @param b (Byte)
|
85
|
+
# @return (Fixnum) the updated digest.
|
86
|
+
#
|
87
|
+
def rollin(b)
|
88
|
+
@s1 = (@s1 + b) % BASE
|
89
|
+
@s2 = (@s2 + @s1) % BASE
|
90
|
+
@count = @count + 1
|
91
|
+
return self.digest
|
92
|
+
end
|
93
|
+
|
94
|
+
##
|
95
|
+
# @param b (Byte)
|
96
|
+
# @return (Fixnum) the updated digest.
|
97
|
+
#
|
98
|
+
def rollout(b)
|
99
|
+
@s1 = (@s1 - b) % BASE
|
100
|
+
@s2 = (@s2 - @count * b) % BASE
|
101
|
+
@count = @count - 1
|
102
|
+
return self.digest
|
103
|
+
end
|
104
|
+
|
105
|
+
##
|
106
|
+
# @return (Fixnum) the current Adler32 digest value.
|
107
|
+
#
|
108
|
+
def digest
|
109
|
+
return (@s2 << 16) | @s1
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# Copyright 2010 Benjamin Yu <http://benjaminyu.org/>
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
module Junkfood
|
17
|
+
|
18
|
+
##
|
19
|
+
# A dumbed down implementation for Assertions based on the 'wrong' gem,
|
20
|
+
# but without any of wrong's awesomeness. This is just for the future in
|
21
|
+
# case 'wrong' disappears from the scene.
|
22
|
+
#
|
23
|
+
# @example
|
24
|
+
# class MyClass
|
25
|
+
# include Junkfood::Assert
|
26
|
+
# def my_method
|
27
|
+
# assert { true }
|
28
|
+
# assert('Custom Error Message') { false }
|
29
|
+
# rescue Junkfood::Assert::AssertionFailedError => e
|
30
|
+
# puts e
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
module Assert
|
35
|
+
|
36
|
+
##
|
37
|
+
# Error thrown when an assertion fails.
|
38
|
+
#
|
39
|
+
class AssertionFailedError < RuntimeError
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# Tests an assertion claim.
|
44
|
+
#
|
45
|
+
# @example
|
46
|
+
# x = 3
|
47
|
+
# assert { x == 2 }
|
48
|
+
# assert('failure message') { x == 1 }
|
49
|
+
#
|
50
|
+
# @overload assert
|
51
|
+
# @yield block with predicates to determine whether or not to raise
|
52
|
+
# an AssertionFailedError.
|
53
|
+
# @yieldreturn [Boolean, nil]
|
54
|
+
#
|
55
|
+
# @overload assert(message)
|
56
|
+
# @param message (String) the custom error message of for the
|
57
|
+
# AssertionFailedError.
|
58
|
+
# @yield block with predicates to determine whether or not to raise
|
59
|
+
# an AssertionFailedError.
|
60
|
+
# @yieldreturn [Boolean, nil]
|
61
|
+
#
|
62
|
+
# @raise AssertionFailedError when the block yields a false/nil value.
|
63
|
+
#
|
64
|
+
# @see http://rubygems.org/gems/wrong
|
65
|
+
#
|
66
|
+
def assert(*args, &block)
|
67
|
+
begin
|
68
|
+
return super if block.nil?
|
69
|
+
rescue NoMethodError
|
70
|
+
raise 'You must pass a block for assertion check'
|
71
|
+
end
|
72
|
+
raise AssertionFailedError, args.first unless yield
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# Copyright 2010 Benjamin Yu <http://benjaminyu.org/>
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
require 'stringio'
|
17
|
+
|
18
|
+
module Junkfood
|
19
|
+
|
20
|
+
##
|
21
|
+
# Error class raised when an invalid Base32 character encoding is encountered.
|
22
|
+
#
|
23
|
+
class Base32DecodeError < StandardError
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# Base32 (RFC4668/3548) encodes and decodes strings of data.
|
28
|
+
#
|
29
|
+
# Requires at least Ruby 1.9
|
30
|
+
#
|
31
|
+
# @see http://tools.ietf.org/html/rfc4648
|
32
|
+
#
|
33
|
+
class Base32
|
34
|
+
# The Base32 alphabet, all caps.
|
35
|
+
ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
|
36
|
+
ALPHABET.freeze
|
37
|
+
# The Base32 alphabet, all lowercase.
|
38
|
+
ALPHABET_DOWNCASE = ALPHABET.downcase
|
39
|
+
ALPHABET_DOWNCASE.freeze
|
40
|
+
|
41
|
+
# Once populated, this is Base32 alphabet to byte mapping.
|
42
|
+
BYTE_MAP = {}
|
43
|
+
(0..(ALPHABET.size)).each do |i|
|
44
|
+
BYTE_MAP[ALPHABET.getbyte(i)] = i
|
45
|
+
BYTE_MAP[ALPHABET_DOWNCASE.getbyte(i)] = i
|
46
|
+
end
|
47
|
+
# This is clear up confusions between the pairs: 1 and I, 0 and O.
|
48
|
+
BYTE_MAP['1'.getbyte(0)] = BYTE_MAP['I'.getbyte(0)]
|
49
|
+
BYTE_MAP['0'.getbyte(0)] = BYTE_MAP['O'.getbyte(0)]
|
50
|
+
BYTE_MAP.freeze
|
51
|
+
|
52
|
+
# Spacer characters to ignore when parsing a Base32 encoded string.
|
53
|
+
IGNORED = "\r\n-_\s".bytes.to_a
|
54
|
+
IGNORED.freeze
|
55
|
+
|
56
|
+
##
|
57
|
+
# The hash of available break strings that can be inserted between
|
58
|
+
# X number of Base32 characters (during the encoding process).
|
59
|
+
SPLITS = {
|
60
|
+
dash: '-',
|
61
|
+
newline: "\n",
|
62
|
+
space: ' ',
|
63
|
+
underscore: '_'
|
64
|
+
}.freeze
|
65
|
+
|
66
|
+
##
|
67
|
+
# Base32 encodes the input object and writes to the output io object.
|
68
|
+
#
|
69
|
+
# @param input [#each_byte]
|
70
|
+
# @option options [#putc] :output (StringIO.new) of the output IO.
|
71
|
+
# @option options [String] :split :dash, :newline, :space, or :underscore
|
72
|
+
# @option options [Fixnum] :split_length (79) number of Base32 characters
|
73
|
+
# before inserting a split character.
|
74
|
+
# @return IO, StringIO instance of object to which encoded data was written.
|
75
|
+
#
|
76
|
+
def self.encode(input, options={})
|
77
|
+
output = options[:output] || StringIO.new(''.force_encoding('US-ASCII'))
|
78
|
+
alphabet = options[:use_downcase] ? ALPHABET_DOWNCASE : ALPHABET
|
79
|
+
|
80
|
+
split = SPLITS[options[:split]]
|
81
|
+
split_length = options[:split_length] || 79
|
82
|
+
|
83
|
+
# Set up the lambda that does the actual work of writing the
|
84
|
+
# quintet to the output stream.
|
85
|
+
if split
|
86
|
+
# This lambda will split up the output stream with a "break" character
|
87
|
+
# for every N quintets (where N is the split_length).
|
88
|
+
split_count = 0
|
89
|
+
write = lambda do |quintet|
|
90
|
+
output.putc alphabet.getbyte(quintet)
|
91
|
+
split_count += 1
|
92
|
+
if split_count >= split_length
|
93
|
+
output.putc split
|
94
|
+
split_count = 0
|
95
|
+
end
|
96
|
+
end
|
97
|
+
else
|
98
|
+
# This lambda just writes out the quintets
|
99
|
+
write = lambda do |quintet|
|
100
|
+
output.putc alphabet.getbyte(quintet)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
position = 0
|
105
|
+
buffer = 0
|
106
|
+
input.each_byte do |byte|
|
107
|
+
case position
|
108
|
+
when 0
|
109
|
+
# Current Buffer: 0 bits from previous byte
|
110
|
+
# Quintet: 5 bits from current byte
|
111
|
+
# New Buffer: Lowest 3 bits of current byte
|
112
|
+
write.call(byte >> 3)
|
113
|
+
buffer = byte & 0x07
|
114
|
+
when 1
|
115
|
+
# Current Buffer: 3 bits from previous byte
|
116
|
+
# Quintet 1: 3 bits of buffer and first 2 bits of current byte
|
117
|
+
# Quintet 2: next 5 bits of byte
|
118
|
+
# New Buffer: Lowest 1 bit of current byte
|
119
|
+
write.call((buffer << 2) | (byte >> 6))
|
120
|
+
write.call((byte >> 1) & 0x1f)
|
121
|
+
buffer = byte & 0x01
|
122
|
+
when 2
|
123
|
+
# Current Buffer: 1 bits from previous byte
|
124
|
+
# Quintet 1: 1 bits of buffer and 4 bits of current byte
|
125
|
+
# New Buffer: Lowest 4 bits of current byte
|
126
|
+
write.call((buffer << 4) | (byte >> 4))
|
127
|
+
buffer = byte & 0x0f
|
128
|
+
when 3
|
129
|
+
# Current Buffer: 4 bits from previous byte
|
130
|
+
# Quintet 1: 4 bits of buffer and top bit of byte
|
131
|
+
# Quintet 2: next 5 bits of byte
|
132
|
+
# New Buffer: bottom 2 bit of byte
|
133
|
+
write.call((buffer << 1) | (byte >> 7))
|
134
|
+
write.call((byte >> 2) & 0x1f)
|
135
|
+
buffer = byte & 0x03
|
136
|
+
when 4
|
137
|
+
# Current Buffer: 2 bits from previous byte
|
138
|
+
# Quintet 1: 2 bits of buffer and top 3 bits of byte
|
139
|
+
# Quintet 2: bottom 5 bits of byte
|
140
|
+
write.call((buffer << 3) | (byte >> 5))
|
141
|
+
write.call(byte & 0x1f)
|
142
|
+
buffer = 0
|
143
|
+
end
|
144
|
+
position = (position + 1) % 5
|
145
|
+
end
|
146
|
+
case position
|
147
|
+
when 0
|
148
|
+
# We are 40-bit aligned, so nothing to do.
|
149
|
+
when 1
|
150
|
+
# 3 bits in buffer
|
151
|
+
write.call(buffer << 2)
|
152
|
+
when 2
|
153
|
+
# 1 bit in buffer
|
154
|
+
write.call(buffer << 4)
|
155
|
+
when 3
|
156
|
+
# 4 bits in buffer
|
157
|
+
write.call(buffer << 1)
|
158
|
+
when 4
|
159
|
+
# 2 bits in buffer
|
160
|
+
write.call(buffer << 3)
|
161
|
+
end
|
162
|
+
|
163
|
+
return output
|
164
|
+
end
|
165
|
+
|
166
|
+
##
|
167
|
+
# Base32 decodes the input object and writes to the output io object.
|
168
|
+
#
|
169
|
+
# @param input [#each_byte]
|
170
|
+
# @option options [#putc] :output (StringIO.new) of the output IO.
|
171
|
+
# before inserting a split character.
|
172
|
+
# @return IO, StringIO instance of object to which decoded data was written.
|
173
|
+
# @raise Base32DecodeError
|
174
|
+
#
|
175
|
+
def self.decode(input, options={})
|
176
|
+
output = options[:output] || StringIO.new(''.force_encoding('BINARY'))
|
177
|
+
buffer = 0
|
178
|
+
bits_left = 0
|
179
|
+
input.each_byte do |byte|
|
180
|
+
next if IGNORED.include? byte
|
181
|
+
raise Base32DecodeError.new("Invalid input byte: #{byte}") unless(
|
182
|
+
BYTE_MAP.key? byte)
|
183
|
+
buffer = (buffer << 5) | BYTE_MAP[byte]
|
184
|
+
bits_left += 5
|
185
|
+
if bits_left >= 8
|
186
|
+
bits_left -= 8
|
187
|
+
output.putc(buffer >> bits_left)
|
188
|
+
buffer &= (2 ** bits_left - 1)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
# We ignore remaining bits in the buffer in cases where there is an
|
192
|
+
# incomplete Base32 quantum (ie, the number of characters are unaligned
|
193
|
+
# with the 40-bit boundries).
|
194
|
+
|
195
|
+
return output
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# Copyright 2010 Benjamin Yu <http://benjaminyu.org/>
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
require 'junkfood/ceb/bus'
|
17
|
+
require 'mongoid'
|
18
|
+
|
19
|
+
module Junkfood
|
20
|
+
module Ceb
|
21
|
+
|
22
|
+
##
|
23
|
+
# BaseCommand abstract class from which one defines and implements
|
24
|
+
# actual Commands to be run.
|
25
|
+
#
|
26
|
+
# _id (id)
|
27
|
+
# _type
|
28
|
+
# created_at
|
29
|
+
# origin
|
30
|
+
class BaseCommand
|
31
|
+
include Junkfood::Ceb::Bus
|
32
|
+
include Mongoid::Document
|
33
|
+
|
34
|
+
acts_as_event_bus
|
35
|
+
|
36
|
+
references_many(
|
37
|
+
:events,
|
38
|
+
:inverse_of => :command,
|
39
|
+
:class_name => 'Junkfood::Ceb::BaseEvent')
|
40
|
+
|
41
|
+
field :created_at, :type => Time
|
42
|
+
field :origin, :type => String
|
43
|
+
|
44
|
+
after_initialize :generate_default_values
|
45
|
+
|
46
|
+
##
|
47
|
+
# @abstract implement the actual handler logic to execute the command.
|
48
|
+
#
|
49
|
+
def perform
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
|
54
|
+
##
|
55
|
+
# Before create hook to set the Command's default values.
|
56
|
+
#
|
57
|
+
def generate_default_values
|
58
|
+
self.created_at = Time.now unless self.created_at
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# Copyright 2010 Benjamin Yu <http://benjaminyu.org/>
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
require 'mongoid'
|
17
|
+
|
18
|
+
module Junkfood
|
19
|
+
module Ceb
|
20
|
+
|
21
|
+
##
|
22
|
+
# _id (id)
|
23
|
+
# _type
|
24
|
+
# created_at
|
25
|
+
# command_id
|
26
|
+
class BaseEvent
|
27
|
+
include Mongoid::Document
|
28
|
+
|
29
|
+
field :created_at, :type => Time
|
30
|
+
|
31
|
+
referenced_in :command, :class_name => 'Junkfood::Ceb::BaseCommand'
|
32
|
+
|
33
|
+
after_initialize :generate_default_values
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
def generate_default_values
|
38
|
+
self.created_at = Time.now unless self.created_at
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# Copyright 2010 Benjamin Yu <http://benjaminyu.org/>
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
require 'active_support/concern'
|
17
|
+
require 'active_support/core_ext/class/attribute'
|
18
|
+
require 'active_support/core_ext/string/inflections'
|
19
|
+
require 'junkfood/ceb/executors/command_executor'
|
20
|
+
require 'junkfood/ceb/executors/event_executor'
|
21
|
+
|
22
|
+
module Junkfood
|
23
|
+
module Ceb
|
24
|
+
|
25
|
+
##
|
26
|
+
# Module to implement the bus framework for Ceb.
|
27
|
+
#
|
28
|
+
# @example
|
29
|
+
#
|
30
|
+
# class WidgetCreateCommand < Junkfood::Ceb::BaseCommand
|
31
|
+
# def perform
|
32
|
+
# # Do Stuff.
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# class ApplicationController < ActionController::Base
|
37
|
+
# include Junkfood::Ceb::Bus
|
38
|
+
# acts_as_command_bus
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# class WidgetController < ApplicationController
|
42
|
+
# def create
|
43
|
+
# # params['widget'] is our posted form data the same as how Rails
|
44
|
+
# # does it for ActiveModel. This eliminates hassle for
|
45
|
+
# # custom parsing, data handling.
|
46
|
+
#
|
47
|
+
# # Create a command (WidgetCreateCommand) and puts it onto the bus
|
48
|
+
# # for execution. The command created is returned along with
|
49
|
+
# # published events by the command.
|
50
|
+
# command, events = send_command 'widget_create', params['widget']
|
51
|
+
#
|
52
|
+
# if command.valid?
|
53
|
+
# # The command is correctly formed, so the backend executors will
|
54
|
+
# # execute it in the future (or already has executed it).
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# # Commands SHOULD return the Event, or list of Events,
|
58
|
+
# # that the command published when its `perform` method was called.
|
59
|
+
# case result
|
60
|
+
# when Junkfood::Ceb::BaseEvent
|
61
|
+
# # The single published event.
|
62
|
+
# # Can assume that the command was executed.
|
63
|
+
# when Array
|
64
|
+
# # An array of Events published.
|
65
|
+
# # Can assume that the command was executed.
|
66
|
+
# when nil, false
|
67
|
+
# # No data was passed back. Nothing can be assumed about the
|
68
|
+
# # execution of the command.
|
69
|
+
# end
|
70
|
+
# end
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# Notes for implementers:
|
74
|
+
# * Executors MAY reraise all raised errors
|
75
|
+
# * This is for problems in the "process" of executing commands,
|
76
|
+
# * not actual errors in the command's execution itself.
|
77
|
+
# * Messages SHOULD handle their errors, publishing Error Events
|
78
|
+
# when a command fails.
|
79
|
+
# * Callers of this method SHOULD check the command to see if
|
80
|
+
# validation errors occurred.
|
81
|
+
#
|
82
|
+
# @see ClassMethods
|
83
|
+
#
|
84
|
+
module Bus
|
85
|
+
extend ActiveSupport::Concern
|
86
|
+
|
87
|
+
##
|
88
|
+
# Methods to add to the actual classes that include Junkfood::Ceb::Bus.
|
89
|
+
#
|
90
|
+
module ClassMethods
|
91
|
+
|
92
|
+
##
|
93
|
+
# Creates:
|
94
|
+
# * a class attribute for the bus executor.
|
95
|
+
# * an instance method to put a message onto the bus.
|
96
|
+
#
|
97
|
+
# @param bus_type (Symbol) the name of the bus.
|
98
|
+
# @param executor (#call) a handler for the bus' messages.
|
99
|
+
#
|
100
|
+
def acts_as_bus(bus_type, executor=nil)
|
101
|
+
class_eval do
|
102
|
+
# We create the class attributes (accessors) for this executor
|
103
|
+
# so individual instances or subclasses can override this.
|
104
|
+
# And it sets it to the given executor or an empty executor
|
105
|
+
executor_name = "#{bus_type}_executor"
|
106
|
+
class_attribute executor_name
|
107
|
+
send "#{executor_name}=", (executor || Proc.new {})
|
108
|
+
|
109
|
+
# Now we define a the bus method in the form of `send_<bus_type>`.
|
110
|
+
# This is the main method that objects call to look up the
|
111
|
+
# commands/events, instantiate them, and execute on them.
|
112
|
+
# This method will return the created command/event along with
|
113
|
+
# the executor's results.
|
114
|
+
method_name = "send_#{bus_type}"
|
115
|
+
define_method(method_name, lambda { |name, *args|
|
116
|
+
klass = "#{name}_#{bus_type}".to_s.classify.constantize
|
117
|
+
message = klass.new *args
|
118
|
+
results = message.valid? ? send(executor_name).call(message) : nil
|
119
|
+
return message, results
|
120
|
+
})
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
##
|
125
|
+
# Creates:
|
126
|
+
# * command_executor class attributes.
|
127
|
+
# * send_command instance method to put commands onto the bus.
|
128
|
+
#
|
129
|
+
# @param executor (#call) an actual executor to use instead of the
|
130
|
+
# default ::Junkfood::Ceb::Executors::CommandExecutor.
|
131
|
+
#
|
132
|
+
def acts_as_command_bus(
|
133
|
+
executor=::Junkfood::Ceb::Executors::CommandExecutor.new)
|
134
|
+
acts_as_bus :command, executor
|
135
|
+
end
|
136
|
+
|
137
|
+
##
|
138
|
+
# Creates:
|
139
|
+
# * event_executor class attributes.
|
140
|
+
# * send_event instance method to put events onto the bus.
|
141
|
+
#
|
142
|
+
# @param executor (#call) an actual executor to use instead of the
|
143
|
+
# default ::Junkfood::Ceb::Executors::EventExecutor.
|
144
|
+
#
|
145
|
+
def acts_as_event_bus(
|
146
|
+
executor=::Junkfood::Ceb::Executors::EventExecutor.new)
|
147
|
+
acts_as_bus :event, executor
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# Copyright 2010 Benjamin Yu <http://benjaminyu.org/>
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
module Junkfood
|
17
|
+
module Ceb
|
18
|
+
module Executors
|
19
|
+
|
20
|
+
##
|
21
|
+
# Processes the command from the command_bus. The command is
|
22
|
+
# saved, then its perform method is executed.
|
23
|
+
#
|
24
|
+
class CommandExecutor
|
25
|
+
|
26
|
+
##
|
27
|
+
# @param command (BaseCommand) the command to save and perform.
|
28
|
+
#
|
29
|
+
def call(command)
|
30
|
+
# We need to raise an error here if we can't save.
|
31
|
+
# But we only save if it's a new record.
|
32
|
+
# Junction asserted that command was valid before calling this
|
33
|
+
# executor, thus any errors in saving probably have to do with
|
34
|
+
# connectivity... we want to abort.
|
35
|
+
command.save! if command.new_record?
|
36
|
+
# We assume that the command will catch all of its problems and
|
37
|
+
# publish error events?
|
38
|
+
# TODO: should wrap this up and publish a generic CommandFailedEvent.
|
39
|
+
return command.perform
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|