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.
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