Tiny Pub/Sub In Ruby
Sep 13 2017I recently had a need to do a very simple in-memory notification mechanism in Ruby and came up with a tiny little class which seems to do exactly what I needed. It’s not nearly as fancy or comprehensive as something like Wisper, but you can’t beat the footprint of it.
A Simple Notifier
notifier.rb
class Notifier
attr_reader :events
def initialize
@events = {}
end
def subscribe(event, &handler)
@events[event] ||= []
@events[event] << handler
end
def broadcast(event, data=nil)
return if @events[event].nil?
@events[event].each do |handler|
handler.call(data)
end
end
end
Usage
So the usage is very straight forward as you might expect.
notifier = Notifier.new
# something that needs to be notified when a task is completed
notifier.subscribe("clone:succeeded") { |data| puts data }
# the thing doing the task
notifier.broadcast("clone:succeeded", "http://somesite.com")
# => http://somesite.com
Specs (you do write specs, don’t you?)
notifier_spec.rb
describe Notifier do
describe "#subscribe" do
it "adds the given handler to the specified event" do
notifier = Notifier.new
block = lambda {}
notifier.subscribe("foo", &block)
expect(notifier.events["foo"]).to include block
end
context "when called twice for the same event" do
it "adds the second handler to the same event" do
notifier = Notifier.new
block1 = lambda {}
block2 = lambda {}
notifier.subscribe("foo", &block1)
notifier.subscribe("foo", &block2)
expect(notifier.events["foo"]).to include block1
expect(notifier.events["foo"]).to include block2
end
end
end
describe "#broadcast" do
it "calls the handlers for the given event" do
notifier = Notifier.new
block = lambda { |data| puts data }
notifier.subscribe("foo", &block)
expect(block).to receive(:call).with("bar")
notifier.broadcast("foo", "bar")
end
it "does NOT call handlers for other events" do
notifier = Notifier.new
block1 = lambda { |data| puts data }
block2 = lambda { |data| puts data }
notifier.subscribe("foo1", &block1)
notifier.subscribe("foo2", &block1)
expect(block1).to receive(:call).with("bar")
expect(block2).not_to receive(:call).with("bar")
notifier.broadcast("foo1", "bar")
end
end
end
Singleton Version
I played around with a singleton version of this too, in cases where you didn’t want to pass around the instance of the Notifier
class. Thankfully, Ruby gives us a Singleton module we can easily include to make this happen.
require "singleton"
class Notifier
include Singleton
# ...
end
Then you just have to replace new
with instance
and you’re good to go.
notifier = Notifier.instance
# something that needs to be notified when a task is completed
notifier.subscribe("clone:succeeded") { |data| puts data }
# the thing doing the task
notifier.broadcast("clone:succeeded", "http://somesite.com")
# => http://somesite.com