Event Management in Rails: Observers

All Rails developers use ActiveRecord callbacks. The use of these callbacks helps our models: validation, data manipulation, relational operations, automatic sending of emails…

When you start developing with Rails, all these operations I mentioned are normally done in the controller. However, as we begin to lighten our controllers we fatten up our models with more and more callbacks. This is a good thing but at times we end up with a load of callbacks in the model that have nothing to do with the model.

To extract all this disparate code which has nothing directly to do with the model, Rails gives us observers.

Inside Observers

In this post, we’ll see how Rails observers work. For this it’s necessary to know how they are used so, if you’ve never used them, you should take a look at the documentation :)

The Ruby Observable module

Ruby implements some common design patterns in its standard library. One such module is Observable.

The Observable module implements the Observer pattern, also known as Publish-Subscribe. In this pattern, an object (the publisher or source) informs a group of interested objects (the subscribers) when it’s state changes. To accomplish this, we are given a series of methods to register subscribers and to notify them of state changes.

Let’s see an example of the use of the Observable module:

require 'observer'

class SystemMonitor
  include Observable

  def run
    last_free_space = nil
    loop do
      free_space = Disk.free_space
      puts "Free disk space: #{free_space}MB"
      if free_space != last_free_space
        changed
        notify_observers(free_space)
        last_free_space = free_space
      end
      sleep(60)
    end
  end
end

class DiskMainteinance
  def initialize(limit)
    @limit = limit
  end

  def update(free_space)
    if free_space < @limit
      puts "-- Cleaning temp files. #{free_space}MB free"
    end
  end
end

class AlertLowSpace
  def initialize(limit, email)
    @limit = limit
    @email = email
  end

  def update(free_space)
    if free_space < @limit
      puts "-- Notifying to #{@email}. #{free_space}MB free"
    end
  end
end

monitor = SystemMonitor.new
monitor.add_observer(DiskMainteinance.new(700))
monitor.add_observer(AlertLowSpace.new(700, 'peter@example.com'))
monitor.add_observer(AlertLowSpace.new(600, 'john@example.com'))
monitor.run

I think the example is pretty easy to follow, but it’s best if I clarify some details :)

The Observable module defines a series of methods that you can look up in the module documentation. In our example we use three of these:

  • changed(state=true): the state of the object has changed.
  • notify_observers(*args): i f the state is true, invoke the update method of each subscriber with the same arguments.
  • add_observer(observer): register a new subscriber.

Lets see an execution example of the previous script:

Free disk space: 1011MB
Free disk space: 821MB
Free disk space: 880MB
Free disk space: 625MB
-- Cleaning temp files. 625MB free
-- Notifying to peter@example.com. 625MB free
Free disk space: 730MB
Free disk space: 570MB
-- Cleaning temp files. 570MB free
-- Notifying to peter@example.com. 570MB free
-- Notifying to john@example.com. 570MB free
Free disk space: 716MB
Free disk space: 841MB
Free disk space: 1016MB
ActiveRecord: Publisher and Subscriber

ActiveRecord has two classes involved in event management:

  • ActiveRecord::Base: includes the Observable module and notifies the subscribers of al events to which callbacks can be associated
  • ActiveRecord::Observer: the base class for all observers and defines the update method

When a model (i.e. a subclass of ActiveRecord::Base) changes its state it invokes, in addition to the callbacks in the class itself, the method notify_observers with the following arguments:

  • The name of the event: before_validation, after_save, after_destroy…
  • The ActiveRecord object

    As we’ve seen, upon invoking notify_observers in the model, update is invoked on all the observers subscribed to the model. In the rails observers, the update method will invoke – if it is defined – the method of the class that has the name of the event notified and will pass as argument the object that has changed its state.

    # Send observed_method(object) if the method exists.
    def update(observed_method, object) #:nodoc:
      send(observed_method, object) if respond_to?(observed_method)
    end
    Initialisation of observers in Rails

    Now we’ve seen how ActiveRecord::Base notifies observers of events. Nevertheless, we’ve not seen how observers subscribe (as I’m sure you know, via a call to the method add_observer in the module Observable).

    When the rails observers call add_observer the constructor of ActiveRecord::Observer is run:

    # Start observing the declared classes and their subclasses.
    def initialize
      Set.new(observed_classes + observed_subclasses).each { |klass| add_observer! klass }
    end

    However, as the documentation states, to use observers, we don’t instantiate objects but instead we configure them in the environment.rb file (config.activerecord.observers_). This is to delegate to Rails the responsibility for initialising the observers.

    We’re not going to look at when they’re initialised, but instead how.

    The ActiveRecord::Observer class follows the Singleton design pattern. This pattern guarantees that there will be at the most one instance of a class. To achieve this, the visibility of the constructor is changed to private and a method is defined which returns the single instance of the class.

    This pattern is also included in the standard Ruby library in the Singleton module. This module changes the visibility of the constructor and defines the instance method.

    Therefore, Rails uses the instance method to subscribe the observers to the corresponding models.

    Knowing this…

    Knowing these details about the implementation of the Publish-Subscribe pattern in ActiveRecord and their initialisation in Rails, we have the technical knowledge to develop our own observers.
    We found it necessary to develop our own observers in order to have a single class that subscribes to various models but attending to different events depending on the model.

    In the next post, we’ll make the most of what we’ve explained here to look in detail at our observers.

    Happy Hacking! :)

    By Ernesto Jiménez
    Saved in: Programming | 1 comment » | 3 March 2008

    One comment in “Event Management in Rails: Observers”

    Gravatar de Nehal Kumar

    Nehal Kumar
    29 June 2011 at 10:26 am    

    I need to have a timeline and presentation. The Presentation table should observe the timeline table and and update the slides accordingly. I have a lot of doubts about observers and models. Can I observe another model with the observer. I will have a timeline in my project which will run and send out events every second. The slides should identify themselves with this and update accordingly. Please help and suggest a good way to do this.

    More posts in Negonation Blog