The Observer Pattern in Ruby
The Observer Pattern in Ruby
The observer pattern is a design pattern where an object (the subject) maintains a list of dependent objects (observers) and notifies them automatically of any state changes. It’s one of the classic Gang of Four patterns and shows up constantly in Ruby code — often without you noticing.
In this guide you’ll learn how the pattern works, how Ruby’s standard library implements it, how to roll your own, and when to reach for alternatives like ActiveSupport::Notifications.
What Problem Does It Solve?
Imagine you have a Newsletter class that sends emails when new articles are published. Later you add analytics tracking. Then you add a search indexer. Each new feature adds more responsibility to the same class, and every caller site needs to know about all the downstream effects.
The observer pattern decouples this. The Newsletter only knows it has observers. It broadcasts events. It has no idea what those observers do.
# Without the observer pattern — Newsletter knows too much
class Newsletter
def initialize
@subscribers = []
end
def subscribe(email)
@subscribers << email
end
def publish(article)
# This class knows about every side effect
@subscribers.each { |email| send_email(email, article) }
Analytics.track_article_view(article)
SearchIndex.reindex(article)
# More integrations keep piling up...
end
end
The observer pattern breaks this dependency chain.
Ruby’s Built-in Observable Module
Ruby’s standard library ships with Observable out of the box. You get it by requiring 'observer' and mixing it into your subject class.
require 'observer'
class Article
include Observable
attr_reader :title, :body
def initialize(title, body)
@title = title
@body = body
end
def publish
changed # marks the subject as having changed
notify_observers(self) # passes self to all observers' update method
end
end
notify_observers calls the update method on every registered observer, passing the subject as an argument.
Define observers by implementing an update(subject) method:
class EmailSubscriber
def update(article)
puts "Sending email about: #{article.title}"
end
end
class SearchIndexer
def update(article)
puts "Indexing article: #{article.title}"
end
end
Wire them up with add_observer:
article = Article.new("Observer Pattern Guide", "Here's how it works...")
article.add_observer(EmailSubscriber.new)
article.add_observer(SearchIndexer.new)
article.publish
# => Sending email about: Observer Pattern Guide
# => Indexing article: Observer Pattern Guide
Controlling When to Notify
changed has a truthy guard — it only marks the object as changed if you pass it a truthy value:
changed(true) # marks as changed
changed(false) # marks as unchanged
changed # equivalent to changed(true)
notify_observers only fires if changed? returns true:
article = Article.new("Draft Post", "Still editing...")
article.add_observer(EmailSubscriber.new)
article.publish # No output — nothing changed since last notification
article.changed(true)
article.publish # Observer fires
notify_observers also accepts an optional argument passed directly to observers, bypassing the subject:
article.notify_observers("custom message") # observers receive "custom message" instead of article
Building a Custom Implementation
Ruby’s Observable is convenient, but understanding the mechanics makes you a better developer — and sometimes you need more control.
A minimal custom observer system needs just a few pieces:
module Subject
def initialize
@observers = []
end
def add_observer(observer)
@observers << observer
end
def remove_observer(observer)
@observers.delete(observer)
end
def notify_observers(event)
@observers.each { |observer| observer.update(event) }
end
end
Apply it by extending or including the module:
class Order
include Subject
attr_reader :status
def initialize
super()
@status = :pending
end
def complete
@status = :completed
notify_observers(self)
end
end
Observers just need an update method:
class OrderLogger
def update(order)
puts "Order #{order.object_id} is now #{order.status}"
end
end
class AccountingSystem
def update(order)
return unless order.status == :completed
puts "Billing customer for completed order"
end
end
order = Order.new
order.add_observer(OrderLogger.new)
order.add_observer(AccountingSystem.new)
order.complete
# => Order 1234567890 is now completed
# => Billing customer for completed order
A More Rubyesque Approach with Blocks
You can also build an observer system using Procs and lambdas, which is a common pattern in Ruby:
class EventEmitter
def initialize
@listeners = Hash.new { |h, k| h[k] = [] }
end
def on(event_name, &block)
@listeners[event_name] << block
end
def emit(event_name, *args)
@listeners[event_name].each { |block| block.call(*args) }
end
def off(event_name, block)
@listeners[event_name].delete(block)
end
end
emitter = EventEmitter.new
logger = ->(msg) { puts "LOG: #{msg}" }
emitter.on(:message, logger)
emitter.on(:message, ->(msg) { puts "ALERT: #{msg}" })
emitter.emit(:message, "Something happened")
# => LOG: Something happened
# => ALERT: Something happened
emitter.off(:message, logger)
emitter.emit(:message, "Only alert fires now")
# => ALERT: Only alert fires now
This pattern is the foundation of many Ruby event systems and is essentially the observer pattern with a more dynamic registration model.
Practical Examples
Model Observers (Plain Ruby)
A common use case is separating business logic from domain models. Rather than stuffing notification code into your model, you attach observers:
class User
include Observable
attr_accessor :email, :name
def initialize(email, name)
@email = email
@name = name
end
def update_profile(name)
@name = name
changed(true)
notify_observers(self)
end
end
class WelcomeMailer
def update(user)
puts "Sending welcome email to #{user.email}"
end
end
class ProfileAuditor
def update(user)
puts "Audit log: #{user.name} updated their profile"
end
end
user = User.new("alice@example.com", "Alice")
user.add_observer(WelcomeMailer.new)
user.add_observer(ProfileAuditor.new)
user.update_profile("Alice Smith")
# => Audit log: Alice Smith updated their profile
Multi-Event Notification Systems
A richer pattern supports named events rather than a single “something changed” notification:
class Blog
def initialize
@observers = Hash.new { |h, k| h[k] = [] }
end
def on(event, &callback)
@observers[event] << callback
end
def emit(event, *args)
@observers[event].each { |cb| cb.call(*args) }
end
end
blog = Blog.new
blog.on(:post_published) do |article, author|
puts "#{author} published: #{article}"
end
blog.on(:comment_added) do |article, commenter|
puts "#{commenter} commented on: #{article}"
end
blog.emit(:post_published, "Observer Pattern", "Alice")
# => Alice published: Observer Pattern
Observer Pattern vs Signals/Slots
Qt uses a signals and slots mechanism that’s conceptually similar to the observer pattern but with some important differences:
| Aspect | Observer Pattern | Signals/Slots |
|---|---|---|
| Coupling | Subject and observer loosely coupled | Very loose — no interface required |
| Type safety | Compile-time checking possible | Usually runtime-based |
| Flexibility | Fixed update signature | Named signals, varied slot signatures |
| Use in Ruby | Standard Ruby idioms | Qt Ruby bindings only |
In pure Ruby, you don’t have signals and slots — you have the observer pattern and its variants. The EventEmitter style shown above is the closest native-Ruby equivalent to signals/slots.
If you’re working in a Qt/Ruby application, prefer signals and slots. For everything else, the observer pattern is your tool.
ActiveSupport::Notifications
If you’re in a Rails environment, ActiveSupport::Notifications is the idiomatic way to implement observer-like pub/sub:
# Instrumenter publishes events
ActiveSupport::Notifications.instrument("article.published", article: @article) do
@article.save!
end
# Subscriber listens for events
ActiveSupport::Notifications.subscribe("article.published") do |event|
article = event.payload[:article]
puts "Article was published: #{article.title}"
end
Benefits Over the Standard Observer Pattern
- Asynchronous delivery — events can be processed in a background thread
- Multiple subscribers — unlimited observers per event
- Event history —
ActiveSupport::Notificationscan buffer and fan-out events - Namespacing — dot-notation events like
action_controller.process_actionorganize naturally
When to Use Each
| Scenario | Recommendation |
|---|---|
| Simple in-process notification | Ruby Observable or custom observer |
| Multiple named events | Custom EventEmitter-style class |
| Rails application | ActiveSupport::Notifications |
| Cross-process / service messaging | Message queues (SQS, RabbitMQ) |
Thread Safety Considerations
Ruby’s Observable is not thread-safe by default. If you’re working in a multi-threaded environment (e.g., a web server like Puma), you need to synchronize access:
require 'observer'
require 'thread'
class ThreadSafeArticle
include Observable
attr_reader :title
def initialize(title)
super()
@title = title
@lock = Mutex.new
end
def add_observer(observer)
@lock.synchronize do
super(observer)
end
end
def remove_observer(observer)
@lock.synchronize do
super(observer)
end
end
def notify_observers(arg = nil)
@lock.synchronize do
super(arg)
end
end
def publish
changed
notify_observers(self)
end
end
For Rails applications, ActiveSupport::Notifications handles thread safety internally. For custom implementations, always wrap observer registration and notification in mutex locks:
@mutex = Mutex.new
def safe_notify(event)
@mutex.synchronize { notify_observers(event) }
end
When using Queue to process observer notifications asynchronously, be aware that the subject may change state between when the event is queued and when it’s processed. Pass a snapshot of relevant data rather than a reference to the subject itself:
# Prefer — immutable snapshot
notify_observers({ title: @title, status: @status, timestamp: Time.now })
# Avoid — subject state may have changed by the time observer processes it
notify_observers(self)
See Also
- /guides/ruby-blocks-procs-lambdas/ — Understanding Ruby’s callable objects is foundational to implementing flexible observer callbacks
- /guides/ruby-concurrency-threads/ — Thread safety considerations for concurrent observer systems
- /guides/ruby-decorators-pattern/ — Another structural pattern for extending object behavior without modifying the class itself