junkfood 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/.document +11 -0
  2. data/.gitignore +6 -0
  3. data/Gemfile +17 -0
  4. data/Gemfile.lock +76 -0
  5. data/LICENSE +202 -0
  6. data/NOTICE +4 -0
  7. data/README.md +375 -0
  8. data/Rakefile +51 -0
  9. data/VERSION +1 -0
  10. data/junkfood.gemspec +147 -0
  11. data/lib/junkfood/adler32.rb +102 -0
  12. data/lib/junkfood/adler32_pure.rb +112 -0
  13. data/lib/junkfood/assert.rb +75 -0
  14. data/lib/junkfood/base32.rb +198 -0
  15. data/lib/junkfood/ceb/base_command.rb +62 -0
  16. data/lib/junkfood/ceb/base_event.rb +42 -0
  17. data/lib/junkfood/ceb/bus.rb +152 -0
  18. data/lib/junkfood/ceb/executors/command_executor.rb +44 -0
  19. data/lib/junkfood/ceb/executors/delayed_job_command_executor.rb +61 -0
  20. data/lib/junkfood/ceb/executors/event_executor.rb +35 -0
  21. data/lib/junkfood/ceb/executors.rb +25 -0
  22. data/lib/junkfood/ceb.rb +27 -0
  23. data/lib/junkfood/one_time.rb +247 -0
  24. data/lib/junkfood/paperclip_string_io.rb +66 -0
  25. data/lib/junkfood/settings.rb +67 -0
  26. data/lib/junkfood.rb +29 -0
  27. data/spec/.rspec +1 -0
  28. data/spec/junkfood/adler32_pure_spec.rb +16 -0
  29. data/spec/junkfood/adler32_spec.rb +16 -0
  30. data/spec/junkfood/assert_spec.rb +84 -0
  31. data/spec/junkfood/base32_spec.rb +39 -0
  32. data/spec/junkfood/ceb/base_command_spec.rb +73 -0
  33. data/spec/junkfood/ceb/base_event_spec.rb +67 -0
  34. data/spec/junkfood/ceb/bus_spec.rb +153 -0
  35. data/spec/junkfood/ceb/executors/command_executor_spec.rb +24 -0
  36. data/spec/junkfood/ceb/executors/delayed_job_command_executor_spec.rb +5 -0
  37. data/spec/junkfood/ceb/executors/event_executor_spec.rb +18 -0
  38. data/spec/junkfood/one_time_spec.rb +167 -0
  39. data/spec/junkfood/paperclip_string_io_spec.rb +40 -0
  40. data/spec/junkfood/settings_spec.rb +7 -0
  41. data/spec/junkfood_spec.rb +4 -0
  42. data/spec/spec_helper.rb +20 -0
  43. 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