wonkavision 0.5.4 → 0.5.9
Sign up to get free protection for your applications and to get access to all the features.
- 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
|