onfire 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,10 @@
1
+ h2. 0.2.0
2
+
3
+ h3. Changes
4
+ * Bubbling now happens in Event. Removed Onfire#bubble_event and friends. This is a lot more flexible and simpler.
5
+ * Changed option for @#on@: @:do@ is now @:call@.
6
+
7
+
1
8
  h2. 0.1.2
2
9
 
3
10
  h3. Changes
@@ -2,54 +2,60 @@ require 'onfire/event'
2
2
  require 'onfire/event_table'
3
3
 
4
4
  module Onfire
5
+ # Attachs an event observer to the receiver which is called by a matching event.
6
+ #
7
+ # The handler might be a block receiving the triggering event.
8
+ #
9
+ # matz.on :applaus do |evt|
10
+ # puts "grin"
11
+ # end
12
+ #
13
+ # You can also pass a callable object as handler.
14
+ #
15
+ # class GrinHandler
16
+ # def call(evt)
17
+ # puts "grin"
18
+ # end
19
+ # end
20
+ #
21
+ # matz.on :applaus, :do => GrinHandler.new
22
+ #
23
+ # The +:from+ option allows conditional filtering.
24
+ #
25
+ # matz.on :applaus, :from => audience do |evt|
26
+ #
27
+ # This handler is called only if +audience+ trigger the +:applaus+ event.
5
28
  def on(event_type, options={}, &block)
6
- table_options = {}
7
- table_options[:event_type] = event_type
8
- table_options[:source_name] = options[:from] if options[:from]
29
+ options[:event_type] = event_type
9
30
 
10
- if block_given?
11
- return attach_event_handler(block, table_options)
12
- end
13
-
14
- attach_event_handler(options[:do], table_options)
31
+ event_table.add_handler(block || options[:call], options)
15
32
  end
16
33
 
34
+ # Fires an event which will bubble up starting from the receiver. While bubbling,
35
+ # the events checks the traversed object for matching observers and calls the handlers
36
+ # in the order they were attached.
37
+ #
38
+ # Notice that you can append payload data to the event object.
39
+ #
40
+ # fire :click, :time => Time.now
41
+ #
42
+ # The payload is accessable in the +data+ attribute.
43
+ #
44
+ # evt.data[:time] # => 2011-03-12 11:25:57 +0100
17
45
  def fire(event_type, data={})
18
- bubble_event event_for(event_type, self, data)
19
- end
20
-
21
- def bubble_event(event)
22
- process_event(event) # locally process event, then climb up.
23
- return if root?
24
-
25
- parent.bubble_event(event)
26
- end
27
-
28
- def process_event(event)
29
- local_event_handlers(event).each do |proc|
30
- return if event.stopped?
31
- proc.call(event)
32
- end
33
- end
34
-
35
- def root?
36
- !parent
46
+ event_for(event_type, self, data).bubble!
37
47
  end
38
48
 
39
49
  def event_table
40
50
  @event_table ||= Onfire::EventTable.new
41
51
  end
42
-
43
- protected
44
- def attach_event_handler(proc, table_options)
45
- event_table.add_handler(proc, table_options)
46
- end
47
52
 
48
- # Get all handlers from self for the passed event.
49
- def local_event_handlers(event)
53
+ # Get all handlers from self for the passed event (interface for Event).
54
+ def handlers_for_event(event)
50
55
  event_table.all_handlers_for(event.type, event.source)
51
56
  end
52
57
 
58
+ protected
53
59
  # Factory method for creating the event. Override if you want your own event.
54
60
  def event_for(*args)
55
61
  Event.new(*args)
@@ -0,0 +1,37 @@
1
+ module Onfire
2
+ class Event
3
+ module Debugging
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ # Blocks added with #debug are executed on every node while traversing up.
10
+ #
11
+ # Event.debug do |node, evt|
12
+ # puts "traversing #{node}"
13
+ # end
14
+ def debug(&block)
15
+ debug_blocks << block
16
+ end
17
+
18
+ def debug_blocks
19
+ @debug_blocks ||= []
20
+ end
21
+ end
22
+
23
+ def process_node(node)
24
+ self.class.debug_blocks.each do |debugger|
25
+ debugger.call(node, self, node.handlers_for_event(self))
26
+ end
27
+
28
+ super
29
+ end
30
+
31
+ # Currently unused.
32
+ def call_handler(proc, node)
33
+ super
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,11 +1,12 @@
1
1
  module Onfire
2
- # An Event is born in #fire and is passed up the ancestor chain of the triggering datastructure.
3
- # It carries a <tt>type</tt>, the fireing widget <tt>source</tt> and arbitrary payload <tt>data</tt>.
2
+ # An event carries a type, source, and optional payload. Calling #bubble! it starts at
3
+ # its source and traverses up the hierarchy using #parent.
4
+ # On every node it calls #handlers_for_event on the node while calling returned procs.
4
5
  class Event
5
6
 
6
7
  attr_accessor :type, :source, :data
7
8
 
8
- def initialize(type=nil, source=nil, data=nil)
9
+ def initialize(type, source, data=nil)
9
10
  @type = type
10
11
  @source = source
11
12
  @data = data
@@ -20,12 +21,25 @@ module Onfire
20
21
  @stopped = true
21
22
  end
22
23
 
23
- ### FIXME: what about serialization? should we simply forget the source?
24
- def _dump(depth)
25
- ""
26
- end
27
- def self._load(str)
28
- ::Onfire::Event.new
24
+ module ProcessingMethods
25
+ def bubble!
26
+ node = source
27
+ # in a real visitor pattern, the visited would call #process_node.
28
+ begin process_node(node) end while node = node.parent
29
+ end
30
+
31
+ def process_node(node)
32
+ node.handlers_for_event(self).each do |proc|
33
+ return if stopped?
34
+ call_handler(proc, node)
35
+ end
36
+ end
37
+
38
+ def call_handler(proc, node)
39
+ proc.call(self)
40
+ end
29
41
  end
42
+
43
+ include ProcessingMethods
30
44
  end
31
45
  end
@@ -9,7 +9,7 @@ module Onfire
9
9
 
10
10
  def add_handler(handler, opts)
11
11
  event_type = opts[:event_type]
12
- source_name = opts[:source_name] || nil
12
+ source_name = opts[:from] || nil
13
13
 
14
14
  handlers_for(event_type, source_name) << handler
15
15
  end
@@ -1,3 +1,3 @@
1
1
  module Onfire
2
- VERSION = '0.1.2'
2
+ VERSION = '0.2.0'
3
3
  end
@@ -0,0 +1,56 @@
1
+ require 'test_helper'
2
+ require 'onfire/debugging'
3
+
4
+ class DebuggingTest < Test::Unit::TestCase
5
+ attr_reader :list
6
+
7
+ context "mixing in the Debugging module" do
8
+ setup do
9
+ @barkeeper = mock('barkeeper')
10
+ @guest = mock('guest')
11
+ @guest.parent = @barkeeper
12
+
13
+
14
+ @evt_class = Class.new(Onfire::Event)
15
+ @evt_class.class_eval do
16
+ include Onfire::Event::Debugging
17
+ end
18
+ end
19
+
20
+ context ".debug" do
21
+ should "set debug_event_blocks" do
22
+ @proc_1 = Proc.new {}
23
+ @proc_2 = Proc.new {}
24
+
25
+ @evt_class.debug &@proc_1
26
+ @evt_class.debug &@proc_1
27
+
28
+ assert_equal [@proc_1, @proc_2], @evt_class.debug_blocks
29
+ end
30
+ end
31
+
32
+ context "#bubble!" do
33
+ setup do
34
+ @list = ""
35
+ @evt_class.debug do |node, evt|
36
+ list << "@ #{node.name} with event type #{evt.type}"
37
+ end
38
+ end
39
+
40
+ should "be invoked at every node" do
41
+ @evt_class.new(:cheers, @barkeeper).bubble!
42
+
43
+ assert_equal "@ barkeeper with event type cheers", list
44
+ end
45
+
46
+ should "call the debug blocks at every processing step" do
47
+ @guest.on :cheers do
48
+ list << " | Cheers!"
49
+ end
50
+ @evt_class.new(:cheers, @guest).bubble!
51
+
52
+ assert_equal "@ guest with event type cheers | Cheers!@ barkeeper with event type cheers", @list
53
+ end
54
+ end
55
+ end
56
+ end
@@ -8,17 +8,17 @@ class EventTableTest < Test::Unit::TestCase
8
8
  end
9
9
 
10
10
  should "return an empty array when it can't find handlers" do
11
- @head.add_handler :drink, :source_name => :stomach, :event_type => :thirsty
11
+ @head.add_handler :drink, :from => :stomach, :event_type => :thirsty
12
12
 
13
13
  assert_equal [], @head.handlers_for( :hungry, :stomach)
14
14
  assert_equal [], @head.all_handlers_for(:hungry, :stomach)
15
15
  end
16
16
 
17
17
  should "return handlers in the same order as they were added" do
18
- @head.add_handler :drink, :source_name => :stomach, :event_type => :hungry
19
- @head.add_handler :eat, :source_name => :stomach, :event_type => :hungry
20
- @head.add_handler :sip, :source_name => :mouth, :event_type => :dry
21
- @head.add_handler :have_desert, :event_type => :hungry
18
+ @head.add_handler :drink, :from => :stomach, :event_type => :hungry
19
+ @head.add_handler :eat, :from => :stomach, :event_type => :hungry
20
+ @head.add_handler :sip, :from => :mouth, :event_type => :dry
21
+ @head.add_handler :have_desert, :event_type => :hungry
22
22
 
23
23
  assert_equal [:sip], @head.handlers_for(:dry, :mouth)
24
24
  assert_equal [:sip], @head.all_handlers_for(:dry, :mouth)
@@ -2,7 +2,7 @@ require 'test_helper'
2
2
 
3
3
  class EventTest < Test::Unit::TestCase
4
4
  context "An event" do
5
- should "accept its type and source in the constructor" do
5
+ should "accept type and source in the constructor" do
6
6
  event = Onfire::Event.new(:click, :source)
7
7
 
8
8
  assert_equal :click, event.type
@@ -10,14 +10,6 @@ class EventTest < Test::Unit::TestCase
10
10
  assert_nil event.data
11
11
  end
12
12
 
13
- should "be fine without any parameters at all" do
14
- event = Onfire::Event.new
15
-
16
- assert_nil event.type
17
- assert_nil event.source
18
- assert_nil event.data
19
- end
20
-
21
13
  should "accept payload data" do
22
14
  event = Onfire::Event.new(:drag, :source, :target => 'me')
23
15
 
@@ -27,7 +19,7 @@ class EventTest < Test::Unit::TestCase
27
19
  end
28
20
 
29
21
  should "stop if needed" do
30
- event = Onfire::Event.new
22
+ event = Onfire::Event.new(:drag, :source)
31
23
 
32
24
  assert ! event.stopped?
33
25
  event.stop!
@@ -35,3 +27,103 @@ class EventTest < Test::Unit::TestCase
35
27
  end
36
28
  end
37
29
  end
30
+
31
+ class EventFunctionalTest < Test::Unit::TestCase
32
+ context "#bubble!" do
33
+ setup do
34
+ @guest = mock('guest')
35
+ @barkeeper = mock("barkeeper")
36
+ @event = Onfire::Event.new(:cheers, @guest)
37
+ @event.data = {:list => []}
38
+ end
39
+
40
+ should "visit a root source without handlers" do
41
+ @event.bubble!
42
+ assert_equal [], @event.data[:list]
43
+ end
44
+
45
+ should "visit the source with multiple handlers when it's root" do
46
+ @guest.on :cheers do |evt|
47
+ evt.data[:list] << "yo"
48
+ end
49
+
50
+ @guest.on :cheers do |evt|
51
+ evt.data[:list] << "man"
52
+ end
53
+
54
+ @event.bubble!
55
+ assert_equal ["yo", "man"], @event.data[:list]
56
+ end
57
+
58
+ should "call appropriate handlers, only" do
59
+ @guest.on :cheers do |evt|
60
+ evt.data[:list] << "yo"
61
+ end
62
+
63
+ @guest.on :drink do |evt|
64
+ evt.data[:list] << "yummy"
65
+ end
66
+
67
+ @event.bubble!
68
+ assert_equal %w(yo), @event.data[:list]
69
+ end
70
+
71
+ should "visit all elements in the path" do
72
+ @guest.parent = @barkeeper
73
+
74
+ @barkeeper.on :cheers do |evt|
75
+ evt.data[:list] << "Thanks!"
76
+ end
77
+
78
+ @guest.on :cheers do |evt|
79
+ evt.data[:list] << "yo!"
80
+ end
81
+
82
+ @guest.on :cheers do |evt|
83
+ evt.data[:list] << "Cheers!"
84
+ end
85
+
86
+ @event.bubble!
87
+ assert_equal %w(yo! Cheers! Thanks!), @event.data[:list]
88
+ end
89
+
90
+ should "invoke handlers in correct order when having nested triggers" do
91
+ list = []
92
+ @guest.parent = @barkeeper
93
+
94
+ @guest.on :cheers do
95
+ list << 'Cheers!'
96
+ @guest.fire :order
97
+ end
98
+ @guest.on :cheers do
99
+ list << 'Hey!'
100
+ end
101
+ @barkeeper.on :cheers do
102
+ list << 'Thanks.'
103
+ end
104
+ @barkeeper.on :order do
105
+ list << 'Enjoy.'
106
+ end
107
+
108
+ @event.bubble!
109
+
110
+ assert_equal %w(Cheers! Enjoy. Hey! Thanks.), list
111
+ end
112
+
113
+ should "not invoke any handler after being stopped" do
114
+ @guest.parent = @barkeeper
115
+
116
+ @barkeeper.on :cheers do |evt|
117
+ evt.data[:list] << "Thanks!"
118
+ end
119
+
120
+ @guest.on :cheers do |evt|
121
+ evt.data[:list] << "yo!"
122
+ evt.stop!
123
+ end
124
+
125
+ @event.bubble!
126
+ assert_equal %w(yo!), @event.data[:list]
127
+ end
128
+ end
129
+ end
@@ -1,21 +1,6 @@
1
1
  require 'test_helper'
2
2
 
3
3
  class OnfireTest < Test::Unit::TestCase
4
- def mock(name='my_mock')
5
- obj = Class.new do
6
- attr_accessor :list
7
- attr_accessor :name
8
- attr_accessor :parent
9
-
10
- include Onfire
11
-
12
- def initialize(name)
13
- @name = name
14
- @list = []
15
- end
16
- end.new(name)
17
- end
18
-
19
4
  context "including Onfire" do
20
5
  should "provide event_table accessors to an emtpy table" do
21
6
  table = mock.event_table
@@ -24,74 +9,6 @@ class OnfireTest < Test::Unit::TestCase
24
9
  end
25
10
  end
26
11
 
27
- context "#process_event" do
28
- setup do
29
- @event = Onfire::Event.new(:click, mock)
30
- end
31
-
32
-
33
- should "not fill #list as there are no event handlers attached" do
34
- obj = mock
35
- obj.process_event(@event)
36
-
37
- assert_equal [], obj.list
38
- end
39
-
40
- should "invoke exactly one proc and thus push `1` onto #list" do
41
- obj = mock
42
- obj.event_table.add_handler(lambda { |evt| obj.list << 1 }, :event_type => :click)
43
-
44
- obj.process_event(@event)
45
-
46
- assert_equal [1], obj.list
47
- end
48
-
49
- should "not invoke procs for another event_type" do
50
- obj = mock
51
- obj.event_table.add_handler(lambda { |evt| obj.list << 1 }, :event_type => :click)
52
- obj.event_table.add_handler(lambda { |evt| obj.list << 2 }, :event_type => :drop) # don't call me!
53
-
54
- obj.process_event(@event)
55
-
56
- assert_equal [1], obj.list
57
- end
58
- end
59
-
60
- context "calling #on" do
61
- setup do
62
- @obj = mock
63
- @event = Onfire::Event.new(:click, @obj)
64
- end
65
-
66
- context "with a block" do
67
- should "add a handler to the event_table when called with a block" do
68
- @obj.on :click do
69
- @obj.list << 1
70
- end
71
-
72
- @obj.process_event(@event)
73
- assert_equal [1], @obj.list
74
- end
75
-
76
- should "invoke two handlers if called twice" do
77
- @obj.on :click do @obj.list << 1 end
78
- @obj.on :click do @obj.list << 2 end
79
-
80
- @obj.process_event(@event)
81
- assert_equal [1,2], @obj.list
82
- end
83
-
84
- should "receive the triggering event as parameter" do
85
- @obj.on :click do |evt|
86
- @obj.list << evt
87
- end
88
-
89
- @obj.process_event(@event)
90
- assert_equal [@event], @obj.list
91
- end
92
- end
93
- end
94
-
95
12
  context "In the bar" do
96
13
  setup do
97
14
  @barkeeper = mock('barkeeper')
@@ -146,14 +63,14 @@ class OnfireTest < Test::Unit::TestCase
146
63
  @callable.instance_eval do
147
64
  def call(event)
148
65
  source = event.source
149
- return source.list << 'order from barkeeper' if source.root?
66
+ return source.list << 'order from barkeeper' unless source.parent
150
67
  source.parent.list << 'order from guest'
151
68
  end
152
69
  end
153
70
  end
154
71
 
155
72
  should "add a handler to the local event_table" do
156
- @barkeeper.on :order, :do => @callable
73
+ @barkeeper.on :order, :call => @callable
157
74
 
158
75
  @barkeeper.fire :order
159
76
  assert_equal ['order from barkeeper'], @barkeeper.list
@@ -165,21 +82,6 @@ class OnfireTest < Test::Unit::TestCase
165
82
  end
166
83
 
167
84
 
168
- context "stopping events" do
169
- should "not invoke any handler after the guest kills it" do
170
- @nice_guest.on(:order) {@nice_guest.list << 'thirsty?'}
171
- @nice_guest.on(:order) do |evt|
172
- @nice_guest.list << 'money?'
173
- evt.stop!
174
- end
175
- @barkeeper.on(:order) {@nice_guest.list << 'draw a beer'}
176
- @nice_guest.fire :order
177
-
178
- assert_equal ['thirsty?', 'money?'], @nice_guest.list
179
- end
180
- end
181
-
182
-
183
85
  context "#event_table" do
184
86
  should "expose the EventTable to the public" do
185
87
  assert_kind_of ::Onfire::EventTable, @barkeeper.event_table
@@ -193,12 +95,6 @@ class OnfireTest < Test::Unit::TestCase
193
95
  @obj = mock
194
96
  end
195
97
 
196
- should "be of no relevance when there are no handlers attached" do
197
- @obj.fire :click
198
-
199
- assert_equal [], @obj.list
200
- end
201
-
202
98
  should "invoke the attached matching handler" do
203
99
  @obj.on :click do @obj.list << 1 end
204
100
  @obj.fire :click
@@ -213,32 +109,6 @@ class OnfireTest < Test::Unit::TestCase
213
109
  assert_equal [], @obj.list
214
110
  end
215
111
 
216
-
217
- should "invoke handlers in the correct order when bubbling" do
218
- # we use @obj for recording the chat.
219
- bar = mock('bar')
220
- guest = mock('guest')
221
-
222
- guest.parent = bar
223
- guest.on :thirsty do
224
- @obj.list << 'A beer!'
225
- guest.fire :order
226
- end
227
- guest.on :thirsty do
228
- @obj.list << 'Hurry up, man!'
229
- end
230
- bar.on :thirsty do
231
- @obj.list << 'Thanks.'
232
- end
233
- bar.on :order do
234
- @obj.list << 'There you go.'
235
- end
236
-
237
- guest.fire :thirsty
238
-
239
- assert_equal ['A beer!', 'There you go.', 'Hurry up, man!', 'Thanks.'], @obj.list
240
- end
241
-
242
112
  should "allow appending arbitrary data to the event" do
243
113
  # we use @obj for recording the chat.
244
114
  bar = mock('bar')
@@ -282,21 +152,4 @@ class OnfireTest < Test::Unit::TestCase
282
152
  end
283
153
  end
284
154
  end
285
-
286
-
287
- context "#root?" do
288
- setup do
289
- @obj = mock
290
- end
291
-
292
- should "return false if we got parents" do
293
- @obj.parent = :daddy
294
- assert !@obj.root?
295
- end
296
-
297
- should "return true if we're at the top" do
298
- assert @obj.root?
299
- end
300
- end
301
-
302
155
  end
@@ -8,4 +8,19 @@ class Onfire::EventTable
8
8
  memo + evts[1].inject(0) {|sum, h| sum + h[1].size} # h => [key, value].
9
9
  end || 0
10
10
  end
11
- end
11
+ end
12
+
13
+ class Test::Unit::TestCase
14
+ def mock(name='my_mock')
15
+ obj = Class.new do
16
+ include Onfire
17
+
18
+ attr_accessor :list, :name, :parent
19
+
20
+ def initialize(name)
21
+ @name = name
22
+ @list = []
23
+ end
24
+ end.new(name)
25
+ end
26
+ end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 1
8
7
  - 2
9
- version: 0.1.2
8
+ - 0
9
+ version: 0.2.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Nick Sutterer
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2011-02-08 00:00:00 +01:00
17
+ date: 2011-03-12 00:00:00 +01:00
18
18
  default_executable:
19
19
  dependencies: []
20
20
 
@@ -33,10 +33,12 @@ files:
33
33
  - README.textile
34
34
  - Rakefile
35
35
  - lib/onfire.rb
36
+ - lib/onfire/debugging.rb
36
37
  - lib/onfire/event.rb
37
38
  - lib/onfire/event_table.rb
38
39
  - lib/onfire/version.rb
39
40
  - onfire.gemspec
41
+ - test/debugging_test.rb
40
42
  - test/event_table_test.rb
41
43
  - test/event_test.rb
42
44
  - test/onfire_test.rb