State change observer for ActiveRecord in Rails
Create a model like this. Here, the attribute mode keeps track of the state.
1 2 class CreateCarTransmission < ActiveRecord::Migration 3 def self.up 4 create_table :car_transmission do |t| 5 t.column :engine_id, :integer, :null => false 6 t.column :mode, :string, :null => false, :default => "park" 7 end 8 end 9 10 def self.down 11 drop_table :car_transmission 12 end 13 end
In the model, you include the state transition observable class, and call state_observable.
1 2 class CarTransmission < ActiveRecord::Base 3 include StateTransition::Observable 4 state_observable CarTransmissionNotifier, :state_name => :mode 5 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}"
1 2 class CarTransmissionNotifier < StateTransition::Observer 3 def mode_from_drive_to_reverse(transmission) 4 # send out mail and flash lights about how this is bad. 5 end 6 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.
1 2 require 'observer' 3 4 module StateTransition 5 module Observable 6 class StateNameNotFoundError < RuntimeError 7 def message 8 "option :state_name needs to be set to the name of an attribute" 9 end 10 end 11 12 def self.included(mod) 13 mod.extend(ClassMethods) 14 end 15 16 module ClassMethods 17 18 def state_observable(observer_class, options) 19 raise StateNameNotFoundError.new if options[:state_name].nil? 20 state_name = options[:state_name].to_s 21 22 include Object::Observable 23 24 define_method(:after_initialize) do 25 add_observer(observer_class.new) 26 end 27 28 define_method("#{state_name}=") do |new_state| 29 old_state = read_attribute(state_name) 30 if old_state != new_state 31 write_attribute(state_name, new_state) # TODO yield the update method 32 changed 33 notify_observers(self, state_name, old_state, new_state) 34 end 35 end 36 end 37 38 end 39 40 end 41 42 class Observer 43 def update(observable, state_name, old_state, new_state) 44 send("#{state_name}_from_#{old_state}_to_#{new_state}", observable) 45 rescue NoMethodError => e 46 # ignore any methods not found here 47 end 48 end 49 50 end
more details here