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?



adzdavies said,
June 10, 2007 @ 11:03 am
A few points:
# On calling of an event! method, acts_as_state_machine calls “update_attribute”. If you check the api docs, it calls ’save’ (not save! - so no exception raised), and furthermore BY-PASSES validations altogether.
The result?
You might want to guard with ‘valid?’ or call ’save!’ yourself in combination with the ‘event!’ method.
# As another consequence, your callback sequence is ever so slightly wrong:
* acts_as_state_machine :enter showering
* ActiveRecord::Base.before_save
* ActiveRecord::Base.save
* acts_as_state_machine :after showering
* ActiveRecord::Base.after_save
* acts_as_state_machine :exit sleeping
It will call after_save, _then_ the :after, then the :exit callbacks (at least in my version of acts_as_state_machine
However, you are 100% right with newly created records - because acts_as_state_machine uses ‘before_create’ callback.
BTW - Thanks for blogging - not enough info out there on this great little plugin!
Cheers,
Adam
aizatto said,
June 10, 2007 @ 3:02 pm
Thanks for the corrections Adam
much appreciated, wrote this up quite late, so the mistakes must have slipped through.
Its a wonderful plugin indeed
Finite State Machines in Rails at Matt Didcoe said,
June 10, 2007 @ 8:27 pm
[…] Ruby on Rails Finite State Machine Plugin: acts_as_state_machine >> rails symphonies: http://rails.aizatto.com/2007/05/24/ruby-on-rails-finite-state-machine-plugin-acts_as_state_machine/ Bookmark It: These icons link to social bookmarking sites where readers can share and discover […]
T said,
August 3, 2007 @ 9:49 pm
Thanks for the great tutorial! One question though: is there a way to pass an argument to an event callback? I have a model for which I want to log a history record every time it changes state. The problem is, I need to pass a user specified date from the controller to be part of the history record.
Bottom line, I need to do something like this:
state :sleeping,
:enter => :get_into_bed,
:after => Proc.new {|model| model.whack_alarm_clock }
def get_into_bed
BedtimeHistory.create(:date_of_sleep => input_date_from_user)
end
Is it possible to get the input_date_from_user into the get_into_bed function?
adz said,
September 3, 2007 @ 7:54 am
Hey T,
When you call a transition method like “sleep!” then all that is happening is your object’s state column is being updated and the record saved. The point is to simplify the logic of moving between states.
To get things from the controller into it, like ‘date_of_sleep’ just set that value first.
Example:
@object.date_of_sleep =input_date_from_user
@object.sleep!
Chetan Patil said,
December 1, 2007 @ 1:41 am
Hi,
Thanks for the writeup. It helped me get started with acts_as_state_machine.
I made some enhancements to the plugin which are described here (using your example):
http://justbarebones.blogspot.com/2007/11/actsasstatemachine-enhancements.html
- c
Frederico Araujo said,
December 3, 2007 @ 7:59 pm
Nice plugin,
I used to do finite state machine in Verilog for FPGAs.
but I never thought of it in ruby.
haha
how nice…
let me make some use of it.
Cheers
Noah said,
December 15, 2007 @ 3:58 pm
Thanks for this. I also extended this useful plugin a tiny bit.
What if you want to execute something after every state transition? (say, for logging the change) Currently, that would require something like
state :one, :after => :logit
state :two, :after => :logit
…
Intead I wrote some code so that you can set all these up at once in the initial call:
acts_as_state_machine :initial => :created, :column => ’status’, :after => :logit
The same pattern could be applied for other callbacks as well. Email me at noahd1 at yahoo dot com for the code (pretty simple) if interested.
Jason said,
January 3, 2008 @ 10:41 am
I think acts_as_state_machine is awesome and have already found it as a solution to a bunch of issues I have had already. But in one situation I have, one of my requirements for a state change is also a user supplied message so that the change can be logged. If the user doesn’t supply a message then I don’t want the state to change but display an error back to the users telling her why the state change didn’t occur. Is there any way to do this?
adz seems to have some insight into this but I’m confused about where to call save! and valid? as use suggest.
In my controller it would look like this:
def switch_to_happy
@person.log_message=params[:log_message] #messge to be logged
@person.switch_to_happy! #state change
end
This would result in a successful state change if params[:log_message] wasn’t empty but if it was then it would return to the form and give a validation error.
The transition events don’t return a @person object (valid or otherwise) so I’m not exactly sure how to do this.
Anyone have any thoughts?
Noah said,
January 3, 2008 @ 12:46 pm
Hi Jason -
You sent me an email, but thought I’d respond here as well.
I think the kind of logic you’re suggesting really belongs in a controller and not in the state machine. So your controller logic would look more like:
unless params[:log_message].blank?
@person.log_message= #messge to be logged
@person.switch_to_happy! #state change
else
# throw an error … return back to user
end
If that doesn’t suit you, I would look into using a guard on the transition:
transitions :from => :sad, :to => :happy, :guard => Proc.new {|o| o.valid? }
though, how to throw back an error without rechecking that the object is valid again, I’m not entirely sure.
Kris Nuttycombe said,
January 5, 2008 @ 6:44 am
It seems like it would be useful to be able to pass arguments to the events, which could subsequently be passed along into the guard methods (or blocks) of the transitions declared for those events.
Something like this:
# event :sleep do
# transitions :from => :showering, :to => :sleeping, guard => lambda {|o, becoming_sleepy| becoming_sleepy && o.has_bed}
# transitions :from => :working, :to => :sleeping
# transitions :from => :dating, :to => :sleeping
# end
This would give one the ability to do something like:
@person.sleep!(Time.now > bedtime)
The basic premise is that the object has some but not all of the information required to make the decision whether or not to perform the state transition.
Adam said,
January 8, 2008 @ 1:38 am
Hello,
I was wondering if there is a way to override the :initial state…or simply not have an initial. I have an :initial state of :pending in my Friendship model, however, i want to create friendship record with the state :accepted. I’ve tried Friendship.create(:state => “accepted”) and creating a new object using .new, setting the state, and calling save!, but when the record initially saves, :pending is set as the status.
Is there anyway to fix this so i do not have to save the record, refetch, change the state, and resave?
Sau Sheong said,
January 27, 2008 @ 1:41 pm
Hi Adam,
This plugin maps a finite state machine. I think you should draw out your state diagram first and see how you want to transit from one state to another. You sound as if you want to have multiple initial states. Things should be much clearer with a state diagram. Hope this helps.
Dave said,
January 29, 2008 @ 2:07 am
One potential gotcha is that invalid transitions return an empty array, rather than raise false or an exception.
For example:
state :fooish
state :barish
state :bazish
event :bar do
transitions :from => :fooish, :to => :bar
end
something.state = ‘baz’
something.bar! #=> []
A quick monkey patch would overload Event::fire(record):
def fire(record)
next_states = next_states(record) # line added
return false if next_states.empty? # line added
next_states.each do |transition| # line edited
break true if transition.perform(record)
end
end
something.state = ‘baz’
something.bar! #=> false
D
Chris said,
February 27, 2008 @ 9:52 pm
First: Thanks to aizatto for that very nice explanation!
Very useful!
To Dave: Good point! I think it’s not good having just an empty array as the transition is not allowed. Any idea how to patch it in a separate “file” / plugin? (I’m very new to rails, so sorry for that “stupid” question.)
Greetz,
Chris
tayfun said,
March 11, 2008 @ 9:11 pm
Many thanks for the enlightening article.
Here are some intricacies I believe I cannot describe well:
>> person = Person.new
=> #
>> person.shower!
NoMethodError: You have a nil object when you didn’t expect it!
But,
>> person.state = ‘working’
=> “working”
>> person
=> #
>> person.shower!
=> true
>> person
=> #
It’s still sleeping in the end! So what I understand is that it doesn’t matter what you do until the record is saved. It will still be initialized like it is defined no matter what events you call before.
I guess in the end it’s not good setting the default value through migrations like you say in the article. It will not matter when it is saved.
Good day.
tayfun said,
March 11, 2008 @ 9:43 pm
Oops, got the < etc. stripped. Here it is in a better format:
>> person = Person.new
=> #<Person id: nil, state: nil, created_at: nil, updated_at: nil>
>> person.shower!
NoMethodError: You have a nil object when you didn’t expect it!
But,
>> person.state = ‘working’
=> “working”
>> person
=> #<Person id: nil, state: “working”, created_at: nil, updated_at: nil>
>> person.shower!
=> true
>> person
=> #<Person id: 3, state: “sleeping”, created_at: “2008-03-11 15:04:41″, updated_at: “2008-03-11 15:04:41″>
It’s still sleeping in the end! So what I understand is that it doesn’t matter what you do until the record is saved. It will still be initialized like it is defined no matter what events you call before.
I guess in the end it’s not good setting the default value through migrations like you say in the article. It will not matter when it is saved.
Good day.
RAILroading » Blog Archive » links for 2008-05-06 said,
May 6, 2008 @ 9:41 am
[…] Ruby on Rails Finite State Machine Plugin: acts_as_state_machine ยป rails symphonies (tags: rubyonrails plugin) […]
Nick Poulden said,
May 15, 2008 @ 5:40 pm
Thanks for this really useful article. I’ve been looking for some good documentation for this plugin for a while!