wonkavision 0.5.4 → 0.5.9
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.
- data/CHANGELOG.rdoc +28 -16
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -21
- data/Rakefile +47 -47
- data/lib/wonkavision.rb +75 -74
- data/lib/wonkavision/acts_as_oompa_loompa.rb +22 -22
- data/lib/wonkavision/event_binding.rb +21 -21
- data/lib/wonkavision/event_context.rb +9 -9
- data/lib/wonkavision/event_coordinator.rb +75 -75
- data/lib/wonkavision/event_handler.rb +15 -15
- data/lib/wonkavision/event_namespace.rb +79 -79
- data/lib/wonkavision/event_path_segment.rb +35 -35
- data/lib/wonkavision/message_mapper.rb +30 -30
- data/lib/wonkavision/message_mapper/indifferent_access.rb +30 -26
- data/lib/wonkavision/message_mapper/map.rb +241 -153
- data/lib/wonkavision/persistence/mongo_mapper_adapter.rb +32 -32
- data/lib/wonkavision/persistence/mongoid_adapter.rb +32 -0
- data/lib/wonkavision/plugins.rb +30 -30
- data/lib/wonkavision/plugins/business_activity.rb +92 -92
- data/lib/wonkavision/plugins/business_activity/event_binding.rb +15 -15
- data/lib/wonkavision/plugins/callbacks.rb +182 -182
- data/lib/wonkavision/plugins/event_handling.rb +111 -111
- data/lib/wonkavision/plugins/timeline.rb +79 -79
- data/lib/wonkavision/version.rb +3 -3
- data/test/business_activity_test.rb +31 -31
- data/test/event_handler_test.rb +68 -69
- data/test/event_namespace_test.rb +108 -108
- data/test/event_path_segment_test.rb +41 -41
- data/test/log/test.log +817 -18354
- data/test/map_test.rb +315 -201
- data/test/message_mapper_test.rb +20 -20
- data/test/test_activity_models.rb +72 -72
- data/test/test_helper.rb +70 -63
- data/test/timeline_test.rb +55 -61
- data/test/wonkavision_test.rb +9 -9
- metadata +72 -12
- data/wonkavision.gemspec +0 -97
@@ -0,0 +1,32 @@
|
|
1
|
+
module Wonkavision
|
2
|
+
module Mongoid
|
3
|
+
module Activity
|
4
|
+
|
5
|
+
def self.included(model)
|
6
|
+
model.send(:include,::Mongoid::Document)
|
7
|
+
model.extend(ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
include Wonkavision::ActsAsOompaLoompa
|
12
|
+
|
13
|
+
def define_document_key(key_name,key_type,options={})
|
14
|
+
options[:type] = key_type
|
15
|
+
field(key_name, options) unless fields[key_name]
|
16
|
+
end
|
17
|
+
|
18
|
+
def update_activity(activity,event_data)
|
19
|
+
activity.write_attributes(event_data)
|
20
|
+
:updated
|
21
|
+
end
|
22
|
+
|
23
|
+
def find_activity_instance(correlation_field_name,correlation_id)
|
24
|
+
self.find_or_create_by({correlation_field_name=>correlation_id})
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
data/lib/wonkavision/plugins.rb
CHANGED
@@ -1,30 +1,30 @@
|
|
1
|
-
#This concept (and code) shamelessly stolen from MongoMapper
|
2
|
-
#(http://railstips.org/blog/archives/2010/02/21/mongomapper-07-plugins/)
|
3
|
-
#as so much of my Ruby code tends to be. I added a few little things and changed some
|
4
|
-
#names to avoid conflicts.
|
5
|
-
module Wonkavision
|
6
|
-
module Plugins
|
7
|
-
def wonkavision_plugins
|
8
|
-
@wonkavision_plugins ||= []
|
9
|
-
end
|
10
|
-
|
11
|
-
def has_wonkavision_plugin?(plugin)
|
12
|
-
wonkavision_plugins.detect{|p|p==plugin}
|
13
|
-
end
|
14
|
-
|
15
|
-
def ensure_wonkavision_plugin(plugin,option={})
|
16
|
-
use(plugin,options) unless has_wonkavision_plugin?(plugin)
|
17
|
-
end
|
18
|
-
|
19
|
-
def plug(mod,options={})
|
20
|
-
extend mod::ClassMethods if mod.const_defined?(:ClassMethods)
|
21
|
-
include mod::InstanceMethods if mod.const_defined?(:InstanceMethods)
|
22
|
-
extend mod::Fields if mod.const_defined?(:Fields)
|
23
|
-
include mod::Fields if mod.const_defined?(:Fields)
|
24
|
-
mod.configure(self,options) if mod.respond_to?(:configure)
|
25
|
-
wonkavision_plugins << mod
|
26
|
-
end
|
27
|
-
alias use plug
|
28
|
-
|
29
|
-
end
|
30
|
-
end
|
1
|
+
#This concept (and code) shamelessly stolen from MongoMapper
|
2
|
+
#(http://railstips.org/blog/archives/2010/02/21/mongomapper-07-plugins/)
|
3
|
+
#as so much of my Ruby code tends to be. I added a few little things and changed some
|
4
|
+
#names to avoid conflicts.
|
5
|
+
module Wonkavision
|
6
|
+
module Plugins
|
7
|
+
def wonkavision_plugins
|
8
|
+
@wonkavision_plugins ||= []
|
9
|
+
end
|
10
|
+
|
11
|
+
def has_wonkavision_plugin?(plugin)
|
12
|
+
wonkavision_plugins.detect{|p|p==plugin}
|
13
|
+
end
|
14
|
+
|
15
|
+
def ensure_wonkavision_plugin(plugin,option={})
|
16
|
+
use(plugin,options) unless has_wonkavision_plugin?(plugin)
|
17
|
+
end
|
18
|
+
|
19
|
+
def plug(mod,options={})
|
20
|
+
extend mod::ClassMethods if mod.const_defined?(:ClassMethods)
|
21
|
+
include mod::InstanceMethods if mod.const_defined?(:InstanceMethods)
|
22
|
+
extend mod::Fields if mod.const_defined?(:Fields)
|
23
|
+
include mod::Fields if mod.const_defined?(:Fields)
|
24
|
+
mod.configure(self,options) if mod.respond_to?(:configure)
|
25
|
+
wonkavision_plugins << mod
|
26
|
+
end
|
27
|
+
alias use plug
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -1,93 +1,93 @@
|
|
1
|
-
module Wonkavision
|
2
|
-
module Plugins
|
3
|
-
module BusinessActivity
|
4
|
-
|
5
|
-
def self.all
|
6
|
-
@@all ||= []
|
7
|
-
end
|
8
|
-
|
9
|
-
|
10
|
-
def self.configure(activity,options={})
|
11
|
-
activity.write_inheritable_attribute :business_activity_options, {}
|
12
|
-
activity.class_inheritable_reader :business_activity_options
|
13
|
-
|
14
|
-
activity.write_inheritable_attribute :correlation_ids, []
|
15
|
-
activity.class_inheritable_reader :correlation_ids
|
16
|
-
|
17
|
-
BusinessActivity.all << activity
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.normalize_correlation_ids(*args)
|
21
|
-
model_field,event_field = if args.length == 1 then
|
22
|
-
case args[0]
|
23
|
-
when Hash then [args[0][:model], args[0][:event] || args[0][:model]]
|
24
|
-
else [args[0],args[0]]
|
25
|
-
end
|
26
|
-
else
|
27
|
-
[args[0],args[1] || args[0]]
|
28
|
-
end
|
29
|
-
{:model=>model_field,:event=>event_field}
|
30
|
-
end
|
31
|
-
|
32
|
-
module ClassMethods
|
33
|
-
def instantiate_handler(event_context)
|
34
|
-
correlation = event_context.binding.correlation
|
35
|
-
event_id = correlation ? (correlation[:event] || event_correlation_id_key) : event_correlation_id_key
|
36
|
-
model_id = correlation ? (correlation[:model] || correlation_id_field) : correlation_id_field
|
37
|
-
|
38
|
-
correlation_id = event_context.data[event_id.to_s]
|
39
|
-
find_activity_instance(model_id,correlation_id)
|
40
|
-
end
|
41
|
-
|
42
|
-
def event(name,*args,&block)
|
43
|
-
handle(name,args) do
|
44
|
-
ctx = @wonkavision_event_context
|
45
|
-
result = :ok
|
46
|
-
if (block_given?)
|
47
|
-
result = case block.arity
|
48
|
-
when 3 then yield ctx.data,ctx.path,self
|
49
|
-
when 2 then yield ctx.data, ctx.path
|
50
|
-
when 1 then yield ctx.data
|
51
|
-
else instance_eval &block
|
52
|
-
end
|
53
|
-
end
|
54
|
-
unless result == :handled
|
55
|
-
result = self.class.update_activity(self,ctx.data) unless result == :updated
|
56
|
-
save!
|
57
|
-
end
|
58
|
-
result
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
def correlate_by(*args)
|
63
|
-
return {:model=>correlation_id_field, :event=>event_correlation_id_key} if args.blank?
|
64
|
-
|
65
|
-
correlation = BusinessActivity.normalize_correlation_ids(*args)
|
66
|
-
|
67
|
-
business_activity_options[:correlation_id_field] = correlation[:model]
|
68
|
-
business_activity_options[:event_correlation_id_key] = correlation[:event]
|
69
|
-
|
70
|
-
define_document_key correlation_id_field, String, :index=>true
|
71
|
-
correlation_ids << correlation
|
72
|
-
end
|
73
|
-
|
74
|
-
def create_binding(name,handler,*args)
|
75
|
-
Wonkavision::Plugins::BusinessActivity::EventBinding.new(name,handler,*args)
|
76
|
-
end
|
77
|
-
|
78
|
-
end
|
79
|
-
|
80
|
-
module Fields
|
81
|
-
|
82
|
-
def correlation_id_field
|
83
|
-
business_activity_options[:correlation_id_field] ||= "id"
|
84
|
-
end
|
85
|
-
|
86
|
-
def event_correlation_id_key
|
87
|
-
business_activity_options[:event_correlation_id_key] || correlation_id_field
|
88
|
-
end
|
89
|
-
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
1
|
+
module Wonkavision
|
2
|
+
module Plugins
|
3
|
+
module BusinessActivity
|
4
|
+
|
5
|
+
def self.all
|
6
|
+
@@all ||= []
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
def self.configure(activity,options={})
|
11
|
+
activity.write_inheritable_attribute :business_activity_options, {}
|
12
|
+
activity.class_inheritable_reader :business_activity_options
|
13
|
+
|
14
|
+
activity.write_inheritable_attribute :correlation_ids, []
|
15
|
+
activity.class_inheritable_reader :correlation_ids
|
16
|
+
|
17
|
+
BusinessActivity.all << activity
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.normalize_correlation_ids(*args)
|
21
|
+
model_field,event_field = if args.length == 1 then
|
22
|
+
case args[0]
|
23
|
+
when Hash then [args[0][:model], args[0][:event] || args[0][:model]]
|
24
|
+
else [args[0],args[0]]
|
25
|
+
end
|
26
|
+
else
|
27
|
+
[args[0],args[1] || args[0]]
|
28
|
+
end
|
29
|
+
{:model=>model_field,:event=>event_field}
|
30
|
+
end
|
31
|
+
|
32
|
+
module ClassMethods
|
33
|
+
def instantiate_handler(event_context)
|
34
|
+
correlation = event_context.binding.correlation
|
35
|
+
event_id = correlation ? (correlation[:event] || event_correlation_id_key) : event_correlation_id_key
|
36
|
+
model_id = correlation ? (correlation[:model] || correlation_id_field) : correlation_id_field
|
37
|
+
|
38
|
+
correlation_id = event_context.data[event_id.to_s]
|
39
|
+
find_activity_instance(model_id,correlation_id)
|
40
|
+
end
|
41
|
+
|
42
|
+
def event(name,*args,&block)
|
43
|
+
handle(name,args) do
|
44
|
+
ctx = @wonkavision_event_context
|
45
|
+
result = :ok
|
46
|
+
if (block_given?)
|
47
|
+
result = case block.arity
|
48
|
+
when 3 then yield ctx.data,ctx.path,self
|
49
|
+
when 2 then yield ctx.data, ctx.path
|
50
|
+
when 1 then yield ctx.data
|
51
|
+
else instance_eval &block
|
52
|
+
end
|
53
|
+
end
|
54
|
+
unless result == :handled
|
55
|
+
result = self.class.update_activity(self,ctx.data) unless result == :updated
|
56
|
+
save!
|
57
|
+
end
|
58
|
+
result
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def correlate_by(*args)
|
63
|
+
return {:model=>correlation_id_field, :event=>event_correlation_id_key} if args.blank?
|
64
|
+
|
65
|
+
correlation = BusinessActivity.normalize_correlation_ids(*args)
|
66
|
+
|
67
|
+
business_activity_options[:correlation_id_field] = correlation[:model]
|
68
|
+
business_activity_options[:event_correlation_id_key] = correlation[:event]
|
69
|
+
|
70
|
+
define_document_key correlation_id_field, String, :index=>true
|
71
|
+
correlation_ids << correlation
|
72
|
+
end
|
73
|
+
|
74
|
+
def create_binding(name,handler,*args)
|
75
|
+
Wonkavision::Plugins::BusinessActivity::EventBinding.new(name,handler,*args)
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
module Fields
|
81
|
+
|
82
|
+
def correlation_id_field
|
83
|
+
business_activity_options[:correlation_id_field] ||= "id"
|
84
|
+
end
|
85
|
+
|
86
|
+
def event_correlation_id_key
|
87
|
+
business_activity_options[:event_correlation_id_key] || correlation_id_field
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
93
|
end
|
@@ -1,16 +1,16 @@
|
|
1
|
-
module Wonkavision
|
2
|
-
module Plugins
|
3
|
-
module BusinessActivity
|
4
|
-
class EventBinding < Wonkavision::EventBinding
|
5
|
-
attr_reader :correlation
|
6
|
-
def initialize(*args)
|
7
|
-
super(*args)
|
8
|
-
if (correlation_args = @options.delete(:correlate_by))
|
9
|
-
correlation_args = [correlation_args] unless correlation_args.is_a?(Array)
|
10
|
-
@correlation = BusinessActivity.normalize_correlation_ids(*correlation_args)
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
1
|
+
module Wonkavision
|
2
|
+
module Plugins
|
3
|
+
module BusinessActivity
|
4
|
+
class EventBinding < Wonkavision::EventBinding
|
5
|
+
attr_reader :correlation
|
6
|
+
def initialize(*args)
|
7
|
+
super(*args)
|
8
|
+
if (correlation_args = @options.delete(:correlate_by))
|
9
|
+
correlation_args = [correlation_args] unless correlation_args.is_a?(Array)
|
10
|
+
@correlation = BusinessActivity.normalize_correlation_ids(*correlation_args)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
16
|
end
|
@@ -1,182 +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
|
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
|