onfire 0.1.2 → 0.2.0

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,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