wonkavision 0.5.1 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
 
@@ -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
@@ -0,0 +1,9 @@
1
+ module Wonkavision
2
+ class EventContext
3
+ attr_accessor :data, :path, :binding, :callback
4
+
5
+ def initialize(data,path,binding,callback)
6
+ @data, @path, @binding, @callback = data, path, binding, callback
7
+ end
8
+ end
9
+ end
@@ -6,6 +6,7 @@ module Wonkavision
6
6
  handler.class_eval do
7
7
  extend Plugins
8
8
  use Plugins::EventHandling
9
+ use Plugins::Callbacks
9
10
  end
10
11
 
11
12
  super
@@ -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) if mod.respond_to?(:configure)
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 |data,path|
23
- activity = find_activity(data)
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 activity,data,path
28
- when 2 then yield activity,data
29
- when 1 then yield activity
30
- else yield
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(activity,data) unless result == :updated
35
- activity.save!
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
- def find_activity(event_data)
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
- event_data = map_data(event_data,event_path)
40
- if block_given?
41
- case block.arity
42
- when 2 then yield event_data,event_path
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
- private
69
+ def handle_event
70
+ ctx = @wonkavision_event_context
71
+ ctx.data = map(ctx.data,ctx.path)
72
+ handler = ctx.callback
53
73
 
54
- def map_data(data,path)
55
- maps.each do |map_def|
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
- #Code here
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 |activity,event_data,event_path|
31
- event_time = extract_event_time(event_data,event_path)
32
- prev_event_time = activity[timeline_field][name]
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
- activity[timeline_field][name] = event_time
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 = activity[latest_milestone_field]
39
+ latest_ms = self[latest_milestone_field]
39
40
  unless latest_ms &&
40
- (last_event = activity[timeline_field][latest_ms]) &&
41
+ (last_event = self[timeline_field][latest_ms]) &&
41
42
  last_event > event_time
42
- update_activity(activity,event_data)
43
- activity[latest_milestone_field] = name
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
@@ -1,3 +1,3 @@
1
1
  module Wonkavision
2
- VERSION = '0.5.1'
2
+ VERSION = '0.5.2'
3
3
  end
@@ -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