wonkavision 0.5.1 → 0.5.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +4 -0
- data/lib/wonkavision.rb +6 -1
- data/lib/wonkavision/event_context.rb +9 -0
- data/lib/wonkavision/event_handler.rb +1 -0
- data/lib/wonkavision/plugins.rb +1 -1
- data/lib/wonkavision/plugins/business_activity.rb +13 -12
- data/lib/wonkavision/plugins/callbacks.rb +182 -0
- data/lib/wonkavision/plugins/event_handling.rb +44 -15
- data/lib/wonkavision/plugins/timeline.rb +9 -9
- data/lib/wonkavision/version.rb +1 -1
- data/test/event_handler_test.rb +9 -0
- data/test/log/test.log +5277 -0
- data/test/test_activity_models.rb +14 -6
- data/wonkavision.gemspec +4 -2
- metadata +6 -4
data/CHANGELOG.rdoc
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
== 0.5.2
|
2
|
+
* Added callbacks to event handler, before_event and after_event. Refactored event handling hierarchy to be a little
|
3
|
+
less functional and a little more object oriented.
|
4
|
+
|
1
5
|
== 0.5.1
|
2
6
|
* Ensure that subscriptions to an event namespace will only get notified once per incoming message
|
3
7
|
|
data/lib/wonkavision.rb
CHANGED
@@ -7,6 +7,7 @@ dir = File.dirname(__FILE__)
|
|
7
7
|
"plugins",
|
8
8
|
"event_path_segment",
|
9
9
|
"event",
|
10
|
+
"event_context",
|
10
11
|
"event_namespace",
|
11
12
|
"event_coordinator",
|
12
13
|
"event_binding",
|
@@ -14,6 +15,7 @@ dir = File.dirname(__FILE__)
|
|
14
15
|
"message_mapper/indifferent_access",
|
15
16
|
"message_mapper/map",
|
16
17
|
"message_mapper",
|
18
|
+
"plugins/callbacks",
|
17
19
|
"plugins/event_handling",
|
18
20
|
"plugins/business_activity",
|
19
21
|
"plugins/timeline",
|
@@ -43,7 +45,7 @@ module Wonkavision
|
|
43
45
|
|
44
46
|
def namespace_wildcard_character
|
45
47
|
@namespace_wildcard_character = "*"
|
46
|
-
end
|
48
|
+
end
|
47
49
|
|
48
50
|
def is_absolute_path(path)
|
49
51
|
path.to_s[0..0] == event_path_separator
|
@@ -63,6 +65,9 @@ module Wonkavision
|
|
63
65
|
end
|
64
66
|
end
|
65
67
|
|
68
|
+
class WonkavisionError < StandardError #:nodoc:
|
69
|
+
end
|
70
|
+
|
66
71
|
|
67
72
|
|
68
73
|
end
|
data/lib/wonkavision/plugins.rb
CHANGED
@@ -21,7 +21,7 @@ module Wonkavision
|
|
21
21
|
include mod::InstanceMethods if mod.const_defined?(:InstanceMethods)
|
22
22
|
extend mod::Fields if mod.const_defined?(:Fields)
|
23
23
|
include mod::Fields if mod.const_defined?(:Fields)
|
24
|
-
mod.configure(self,options)
|
24
|
+
mod.configure(self,options) if mod.respond_to?(:configure)
|
25
25
|
wonkavision_plugins << mod
|
26
26
|
end
|
27
27
|
alias use plug
|
@@ -17,22 +17,26 @@ module Wonkavision
|
|
17
17
|
end
|
18
18
|
|
19
19
|
module ClassMethods
|
20
|
+
def instantiate_handler(event_context)
|
21
|
+
correlation_id = event_context.data[event_correlation_id_key.to_s]
|
22
|
+
find_activity_instance(correlation_id_field,correlation_id)
|
23
|
+
end
|
20
24
|
|
21
25
|
def event(name,*args,&block)
|
22
|
-
handle(name,args) do
|
23
|
-
|
26
|
+
handle(name,args) do
|
27
|
+
ctx = @wonkavision_event_context
|
24
28
|
result = :ok
|
25
29
|
if (block_given?)
|
26
30
|
result = case block.arity
|
27
|
-
when 3 then yield
|
28
|
-
when 2 then yield
|
29
|
-
when 1 then yield
|
30
|
-
else
|
31
|
+
when 3 then yield ctx.data,ctx.path,self
|
32
|
+
when 2 then yield ctx.data, ctx.path
|
33
|
+
when 1 then yield ctx.data
|
34
|
+
else instance_eval &block
|
31
35
|
end
|
32
36
|
end
|
33
37
|
unless result == :handled
|
34
|
-
result = update_activity(
|
35
|
-
|
38
|
+
result = self.class.update_activity(self,ctx.data) unless result == :updated
|
39
|
+
save!
|
36
40
|
end
|
37
41
|
result
|
38
42
|
end
|
@@ -56,10 +60,7 @@ module Wonkavision
|
|
56
60
|
correlation_ids << {:model=>model_field.to_s, :event=>event_field.to_s}
|
57
61
|
end
|
58
62
|
|
59
|
-
|
60
|
-
correlation_id = event_data[event_correlation_id_key.to_s]
|
61
|
-
find_activity_instance(correlation_id_field,correlation_id)
|
62
|
-
end
|
63
|
+
|
63
64
|
|
64
65
|
end
|
65
66
|
|
@@ -0,0 +1,182 @@
|
|
1
|
+
# The note you see below, 'Almost all of this callback stuff...', along with all the code adapted from ActiveSupport,
|
2
|
+
# is in turn adapted from MongoMapper, and for the same reasons.
|
3
|
+
# encoding: UTF-8
|
4
|
+
# Almost all of this callback stuff is pulled directly from ActiveSupport
|
5
|
+
# in the interest of support rails 2 and 3 at the same time and is the
|
6
|
+
# same copyright as rails.
|
7
|
+
module Wonkavision
|
8
|
+
module Plugins
|
9
|
+
module Callbacks
|
10
|
+
def self.configure(handler,options)
|
11
|
+
handler.define_wonkavision_callbacks :before_event,
|
12
|
+
:after_event
|
13
|
+
|
14
|
+
handler.alias_method_chain :handle_event, :callbacks
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
#The ODM library may already have taken care of mixing in callback functioanlity,
|
19
|
+
#in which case we'll just use that
|
20
|
+
def define_wonkavision_callbacks(*callbacks)
|
21
|
+
callbacks.each do |callback|
|
22
|
+
class_eval <<-"end_eval"
|
23
|
+
def self.#{callback}(*methods, &block)
|
24
|
+
callbacks = CallbackChain.build(:#{callback}, *methods, &block)
|
25
|
+
@#{callback}_callbacks ||= CallbackChain.new
|
26
|
+
@#{callback}_callbacks.concat callbacks
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.#{callback}_callback_chain
|
30
|
+
@#{callback}_callbacks ||= CallbackChain.new
|
31
|
+
|
32
|
+
if superclass.respond_to?(:#{callback}_callback_chain)
|
33
|
+
CallbackChain.new(
|
34
|
+
superclass.#{callback}_callback_chain +
|
35
|
+
@#{callback}_callbacks
|
36
|
+
)
|
37
|
+
else
|
38
|
+
@#{callback}_callbacks
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end_eval
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module InstanceMethods
|
47
|
+
def handle_event_with_callbacks
|
48
|
+
ctx = @wonkavision_event_context
|
49
|
+
run_wonkavision_callbacks(:before_event)
|
50
|
+
handle_event_without_callbacks
|
51
|
+
run_wonkavision_callbacks(:after_event)
|
52
|
+
end
|
53
|
+
|
54
|
+
def run_wonkavision_callbacks(kind, options={}, &block)
|
55
|
+
callback_chain_method = "#{kind}_callback_chain"
|
56
|
+
return unless self.class.respond_to?(callback_chain_method)
|
57
|
+
self.class.send(callback_chain_method).run(self, options, &block)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class CallbackChain < Array
|
62
|
+
def self.build(kind, *methods, &block)
|
63
|
+
methods, options = extract_options(*methods, &block)
|
64
|
+
methods.map! { |method| Callback.new(kind, method, options) }
|
65
|
+
new(methods)
|
66
|
+
end
|
67
|
+
|
68
|
+
def run(object, options={}, &terminator)
|
69
|
+
enumerator = options[:enumerator] || :each
|
70
|
+
|
71
|
+
unless block_given?
|
72
|
+
send(enumerator) { |callback| callback.call(object) }
|
73
|
+
else
|
74
|
+
send(enumerator) do |callback|
|
75
|
+
result = callback.call(object)
|
76
|
+
break result if terminator.call(result, object)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def replace_or_append!(chain)
|
82
|
+
if index = index(chain)
|
83
|
+
self[index] = chain
|
84
|
+
else
|
85
|
+
self << chain
|
86
|
+
end
|
87
|
+
self
|
88
|
+
end
|
89
|
+
|
90
|
+
def find(callback, &block)
|
91
|
+
select { |c| c == callback && (!block_given? || yield(c)) }.first
|
92
|
+
end
|
93
|
+
|
94
|
+
def delete(callback)
|
95
|
+
super(callback.is_a?(Callback) ? callback : find(callback))
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
def self.extract_options(*methods, &block)
|
100
|
+
methods.flatten!
|
101
|
+
options = methods.extract_options!
|
102
|
+
methods << block if block_given?
|
103
|
+
return methods, options
|
104
|
+
end
|
105
|
+
|
106
|
+
def extract_options(*methods, &block)
|
107
|
+
self.class.extract_options(*methods, &block)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
class Callback
|
112
|
+
attr_reader :kind, :method, :identifier, :options
|
113
|
+
|
114
|
+
def initialize(kind, method, options={})
|
115
|
+
@kind = kind
|
116
|
+
@method = method
|
117
|
+
@identifier = options[:identifier]
|
118
|
+
@options = options
|
119
|
+
end
|
120
|
+
|
121
|
+
def ==(other)
|
122
|
+
case other
|
123
|
+
when Callback
|
124
|
+
(self.identifier && self.identifier == other.identifier) || self.method == other.method
|
125
|
+
else
|
126
|
+
(self.identifier && self.identifier == other) || self.method == other
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def eql?(other)
|
131
|
+
self == other
|
132
|
+
end
|
133
|
+
|
134
|
+
def dup
|
135
|
+
self.class.new(@kind, @method, @options.dup)
|
136
|
+
end
|
137
|
+
|
138
|
+
def hash
|
139
|
+
if @identifier
|
140
|
+
@identifier.hash
|
141
|
+
else
|
142
|
+
@method.hash
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def call(*args, &block)
|
147
|
+
evaluate_method(method, *args, &block) if should_run_callback?(*args)
|
148
|
+
rescue LocalJumpError
|
149
|
+
raise ArgumentError,
|
150
|
+
"Cannot yield from a Proc type filter. The Proc must take two " +
|
151
|
+
"arguments and execute #call on the second argument."
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
def evaluate_method(method, *args, &block)
|
156
|
+
case method
|
157
|
+
when Symbol
|
158
|
+
object = args.shift
|
159
|
+
object.send(method, *args, &block)
|
160
|
+
when String
|
161
|
+
eval(method, args.first.instance_eval { binding })
|
162
|
+
when Proc, Method
|
163
|
+
method.call(*args, &block)
|
164
|
+
else
|
165
|
+
if method.respond_to?(kind)
|
166
|
+
method.send(kind, *args, &block)
|
167
|
+
else
|
168
|
+
raise ArgumentError,
|
169
|
+
"Callbacks must be a symbol denoting the method to call, a string to be evaluated, " +
|
170
|
+
"a block to be invoked, or an object responding to the callback method."
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def should_run_callback?(*args)
|
176
|
+
[options[:if]].flatten.compact.all? { |a| evaluate_method(a, *args) } &&
|
177
|
+
![options[:unless]].flatten.compact.any? { |a| evaluate_method(a, *args) }
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
@@ -11,6 +11,7 @@ module Wonkavision
|
|
11
11
|
|
12
12
|
handler.write_inheritable_attribute :maps, []
|
13
13
|
handler.class_inheritable_reader :maps
|
14
|
+
|
14
15
|
end
|
15
16
|
|
16
17
|
module ClassMethods
|
@@ -36,23 +37,53 @@ module Wonkavision
|
|
36
37
|
def handle(name,*args,&block)
|
37
38
|
binding = Wonkavision::EventBinding.new(name,self,*args)
|
38
39
|
binding.subscribe_to_events do |event_data,event_path|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
when 1 then yield event_data
|
44
|
-
else yield
|
45
|
-
end
|
46
|
-
end
|
40
|
+
ctx = Wonkavision::EventContext.new(event_data,event_path,binding,block)
|
41
|
+
handler = instantiate_handler(ctx)
|
42
|
+
handler.instance_variable_set(:@wonkavision_event_context, ctx)
|
43
|
+
handler.handle_event
|
47
44
|
end
|
48
45
|
bindings << binding
|
49
46
|
binding
|
50
47
|
end
|
48
|
+
|
49
|
+
def instantiate_handler(event_context)
|
50
|
+
self.new
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
module InstanceMethods
|
56
|
+
|
57
|
+
def handled?
|
58
|
+
@wonkavision_event_handled ||= false
|
59
|
+
end
|
60
|
+
|
61
|
+
def handled=(handled)
|
62
|
+
@wonkavision_event_handled = handled
|
63
|
+
end
|
64
|
+
|
65
|
+
def event_context
|
66
|
+
@wonkavision_event_context
|
67
|
+
end
|
51
68
|
|
52
|
-
|
69
|
+
def handle_event
|
70
|
+
ctx = @wonkavision_event_context
|
71
|
+
ctx.data = map(ctx.data,ctx.path)
|
72
|
+
handler = ctx.callback
|
53
73
|
|
54
|
-
|
55
|
-
|
74
|
+
if handler && handler.respond_to?(:call) && handler.respond_to?(:arity)
|
75
|
+
case handler.arity
|
76
|
+
when 3 then handler.call(ctx.data,ctx.path,self)
|
77
|
+
when 2 then handler.call(ctx.data,ctx.path)
|
78
|
+
when 1 then handler.call (ctx.data)
|
79
|
+
else instance_eval &handler
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
protected
|
85
|
+
def map(data,path)
|
86
|
+
self.class.maps.each do |map_def|
|
56
87
|
condition = map_def[0]
|
57
88
|
map_block = map_def[1]
|
58
89
|
return Wonkavision::MessageMapper.execute(map_block,data) if map?(condition,data,path)
|
@@ -61,18 +92,16 @@ module Wonkavision
|
|
61
92
|
end
|
62
93
|
|
63
94
|
def map?(condition,data,path)
|
64
|
-
return true unless condition && condition.to_s != 'all'
|
95
|
+
return true unless condition && condition.to_s != 'all' && condition.to_s != '*'
|
65
96
|
return path =~ condition if condition.is_a?(Regexp)
|
66
97
|
if (condition.is_a?(Proc))
|
67
98
|
return condition.call if condition.arity <= 0
|
68
99
|
return condition.call(path) if condition.arity == 1
|
69
100
|
return condition.call(path,data)
|
70
101
|
end
|
71
|
-
#default behavior
|
72
|
-
header.properties[:routing_key] == filter.to_s
|
73
102
|
end
|
74
103
|
end
|
75
|
-
|
104
|
+
|
76
105
|
end
|
77
106
|
end
|
78
107
|
end
|
@@ -27,20 +27,21 @@ module Wonkavision
|
|
27
27
|
module ClassMethods
|
28
28
|
|
29
29
|
def milestone(name,*args)
|
30
|
-
timeline_milestones << event(name,*args) do
|
31
|
-
|
32
|
-
|
30
|
+
timeline_milestones << event(name,*args) do
|
31
|
+
ctx = @wonkavision_event_context
|
32
|
+
event_time = self.class.extract_event_time(ctx.data,ctx.path)
|
33
|
+
prev_event_time = self[timeline_field][name]
|
33
34
|
unless prev_event_time
|
34
|
-
|
35
|
+
self[timeline_field][name] = event_time
|
35
36
|
#If the event being processed happened earlier than a previously
|
36
37
|
#recorded event, we don't want to overwrite state of the activity, as
|
37
38
|
#it is already more up to date than the incoming event.
|
38
|
-
latest_ms =
|
39
|
+
latest_ms = self[latest_milestone_field]
|
39
40
|
unless latest_ms &&
|
40
|
-
(last_event =
|
41
|
+
(last_event = self[timeline_field][latest_ms]) &&
|
41
42
|
last_event > event_time
|
42
|
-
update_activity(
|
43
|
-
|
43
|
+
self.class.update_activity(self,ctx.data)
|
44
|
+
self[latest_milestone_field] = name
|
44
45
|
end
|
45
46
|
:updated
|
46
47
|
else
|
@@ -56,7 +57,6 @@ module Wonkavision
|
|
56
57
|
time ? time.to_time : nil
|
57
58
|
end
|
58
59
|
|
59
|
-
private
|
60
60
|
def extract_event_time(event_data,event_path)
|
61
61
|
convert_time(event_data.delete(event_time_key.to_s)) || Time.now.utc
|
62
62
|
end
|
data/lib/wonkavision/version.rb
CHANGED
data/test/event_handler_test.rb
CHANGED
@@ -55,6 +55,15 @@ class EventHandlerTest < ActiveSupport::TestCase
|
|
55
55
|
Wonkavision.event_coordinator.receive_event("vermicious/oompa",1);
|
56
56
|
assert_equal 1, TestEventHandler.knids.length
|
57
57
|
end
|
58
|
+
|
59
|
+
should "process any defined callbacks" do
|
60
|
+
TestEventHandler.reset
|
61
|
+
|
62
|
+
Wonkavision.event_coordinator.receive_event("vermicious/knid",1)
|
63
|
+
#3 execs of each kind of callback, one for event subscription, one for namespace sub and global sub
|
64
|
+
assert_equal 3 * 2, TestEventHandler.callbacks.length
|
65
|
+
end
|
66
|
+
|
58
67
|
end
|
59
68
|
|
60
69
|
end
|