Teach models to stalk each other with Stalker

I often come by a case where models need to talk to each other. ModelA sometimes needs to ask ModelB to do something after_save. Usually you’d just use a callback.

class ModelA < ActiveRecord::Base
  after_save :update_model_b

  def update_model_b
    ModelB.update!
  end
end

This is obvious coupling. ModelA needs to know what to call in ModelB. This isn’t ModelA’s concern. Ideally, ModelA should simply notify ModelB that it saved.

class ModelA < ActiveRecord::Base
  after_save :notify_model_b

  def notify_model_b
    ModelB.model_a_saved!(self)
  end
end

class ModelB < ActiveRecord::Base
  class << self
    def model_a_saved!(modela)
      self.do_something_about(modela)
    end
  end
end

This is a better way to do it since now ModelB is responsible for performing whatever it requires. Except, this is too verbose. Imagine you had to notify ModelC as well, it’d get ridiculous.

class ModelA < ActiveRecord::Base
  after_save :notify_model_b
  after_save :notify_model_c

  def notify_model_b
    ModelB.model_a_saved!(self)
  end

  def notify_model_c
    ModelC.model_a_saved!(self)
  end
end

This is too much work for each such notification. Instead, you may notice that this can be solved by observer pattern. Rails already provides Observers, but they are completely separate classes, and don’t save us any work in this case, since they can’t be part of an existing model. This is where Stalker comes in. Stalker allows you to have one model simply observe the other.

class ModelA < ActiveRecord::Base
  # nothing to do here
end

class ModelB < ActiveRecord::Base
  stalk ModelA

  class << self
    def after_model_a_save(modela)
      self.do_something_about(modela)
    end
  end
end

The above solves separation of concerns issue. ModelB is now fully responsible for watching ModelA, and can easily react to events by using callbacks such as after_[model_name]save or before[model_name]_create. In the parameter it will always automatically get the object which triggers the callback.

Just like with Rails observers, you have to ensure that the stalker models load before the stalked ones. You can use the same active_record config for that.

In your environment.rb have this line uncommented and make sure it includes the name of the stalker.

config.active_record.observers = [:model_b]

Under the hood.

Stalker is a micro-plugin with only 35 lines of code. It takes away most of the boilerplate work from setting up model-model observers. The stalked model receives a “notify” method for each possible callback, and this method calls the after_[my_name]_save on the stalker model, just like in the above example. The difference is that you don’t have to maintain ModelA at all, stalker will do it automatically. In addition, since you get the same code as in the above example, you’ll also be able to interrupt the callback chain by returning false from the stalker model.

Install

% script/plugin install git://github.com/maxim/stalker.git

Leave a Reply