nilclass-onfire 0.1.1.nc

Sign up to get free protection for your applications and to get access to all the features.
data/README.textile ADDED
@@ -0,0 +1,231 @@
1
+ h1. Onfire
2
+
3
+ _Have bubbling events and observers in all your Ruby objects._
4
+
5
+
6
+ h2. Introduction
7
+
8
+ If you think "bubbling events" sounds awesome and should definitly be used in your project, you're lame. However, if you answer "yes" to at least one of the following requirements you're in. If not, go and use Ruby's great @Observable@ mixin.
9
+
10
+ *Do you...?*
11
+ * prefer *decoupled systems*, where observers don't wanna know the observed object (as @Observable#add_observer@ requires)?
12
+ * rather intend to *observe _events_*, not business objects alone?
13
+ * have a *tree-like* data structure? Bubbling events only make sense in a hierarchical environment where observers and event sources form a tree.
14
+ * miss a *stop!* command which prevents the event from further propagation?
15
+
16
+ h2. Example
17
+
18
+ Let's assume you have a set of @User@ objects with roles, like "CEO", "Manager", and "Developer". You just decided to implement some messaging system where developers can complain, managers can ignore, and the CEO is trying to control.
19
+
20
+ <pre>
21
+ CEO: bill
22
+ | |
23
+ Managers: mike matz
24
+ | |
25
+ Developers: dave didi
26
+ </pre>
27
+
28
+ If _dave_ would complain about a new policy (which implies exclusive usage of Win PCs only) it would bubble up to his manager _matz_ and then to _bill_, who'd fire _dave_ right away.
29
+
30
+ As _matz_ somehow likes his developers he would try to prevent his boss _bill_ from overhearing the conversation or make the complainment management-compatible. Good guy _matz_.
31
+
32
+
33
+ h2. Installation
34
+
35
+ <pre>
36
+ $ sudo gem install onfire
37
+ </pre>
38
+
39
+
40
+ h2. Usage
41
+
42
+ First, you extend your @User@ class to be "on fire".
43
+
44
+ <pre>
45
+ class User < ...
46
+ include Onfire
47
+ </pre>
48
+
49
+
50
+ As your @User@ objects don't have a tree structure *you* implement *@#parent@*. That's the *only requirement Onfire has* to the class it's mixed into.
51
+
52
+ *@#parent@* would return the boss object of the asked instance.
53
+
54
+ <pre>
55
+ dave.parent # => matz
56
+ matz.parent # => bill
57
+ bill.parent # => nil
58
+ </pre>
59
+
60
+ There's your hierarchical tree structure.
61
+
62
+ h2. Fireing events
63
+
64
+ Now _dave_ issues the bad circumstances in his office:
65
+
66
+ <pre>
67
+ dave.fire :thatSucks
68
+ </pre>
69
+
70
+ So far, nothing would happen as no one in the startup is observing that event.
71
+
72
+ h2. Responding to events
73
+
74
+ Anyway, a real CEO should respond to complainments from his subordinates.
75
+
76
+ <pre>
77
+ bill.on :thatSucks do puts "who's that?" end
78
+ </pre>
79
+
80
+ Now _bill_ would at least find out somebody's crying.
81
+
82
+ <pre>
83
+ > dave.fire :thatSucks
84
+ => "who's that?" # by bill
85
+ </pre>
86
+
87
+ That's right, the *Onfire API* is just the two public methods
88
+ * *@#on@* for responding to events and
89
+ * *@#fire@* for triggering those
90
+
91
+
92
+ h2. Bubbling events
93
+
94
+ _matz_ being a good manager wants to mediate, so he takes part in the game:
95
+
96
+ <pre>
97
+ matz.on :thatSucks do puts "dave, sshhht!" end
98
+ </pre>
99
+
100
+ Which results in
101
+
102
+ <pre>
103
+ > dave.fire :thatSucks
104
+ => "dave, sshhht!" # by matz
105
+ => "who's that?" # by bill
106
+ </pre>
107
+
108
+
109
+ h2. Using the @Event@ object
110
+
111
+ Of course _bill_ wants to find out who's the subversive element, so he just asks the revealing *Event* object.
112
+
113
+ <pre>
114
+ bill.on :thatSucks do |event| event.source.fire! end
115
+ </pre>
116
+
117
+ That's bad for _dave_, as he's unemployed now.
118
+
119
+
120
+ h2. Intercepting events
121
+
122
+ As _dave_ has always been on time, _matz_ just swallows any offending messages for now.
123
+
124
+ <pre>
125
+ matz.on :thatSucks do |event| event.stop! end
126
+ </pre>
127
+
128
+ That leads to an event that's stopped at _matz_. It won't propagate further up to the big boss.
129
+
130
+ <pre>
131
+ > dave.fire :thatSucks
132
+ => "dave, sshhht!" # first, by matz
133
+ => nil # second, matz stops the event.
134
+ </pre>
135
+
136
+ h2. Organic event filtering
137
+
138
+ What happens if _mike_ wants to be a good manager, too?
139
+
140
+ <pre>
141
+ mike.on :thatSucks do puts "take it easy, dude!" end
142
+ </pre>
143
+
144
+ When _dave_ starts to cry, there's no _mike_ involved:
145
+
146
+ <pre>
147
+ > dave.fire :thatSucks
148
+ => "dave, sshhht!" # by matz
149
+ => nil
150
+ </pre>
151
+
152
+ Obviously, the @:thatSucks@ event triggered by _dave_ never passes _mike_ as he is on a completely different tree branch. The event travels from _dave_ to _matz_ up to _bill_.
153
+
154
+ That is dead simple, however it is a clear way to observe only *particular events*. When _mike_ calls @#on@ he limits his attention to events from his branch - his developers - only.
155
+
156
+ h2. Event source filtering
157
+
158
+ After a while discontent moves over to _didi_.
159
+
160
+ <pre>
161
+ > didi.fire :thatSucks
162
+ => "dave, sshhht!" # first, by matz
163
+ => nil
164
+ </pre>
165
+
166
+ _didi_ is a lamer and _matz_ always prefered working with _dave_ so he changes his tune.
167
+
168
+ <pre>
169
+ matz.on :thatSucks do |event| event.stop! if event.source == dave end
170
+ </pre>
171
+
172
+ That's unfair!
173
+
174
+ <pre>
175
+ > dave.fire :thatSucks
176
+ => "dave, sshhht!" # by matz
177
+ => nil # dave still got a job.
178
+ > didi.fire :thatSucks
179
+ => "fired!" # didi's event travels up to boss who fires him.
180
+ </pre>
181
+
182
+ _matz_ is lazy, so he explicity lets Onfire handle the filtering:
183
+
184
+ <pre>
185
+ matz.on :thatSucks, :from => dave do |event| event.stop! end
186
+ </pre>
187
+
188
+ which will result in the same bad outcome for _didi_.
189
+
190
+ h2. Responding with instance methods
191
+
192
+ Nevertheless _matz_ is trying to keep himself clean, so he refactors the handler block to an instance method.
193
+
194
+ <pre>
195
+ matz.instance_eval do
196
+ def shield_dave(event)
197
+ event.stop!
198
+ end
199
+ end
200
+
201
+ matz.on :thatSucks, :from => dave, :do => :shield_dave
202
+ </pre>
203
+
204
+ Awesome shit!
205
+
206
+ h2. Who's using it?
207
+ * Right now, Onfire is used as clean, small event engine in "Apotomo":http://github.com/apotonick/apotomo, that's stateful widgets for Ruby and Rails.
208
+
209
+ h2. License
210
+
211
+ Copyright (c) 2010, Nick Sutterer
212
+
213
+ The MIT License
214
+
215
+ Permission is hereby granted, free of charge, to any person obtaining a copy
216
+ of this software and associated documentation files (the "Software"), to deal
217
+ in the Software without restriction, including without limitation the rights
218
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
219
+ copies of the Software, and to permit persons to whom the Software is
220
+ furnished to do so, subject to the following conditions:
221
+
222
+ The above copyright notice and this permission notice shall be included in
223
+ all copies or substantial portions of the Software.
224
+
225
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
226
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
227
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
228
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
229
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
230
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
231
+ THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,62 @@
1
+ # encoding: utf-8
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+ require File.join(File.dirname(__FILE__), 'lib', 'onfire', 'version')
6
+
7
+ desc 'Default: run unit tests.'
8
+ task :default => :test
9
+
10
+ desc 'Test the onfire library.'
11
+ Rake::TestTask.new(:test) do |test|
12
+ test.libs << ['lib', 'test']
13
+ test.pattern = 'test/*_test.rb'
14
+ test.verbose = true
15
+ end
16
+
17
+
18
+ # Gem managment tasks.
19
+ #
20
+ # == Bump gem version (any):
21
+ #
22
+ # rake version:bump:major
23
+ # rake version:bump:minor
24
+ # rake version:bump:patch
25
+ #
26
+ # == Generate gemspec, build & install locally:
27
+ #
28
+ # rake gemspec
29
+ # rake build
30
+ # sudo rake install
31
+ #
32
+ # == Git tag & push to origin/master
33
+ #
34
+ # rake release
35
+ #
36
+ # == Release to Gemcutter.org:
37
+ #
38
+ # rake gemcutter:release
39
+ #
40
+ begin
41
+ gem 'jeweler'
42
+ require 'jeweler'
43
+
44
+ Jeweler::Tasks.new do |spec|
45
+ spec.name = "nilclass-onfire"
46
+ spec.version = ::Onfire::VERSION
47
+ spec.summary = %{Have bubbling events and observers in all your Ruby objects.}
48
+ spec.description = spec.summary
49
+ spec.homepage = "http://github.com/apotonick/onfire"
50
+ spec.authors = ["Nick Sutterer"]
51
+ spec.email = "apotonick@gmail.com"
52
+
53
+ spec.files = FileList["[A-Z]*", File.join(*%w[{lib,test} ** *]).to_s]
54
+
55
+ # spec.add_dependency 'activesupport', '>= 2.3.0' # Dependencies and minimum versions?
56
+ end
57
+
58
+ Jeweler::GemcutterTasks.new
59
+ rescue LoadError
60
+ puts "Jeweler - or one of its dependencies - is not available. " <<
61
+ "Install it with: sudo gem install jeweler -s http://gemcutter.org"
62
+ end
@@ -0,0 +1,31 @@
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>.
4
+ class Event
5
+
6
+ attr_accessor :type, :source, :data
7
+
8
+ def initialize(type=nil, source=nil, data=nil)
9
+ @type = type
10
+ @source = source
11
+ @data = data
12
+ end
13
+
14
+ def stopped?
15
+ @stopped ||= false
16
+ end
17
+
18
+ # Stop event bubbling.
19
+ def stop!
20
+ @stopped = true
21
+ end
22
+
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
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,27 @@
1
+ module Onfire
2
+ # Keeps all event handlers attached to one object.
3
+ class EventTable
4
+ attr_accessor :source2evt
5
+
6
+ def initialize
7
+ @source2evt = {}
8
+ end
9
+
10
+ def add_handler(handler, opts)
11
+ event_type = opts[:event_type]
12
+ source_name = opts[:source_name] || nil
13
+
14
+ handlers_for(event_type, source_name) << handler
15
+ end
16
+
17
+ def handlers_for(event_type, source_name=nil)
18
+ evt_types = source2evt[source_name] ||= {}
19
+ evt_types[event_type] ||= []
20
+ end
21
+
22
+ # Returns all handlers, with :from first, then catch-all.
23
+ def all_handlers_for(event_type, source_name)
24
+ handlers_for(event_type, source_name) + handlers_for(event_type, nil)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,5 @@
1
+ # encoding: utf-8
2
+
3
+ module Onfire
4
+ VERSION = '0.1.1.nc'.freeze
5
+ end
data/lib/onfire.rb ADDED
@@ -0,0 +1,58 @@
1
+ require 'onfire/event'
2
+ require 'onfire/event_table'
3
+
4
+ module Onfire
5
+ 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]
9
+
10
+ if block_given?
11
+ return attach_event_handler(block, table_options)
12
+ end
13
+
14
+ attach_event_handler(options[:do], table_options)
15
+ end
16
+
17
+ def fire(event_type)
18
+ bubble_event Event.new(event_type, self)
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
+ handlers = local_event_handlers(event)
30
+ handlers.any? ? handlers.each do |proc|
31
+ return if event.stopped?
32
+ proc.call(event)
33
+ end : (@__default_event_handler__ && @__default_event_handler__.call(event))
34
+ end
35
+
36
+ def root?
37
+ !parent
38
+ end
39
+
40
+ def event_table
41
+ @event_table ||= Onfire::EventTable.new
42
+ end
43
+
44
+ def set_default_event_handler(method=nil, &block)
45
+ block = lambda { |evt| self.send(method, evt) } unless block_given?
46
+ @__default_event_handler__ = block
47
+ end
48
+
49
+ protected
50
+ def attach_event_handler(proc, table_options)
51
+ event_table.add_handler(proc, table_options)
52
+ end
53
+
54
+ # Get all handlers from self for the passed event.
55
+ def local_event_handlers(event)
56
+ event_table.all_handlers_for(event.type, event.source)
57
+ end
58
+ end
@@ -0,0 +1,30 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class EventTableTest < Test::Unit::TestCase
4
+
5
+ context "An EventTable" do
6
+ setup do
7
+ @head = Onfire::EventTable.new
8
+ end
9
+
10
+ should "return an empty array when it can't find handlers" do
11
+ @head.add_handler :drink, :source_name => :stomach, :event_type => :thirsty
12
+
13
+ assert_equal [], @head.handlers_for( :hungry, :stomach)
14
+ assert_equal [], @head.all_handlers_for(:hungry, :stomach)
15
+ end
16
+
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
22
+
23
+ assert_equal [:sip], @head.handlers_for(:dry, :mouth)
24
+ assert_equal [:sip], @head.all_handlers_for(:dry, :mouth)
25
+
26
+ assert_equal [:drink, :eat], @head.handlers_for(:hungry, :stomach)
27
+ assert_equal [:drink, :eat, :have_desert], @head.all_handlers_for(:hungry, :stomach)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,28 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class EventTest < Test::Unit::TestCase
4
+ context "An event" do
5
+ should "accept its type and source in the constructor" do
6
+ event = Onfire::Event.new(:click, :source)
7
+
8
+ assert_equal :click, event.type
9
+ assert_equal :source, event.source
10
+ end
11
+
12
+ should "be fine without any parameters at all" do
13
+ event = Onfire::Event.new
14
+
15
+ assert_nil event.type
16
+ assert_nil event.source
17
+ assert_nil event.data
18
+ end
19
+
20
+ should "stop if needed" do
21
+ event = Onfire::Event.new
22
+
23
+ assert ! event.stopped?
24
+ event.stop!
25
+ assert event.stopped?
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,283 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
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
+ context "including Onfire" do
20
+ should "provide event_table accessors to an emtpy table" do
21
+ table = mock.event_table
22
+ assert_kind_of Onfire::EventTable, table
23
+ assert_equal 0, table.size
24
+ end
25
+ end
26
+
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
+
59
+ context "with default handler" do
60
+ setup {
61
+ @obj = mock
62
+ @obj.set_default_event_handler do |event|
63
+ @default_event_handler_called = true
64
+ end
65
+ }
66
+ context 'with no handler for a given event' do
67
+ setup { @obj.process_event(@event) }
68
+ should('call the default handler') { assert @default_event_handler_called }
69
+ end
70
+
71
+ context 'with handler for a given event' do
72
+ setup {
73
+ @obj.event_table.add_handler(lambda { |evt| @specific_event_handler_called = true }, :event_type => :click)
74
+ @obj.process_event(@event)
75
+ }
76
+
77
+ should('call that specific handler') { assert @specific_event_handler_called }
78
+ should('not call the default handler') { assert ! @default_event_handler_called }
79
+ end
80
+ end
81
+ end
82
+
83
+ context "calling #on" do
84
+ setup do
85
+ @obj = mock
86
+ @event = Onfire::Event.new(:click, @obj)
87
+ end
88
+
89
+ context "with a block" do
90
+ should "add a handler to the event_table when called with a block" do
91
+ @obj.on :click do
92
+ @obj.list << 1
93
+ end
94
+
95
+ @obj.process_event(@event)
96
+ assert_equal [1], @obj.list
97
+ end
98
+
99
+ should "invoke two handlers if called twice" do
100
+ @obj.on :click do @obj.list << 1 end
101
+ @obj.on :click do @obj.list << 2 end
102
+
103
+ @obj.process_event(@event)
104
+ assert_equal [1,2], @obj.list
105
+ end
106
+
107
+ should "receive the triggering event as parameter" do
108
+ @obj.on :click do |evt|
109
+ @obj.list << evt
110
+ end
111
+
112
+ @obj.process_event(@event)
113
+ assert_equal [@event], @obj.list
114
+ end
115
+ end
116
+ end
117
+
118
+ context "In the bar" do
119
+ setup do
120
+ @barkeeper = mock('barkeeper')
121
+ @nice_guest = mock('nice guest')
122
+ @bad_guest = mock('bad guest')
123
+
124
+ @nice_guest.parent = @barkeeper
125
+ @bad_guest.parent = @barkeeper
126
+ end
127
+
128
+ context "the #on method" do
129
+ context "with the :from option for filtering" do
130
+ setup do
131
+ @barkeeper.on(:order, :from => @nice_guest) {@barkeeper.list << 'be nice'}
132
+ @barkeeper.on(:order, :from => @bad_guest) {@barkeeper.list << 'ignore'}
133
+ @barkeeper.on(:order, :from => @bad_guest) {@barkeeper.list << 'throw out'}
134
+ end
135
+
136
+ should "invoke the handler for the nice guest only" do
137
+ @nice_guest.fire :order
138
+ assert_equal ['be nice'], @barkeeper.list
139
+ end
140
+
141
+ should "invoke both handlers for the bad guest only" do
142
+ @bad_guest.fire :order
143
+ assert_equal ['ignore', 'throw out'], @barkeeper.list
144
+ end
145
+
146
+ should "invoke an additional catch-all handler" do
147
+ @barkeeper.on(:order) {@barkeeper.list << 'have a drink yourself'}
148
+ @nice_guest.fire :order
149
+ assert_equal ['be nice', 'have a drink yourself'], @barkeeper.list
150
+ end
151
+
152
+ should "invoke another handler when :from is nil" do
153
+ @barkeeper.on(:order, :from => nil) {@barkeeper.list << 'have a drink yourself'}
154
+ @nice_guest.fire :order
155
+ assert_equal ['be nice', 'have a drink yourself'], @barkeeper.list
156
+ end
157
+
158
+ should "invoke :from handlers before it processes catch-all handlers" do
159
+ @barkeeper.on(:order) {@barkeeper.list << 'have a drink yourself'}
160
+ @barkeeper.on(:order, :from => @nice_guest) {@barkeeper.list << 'bring out toast'}
161
+ @nice_guest.fire :order
162
+ assert_equal ['be nice', 'bring out toast', 'have a drink yourself'], @barkeeper.list
163
+ end
164
+ end
165
+
166
+ context "with a callable object" do
167
+ setup do
168
+ @callable = Class.new.new
169
+ @callable.instance_eval do
170
+ def call(event)
171
+ source = event.source
172
+ return source.list << 'order from barkeeper' if source.root?
173
+ source.parent.list << 'order from guest'
174
+ end
175
+ end
176
+ end
177
+
178
+ should "add a handler to the local event_table" do
179
+ @barkeeper.on :order, :do => @callable
180
+
181
+ @barkeeper.fire :order
182
+ assert_equal ['order from barkeeper'], @barkeeper.list
183
+
184
+ @nice_guest.fire :order
185
+ assert_equal ['order from barkeeper', 'order from guest'], @barkeeper.list
186
+ end
187
+ end
188
+ end
189
+
190
+
191
+ context "stopping events" do
192
+ should "not invoke any handler after the guest kills it" do
193
+ @nice_guest.on(:order) {@nice_guest.list << 'thirsty?'}
194
+ @nice_guest.on(:order) do |evt|
195
+ @nice_guest.list << 'money?'
196
+ evt.stop!
197
+ end
198
+ @barkeeper.on(:order) {@nice_guest.list << 'draw a beer'}
199
+ @nice_guest.fire :order
200
+
201
+ assert_equal ['thirsty?', 'money?'], @nice_guest.list
202
+ end
203
+ end
204
+
205
+
206
+ context "#event_table" do
207
+ should "expose the EventTable to the public" do
208
+ assert_kind_of ::Onfire::EventTable, @barkeeper.event_table
209
+ end
210
+ end
211
+
212
+ end
213
+
214
+ context "calling #fire" do
215
+ setup do
216
+ @obj = mock
217
+ end
218
+
219
+ should "be of no relevance when there are no handlers attached" do
220
+ @obj.fire :click
221
+
222
+ assert_equal [], @obj.list
223
+ end
224
+
225
+ should "invoke the attached matching handler" do
226
+ @obj.on :click do @obj.list << 1 end
227
+ @obj.fire :click
228
+
229
+ assert_equal [1], @obj.list
230
+ end
231
+
232
+ should "not invoke same handlers for :symbol or 'string' event names" do
233
+ @obj.on :click do @obj.list << 1 end
234
+ @obj.fire 'click'
235
+
236
+ assert_equal [], @obj.list
237
+ end
238
+
239
+
240
+ should "invoke handlers in the correct order when bubbling" do
241
+ # we use @obj for recording the chat.
242
+ bar = mock('bar')
243
+ guest = mock('guest')
244
+
245
+ guest.parent = bar
246
+ guest.on :thirsty do
247
+ @obj.list << 'A beer!'
248
+ guest.fire :order
249
+ end
250
+ guest.on :thirsty do
251
+ @obj.list << 'Hurry up, man!'
252
+ end
253
+ bar.on :thirsty do
254
+ @obj.list << 'Thanks.'
255
+ end
256
+ bar.on :order do
257
+ @obj.list << 'There you go.'
258
+ end
259
+
260
+ guest.fire :thirsty
261
+
262
+ assert_equal ['A beer!', 'There you go.', 'Hurry up, man!', 'Thanks.'], @obj.list
263
+ end
264
+
265
+ end
266
+
267
+
268
+ context "#root?" do
269
+ setup do
270
+ @obj = mock
271
+ end
272
+
273
+ should "return false if we got parents" do
274
+ @obj.parent = :daddy
275
+ assert !@obj.root?
276
+ end
277
+
278
+ should "return true if we're at the top" do
279
+ assert @obj.root?
280
+ end
281
+ end
282
+
283
+ end
@@ -0,0 +1,11 @@
1
+ require 'rubygems'
2
+ require 'shoulda'
3
+ require 'onfire'
4
+
5
+ class Onfire::EventTable
6
+ def size
7
+ source2evt.inject(0)do |memo, evts|
8
+ memo + evts[1].inject(0) {|sum, h| sum + h[1].size} # h => [key, value].
9
+ end || 0
10
+ end
11
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nilclass-onfire
3
+ version: !ruby/object:Gem::Version
4
+ hash: 7441426
5
+ prerelease: true
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 1
10
+ - nc
11
+ version: 0.1.1.nc
12
+ platform: ruby
13
+ authors:
14
+ - Nick Sutterer
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2010-06-13 00:00:00 +02:00
20
+ default_executable:
21
+ dependencies: []
22
+
23
+ description: Have bubbling events and observers in all your Ruby objects.
24
+ email: apotonick@gmail.com
25
+ executables: []
26
+
27
+ extensions: []
28
+
29
+ extra_rdoc_files:
30
+ - README.textile
31
+ files:
32
+ - README.textile
33
+ - Rakefile
34
+ - lib/onfire.rb
35
+ - lib/onfire/event.rb
36
+ - lib/onfire/event_table.rb
37
+ - lib/onfire/version.rb
38
+ - test/event_table_test.rb
39
+ - test/event_test.rb
40
+ - test/onfire_test.rb
41
+ - test/test_helper.rb
42
+ has_rdoc: true
43
+ homepage: http://github.com/apotonick/onfire
44
+ licenses: []
45
+
46
+ post_install_message:
47
+ rdoc_options:
48
+ - --charset=UTF-8
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ hash: 3
57
+ segments:
58
+ - 0
59
+ version: "0"
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ">"
64
+ - !ruby/object:Gem::Version
65
+ hash: 25
66
+ segments:
67
+ - 1
68
+ - 3
69
+ - 1
70
+ version: 1.3.1
71
+ requirements: []
72
+
73
+ rubyforge_project:
74
+ rubygems_version: 1.3.7
75
+ signing_key:
76
+ specification_version: 3
77
+ summary: Have bubbling events and observers in all your Ruby objects.
78
+ test_files:
79
+ - test/test_helper.rb
80
+ - test/onfire_test.rb
81
+ - test/event_table_test.rb
82
+ - test/event_test.rb