onfire 0.1.0

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 askes 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 = "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.0'.freeze
5
+ end
data/lib/onfire.rb ADDED
@@ -0,0 +1,52 @@
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
+ 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
37
+ end
38
+
39
+ def event_table
40
+ @event_table ||= Onfire::EventTable.new
41
+ end
42
+
43
+ protected
44
+ def attach_event_handler(proc, table_options)
45
+ event_table.add_handler(proc, table_options)
46
+ end
47
+
48
+ # Get all handlers from self for the passed event.
49
+ def local_event_handlers(event)
50
+ event_table.all_handlers_for(event.type, event.source)
51
+ end
52
+ 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,260 @@
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 { 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{obj.list << 1}, :event_type => :click)
52
+ obj.event_table.add_handler(lambda{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
+ context "In the bar" do
96
+ setup do
97
+ @barkeeper = mock('barkeeper')
98
+ @nice_guest = mock('nice guest')
99
+ @bad_guest = mock('bad guest')
100
+
101
+ @nice_guest.parent = @barkeeper
102
+ @bad_guest.parent = @barkeeper
103
+ end
104
+
105
+ context "the #on method" do
106
+ context "with the :from option for filtering" do
107
+ setup do
108
+ @barkeeper.on(:order, :from => @nice_guest) {@barkeeper.list << 'be nice'}
109
+ @barkeeper.on(:order, :from => @bad_guest) {@barkeeper.list << 'ignore'}
110
+ @barkeeper.on(:order, :from => @bad_guest) {@barkeeper.list << 'throw out'}
111
+ end
112
+
113
+ should "invoke the handler for the nice guest only" do
114
+ @nice_guest.fire :order
115
+ assert_equal ['be nice'], @barkeeper.list
116
+ end
117
+
118
+ should "invoke both handlers for the bad guest only" do
119
+ @bad_guest.fire :order
120
+ assert_equal ['ignore', 'throw out'], @barkeeper.list
121
+ end
122
+
123
+ should "invoke an additional catch-all handler" do
124
+ @barkeeper.on(:order) {@barkeeper.list << 'have a drink yourself'}
125
+ @nice_guest.fire :order
126
+ assert_equal ['be nice', 'have a drink yourself'], @barkeeper.list
127
+ end
128
+
129
+ should "invoke another handler when :from is nil" do
130
+ @barkeeper.on(:order, :from => nil) {@barkeeper.list << 'have a drink yourself'}
131
+ @nice_guest.fire :order
132
+ assert_equal ['be nice', 'have a drink yourself'], @barkeeper.list
133
+ end
134
+
135
+ should "invoke :from handlers before it processes catch-all handlers" do
136
+ @barkeeper.on(:order) {@barkeeper.list << 'have a drink yourself'}
137
+ @barkeeper.on(:order, :from => @nice_guest) {@barkeeper.list << 'bring out toast'}
138
+ @nice_guest.fire :order
139
+ assert_equal ['be nice', 'bring out toast', 'have a drink yourself'], @barkeeper.list
140
+ end
141
+ end
142
+
143
+ context "with a callable object" do
144
+ setup do
145
+ @callable = Class.new.new
146
+ @callable.instance_eval do
147
+ def call(event)
148
+ source = event.source
149
+ return source.list << 'order from barkeeper' if source.root?
150
+ source.parent.list << 'order from guest'
151
+ end
152
+ end
153
+ end
154
+
155
+ should "add a handler to the local event_table" do
156
+ @barkeeper.on :order, :do => @callable
157
+
158
+ @barkeeper.fire :order
159
+ assert_equal ['order from barkeeper'], @barkeeper.list
160
+
161
+ @nice_guest.fire :order
162
+ assert_equal ['order from barkeeper', 'order from guest'], @barkeeper.list
163
+ end
164
+ end
165
+ end
166
+
167
+
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
+ context "#event_table" do
184
+ should "expose the EventTable to the public" do
185
+ assert_kind_of ::Onfire::EventTable, @barkeeper.event_table
186
+ end
187
+ end
188
+
189
+ end
190
+
191
+ context "calling #fire" do
192
+ setup do
193
+ @obj = mock
194
+ end
195
+
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
+ should "invoke the attached matching handler" do
203
+ @obj.on :click do @obj.list << 1 end
204
+ @obj.fire :click
205
+
206
+ assert_equal [1], @obj.list
207
+ end
208
+
209
+ should "not invoke same handlers for :symbol or 'string' event names" do
210
+ @obj.on :click do @obj.list << 1 end
211
+ @obj.fire 'click'
212
+
213
+ assert_equal [], @obj.list
214
+ end
215
+
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
+ end
243
+
244
+
245
+ context "#root?" do
246
+ setup do
247
+ @obj = mock
248
+ end
249
+
250
+ should "return false if we got parents" do
251
+ @obj.parent = :daddy
252
+ assert !@obj.root?
253
+ end
254
+
255
+ should "return true if we're at the top" do
256
+ assert @obj.root?
257
+ end
258
+ end
259
+
260
+ 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,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: onfire
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Nick Sutterer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-03-19 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Have bubbling events and observers in all your Ruby objects.
17
+ email: apotonick@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.textile
24
+ files:
25
+ - README.textile
26
+ - Rakefile
27
+ - lib/onfire.rb
28
+ - lib/onfire/event.rb
29
+ - lib/onfire/event_table.rb
30
+ - lib/onfire/version.rb
31
+ - test/event_table_test.rb
32
+ - test/event_test.rb
33
+ - test/onfire_test.rb
34
+ - test/test_helper.rb
35
+ has_rdoc: true
36
+ homepage: http://github.com/apotonick/onfire
37
+ licenses: []
38
+
39
+ post_install_message:
40
+ rdoc_options:
41
+ - --charset=UTF-8
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: "0"
49
+ version:
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ requirements: []
57
+
58
+ rubyforge_project:
59
+ rubygems_version: 1.3.5
60
+ signing_key:
61
+ specification_version: 3
62
+ summary: Have bubbling events and observers in all your Ruby objects.
63
+ test_files:
64
+ - test/onfire_test.rb
65
+ - test/test_helper.rb
66
+ - test/event_table_test.rb
67
+ - test/event_test.rb