junkfood 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|