Ruby on Rails Finite State Machine Plugin: acts_as_state_machine
A finite state machine is a model of behavior with a finite number of states, interconnected via transitions and events.
In this guide, I will introduce you to a Ruby on Rails plugin to easily recreate a Finite State Machine with your models. The plugin we will be using is the acts_as_state_machine, which as you can see has very sparse documentation. Google does pull up some results, but not good enough, at least for beginners.
Why would you use a Finite State Machine?
For starters, if your model has a finite number of various states, and you want an easy way for callbacks to be done. Callbacks can be used to notify, validate, increment, anything, when your model changes state.
Installing acts_as_state_machine
Go to the root folder of your Rails application and execute:
./script/plugin install \
http://elitists.textdriven.com/svn/plugins/acts_as_state_machine/trunk/
Warning: Split over two lines as it was really long.
Don’t forget if your project is under subversion, you can use the -x flag to externally link to the plugin
Using acts_as_state_machine
Note: The plugin makes an assumption that the state of your model is saved in field called state. This can be replaced by adding the additional option :column => 'field'.
Warning: If you are using a model that stores addresses, be weary of a field called “state”. You can spend hours wondering why things aren’t working like they should be.
Personal Preference: I like to describe the states my models are in with as an adjective, and the event as a verb.
Notice how in line 2 we explicitly state the initial state of the model. In lines 3 to 6, we indicate the various states the Person may be in.
There is a peculiar behavior when creating objects via new, in that the model’s state is not specified. It will only be specified when saving the new record. One solution is to specify the default state from within the migration. The other solution then is to call create.
Example:
Note: If you didn’t notice the trend, the method to test if the model is in the state, is to append the state with a question mark: "state?"
The events you specified also creates instance methods, to transition the model from one state to another.
The following instance methods were created:
Note: The instance methods created follow the pattern "event!"
Events
Note: By calling any event, you also call ActiveRecord::Base.save. For when it fails, it only returns false. You can guard yourself by calling valid? and save!
Events help you to transition from one state to another. So suppose your person is sleeping, and you want him to shower, well we’ll just call shower!.
Events can help your organize the flow of your model. But they can get more powerful with callbacks.
Callbacks
The state also comes with a few callbacks that can be used.
Callbacks are called when the model is transitioning into the specified state.
Note:
symbol or a Proc. If used as a symbol, the instance method of the model will be called
When put into consideration with ActiveRecord’s callbacks, a new record’s callback would look like this:
- ActiveRecord::Base.before_save
- ActiveRecord::Base.save
- acts_as_state_machine :enter sleeping
- acts_as_state_machine :after sleeping
- ActiveRecord::Base.after_save
When the model is no longer a new record, the callbacks execute as follows, if I had called the shower! method.
- acts_as_state_machine :enter showering
- ActiveRecord::Base.before_save
- ActiveRecord::Base.save
- ActiveRecord::Base.after_save
- acts_as_state_machine :after showering
- acts_as_state_machine :exit sleeping
Guarding States
But how about if you want some sort of validation for a transition. You know, just to ensure data integrity.
The transition can be guarded by specifying a :guard option, with either a symbol or Proc (similar to the Callbacks). The method or Proc has to return true to proceed with the transition, else it will fail silently.
Conclusion
Well thats the basics, and as much as acts_as_state_machine does.
Any tips, tricks and or pointers? Leave them as comments.
Or are there any other interesting plugins you’d like me to explore?


