DEV Community

Cover image for Design Patterns in Ruby #2: Observer Pattern
Shrouk Abozeid
Shrouk Abozeid

Posted on

Design Patterns in Ruby #2: Observer Pattern

Applications often need to react when something changes.

When a user signs up, you might want to:

  • Send a welcome email
  • Create a user profile
  • Log the activity
  • Notify an analytics service

One approach is to put all of this logic inside the signup code. While it works initially, the code quickly becomes difficult to maintain as more responsibilities are added.

This is exactly the kind of problem the Observer Pattern solves.

The Observer Pattern is one of the most commonly used behavioral design patterns.

It defines a one-to-many relationship between objects so that when one object (the Subject) changes state, all of its dependent objects (the Observers) are notified automatically.

Instead of tightly coupling objects together, the subject simply announces that "something happened," and any interested observers can react.

How It Works

The pattern consists of two main participants.

Subject (Publisher)

The subject owns the state and keeps track of all registered observers.

Whenever its state changes, it notifies every observer.

Observer (Subscriber)

Observers register themselves with the subject.

When they receive a notification, they perform whatever action they're responsible for.

A classic real-world example is a UI button.

The button is the Subject, while event listeners are the Observers. When the button is clicked, every registered listener is notified.


Why Use the Observer Pattern?

Without the Observer Pattern, your code often grows into something like this:

class User
  def signup
    save

    WelcomeMailer.send_email(self)
    Analytics.track_signup(self)
    ActivityLogger.log_signup(self)
    UserProfile.create(self)
  end
end
Enter fullscreen mode Exit fullscreen mode

Every new action requires modifying the signup method.

This makes the class responsible for many unrelated tasks and violates the Single Responsibility Principle.

With the Observer Pattern, the signup process only needs to announce that a user has signed up. Each observer decides how to respond.


Loose Coupling

One of the biggest benefits of the Observer Pattern is loose coupling.

The subject only knows that its observers respond to a common interface (for example, an update method).

This means:

  • New observers can be added without changing the subject.
  • Observers can be removed at runtime.
  • The subject doesn't know or care what each observer does.
  • Subjects and observers can evolve independently.
  • Each observer has a single, focused responsibility.

Ruby Example

Let's build a simple weather station.

Whenever the temperature changes, every registered display should update automatically.

class WeatherStation
  def initialize
    @observers = []
  end

  def add_observer(observer)
    @observers << observer
  end

  def remove_observer(observer)
    @observers.delete(observer)
  end

  def temperature=(value)
    @temperature = value
    notify_observers
  end

  private

  def notify_observers
    @observers.each do |observer|
      observer.update(@temperature)
    end
  end
end

class PhoneDisplay
  def update(temperature)
    puts "Phone Display: #{temperature}°C"
  end
end

class DashboardDisplay
  def update(temperature)
    puts "Dashboard Display: #{temperature}°C"
  end
end

station = WeatherStation.new

station.add_observer(PhoneDisplay.new)
station.add_observer(DashboardDisplay.new)

station.temperature = 25
Enter fullscreen mode Exit fullscreen mode

Output:

Phone Display: 25°C
Dashboard Display: 25°C
Enter fullscreen mode Exit fullscreen mode

When the temperature changes, the weather station automatically notifies every registered display.

Notice that WeatherStation doesn't know anything about phones or dashboards. It only knows that each observer responds to update.


Observer Pattern in Rails

Even if you've never implemented the Observer Pattern yourself, you've probably used it in Rails.

Some common examples include:

  • Active Record callbacks (after_create, after_commit, before_save)
  • ActiveSupport::Notifications
  • Action Cable broadcasting events to connected clients

For example:

class User < ApplicationRecord
  after_create :send_welcome_email

  private

  def send_welcome_email
    WelcomeMailer.welcome(self).deliver_later
  end
end
Enter fullscreen mode Exit fullscreen mode

When a User is created, Rails automatically invokes the callback.

Although callbacks aren't a textbook implementation of the Observer Pattern, they follow the same fundamental idea: responding automatically when an object's state changes.


When Should You Use the Observer Pattern?

The Observer Pattern is a good choice when:

  • Multiple objects need to react to the same event.
  • You want to avoid tightly coupling classes together.
  • New behaviors should be be added without modifying existing code.
  • Different parts of the application should respond independently to state changes.
  • You want to follow the Open/Closed Principle and the Single Responsibility Principle.

Final Thoughts

The Observer Pattern is all about communication without tight coupling.

Instead of asking every object to perform additional work, the subject simply announces that something has changed.

Any interested observer can react in its own way.

In Ruby, where objects communicate through simple interfaces and duck typing, the Observer Pattern is straightforward to implement and can significantly improve the flexibility and maintainability of your applications.

As your applications grow, you'll find this pattern everywhere—from GUI frameworks to Rails callbacks to event-driven architectures.

for a full example in ruby check this https://github.com/ShroukAbozeid/design-pattern/tree/main/observer

Top comments (0)