wonkavision 0.5.1 → 0.5.2

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