h1. Onfire _Have bubbling events and observers in all your Ruby objects._ h2. Introduction 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. *Do you...?* * prefer *decoupled systems*, where observers don't wanna know the observed object (as @Observable#add_observer@ requires)? * rather intend to *observe _events_*, not business objects alone? * have a *tree-like* data structure? Bubbling events only make sense in a hierarchical environment where observers and event sources form a tree. * miss a *stop!* command which prevents the event from further propagation? h2. Example 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.
CEO: bill | | Managers: mike matz | | Developers: dave didiIf _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. 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_. h2. Installation
$ sudo gem install onfireh2. Usage First, you extend your @User@ class to be "on fire".
class User < ... include OnfireAs 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. *@#parent@* would return the boss object of the asked instance.
dave.parent # => matz matz.parent # => bill bill.parent # => nilThere's your hierarchical tree structure. h2. Fireing events Now _dave_ issues the bad circumstances in his office:
dave.fire :thatSucksSo far, nothing would happen as no one in the startup is observing that event. h2. Responding to events Anyway, a real CEO should respond to complainments from his subordinates.
bill.on :thatSucks do puts "who's that?" endNow _bill_ would at least find out somebody's crying.
> dave.fire :thatSucks => "who's that?" # by billThat's right, the *Onfire API* is just the two public methods * *@#on@* for responding to events and * *@#fire@* for triggering those h2. Bubbling events _matz_ being a good manager wants to mediate, so he takes part in the game:
matz.on :thatSucks do puts "dave, sshhht!" endWhich results in
> dave.fire :thatSucks => "dave, sshhht!" # by matz => "who's that?" # by billh2. Using the @Event@ object Of course _bill_ wants to find out who's the subversive element, so he just asks the revealing *Event* object.
bill.on :thatSucks do |event| event.source.fire! endThat's bad for _dave_, as he's unemployed now. h2. Intercepting events As _dave_ has always been on time, _matz_ just swallows any offending messages for now.
matz.on :thatSucks do |event| event.stop! endThat leads to an event that's stopped at _matz_. It won't propagate further up to the big boss.
> dave.fire :thatSucks => "dave, sshhht!" # first, by matz => nil # second, matz stops the event.h2. Organic event filtering What happens if _mike_ wants to be a good manager, too?
mike.on :thatSucks do puts "take it easy, dude!" endWhen _dave_ starts to cry, there's no _mike_ involved:
> dave.fire :thatSucks => "dave, sshhht!" # by matz => nilObviously, 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_. 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. h2. Event source filtering After a while discontent moves over to _didi_.
> didi.fire :thatSucks => "dave, sshhht!" # first, by matz => nil_didi_ is a lamer and _matz_ always prefered working with _dave_ so he changes his tune.
matz.on :thatSucks do |event| event.stop! if event.source == dave endThat's unfair!
> dave.fire :thatSucks => "dave, sshhht!" # by matz => nil # dave still got a job. > didi.fire :thatSucks => "fired!" # didi's event travels up to boss who fires him._matz_ is lazy, so he explicity lets Onfire handle the filtering:
matz.on :thatSucks, :from => dave do |event| event.stop! endwhich will result in the same bad outcome for _didi_. h2. Responding with instance methods Nevertheless _matz_ is trying to keep himself clean, so he refactors the handler block to an instance method.
matz.instance_eval do def shield_dave(event) event.stop! end end matz.on :thatSucks, :from => dave, :do => :shield_daveAwesome shit! h2. Who's using it? * 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. h2. License Copyright (c) 2010, Nick Sutterer The MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.