State change observer for ActiveRecord in Rails
Create a model like this. Here, the attribute mode keeps track of the state.
class CreateCarTransmission < ActiveRecord::Migration def self.up create_table :car_transmission do |t| t.column :engine_id, :integer, :null => false t.column :mode, :string, :null => false, :default => "park" end end def self.down drop_table :car_transmission end end
In the model, you include the state transition observable class, and call state_observable.
class CarTransmission < ActiveRecord::Base include StateTransition::Observable state_observable CarTransmissionNotifier, :state_name => :mode end
Then you define a notifier class that has different callbacks based on the transitions. It follows the pattern, "#{state_name}_from_#{state you're coming from}_to_#{state you're going to}"
class CarTransmissionNotifier < StateTransition::Observer def mode_from_drive_to_reverse(transmission) # send out mail and flash lights about how this is bad. end end
So whenever you change the state from "drive" to "reverse" of a car transmission model in the controller, the notifier will fire up.
To use it, put the following code in a file called 'state_transition.rb', put it in your lib/ directory in rails.
require 'observer' module StateTransition module Observable class StateNameNotFoundError < RuntimeError def message "option :state_name needs to be set to the name of an attribute" end end def self.included(mod) mod.extend(ClassMethods) end module ClassMethods def state_observable(observer_class, options) raise StateNameNotFoundError.new if options[:state_name].nil? state_name = options[:state_name].to_s include Object::Observable define_method(:after_initialize) do add_observer(observer_class.new) end define_method("#{state_name}=") do |new_state| old_state = read_attribute(state_name) if old_state != new_state write_attribute(state_name, new_state) # TODO yield the update method changed notify_observers(self, state_name, old_state, new_state) end end end end end class Observer def update(observable, state_name, old_state, new_state) send("#{state_name}_from_#{old_state}_to_#{new_state}", observable) rescue NoMethodError => e # ignore any methods not found here end end end
more details here