When using callbacks in your Ruby objects, there are more than a few ways of doing it. Recently I stumbled into Wisper gem that abstracts details away in a nice manner. Best do some code comparison.
Previously, I might be writing something like this:
class Worker def do_some_hard_work status = :should_be_sleeping_like_a_log notify_listeners(:on_work_done, status) end def add_listener(listener) (@listeners ||= ) << listener end def notify_listeners(event_name, *args) @listeners && @listeners.each do |listener| if listener.respond_to?(event_name) listener.public_send(event_name, self, *args) end end end end class Owner def set_things_in_motion worker = Worker.new worker.add_listener(WorkerDisplay.new) worker.do_some_hard_work end end class WorkerDisplay def on_work_done(worker, status) display_worker_status(worker, status) end end
Here you have a worker class that has the ability to register listeners and to trigger appropriate events on them, if they respond to the given event. Not too big of a footprint but it does sort of pollute the domain a bit. I guess things could be extracted to a module but let’s try it with Wisper now:
class Worker include Wisper def do_some_hard_work status = :should_be_sleeping_like_a_log publish(:on_work_done, self, status) end end class Owner def set_things_in_motion worker = Worker.new worker.subscribe(WorkerDisplay.new) worker.do_some_hard_work end end class WorkerDisplay def on_work_done(worker, status) display_worker_status(worker, status) end end
So, most of the code didn’t change, but the Worker class did benefit from a more clearly revealed intent. A small one, but still a win. Best thing is, you can use the async Wisper gem extension and turn your listener into a Celluloid Actor.
class Owner def set_things_in_motion ... worker.subscribe(WorkerDisplay.new, async: true) ... end end
There are other nice features like global listeners, mapping subscription to a different method or subscription chaining, so if you’re interested go ahead and read the project’s readme.