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.

Note: 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.

Note: 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.

Note: 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:

  • Callbacks can be either a symbol or a Proc. If used as a symbol, the instance method of the model will be called
  • The callbacks act differently if the model is a new record and hasn’t been saved, versus an already saved model.

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?

This entry was posted in acts_as_state_machine. Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

27 Comments

  1. Posted 10 June, 2007 at 11:03 am | Permalink

    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

  2. Posted 10 June, 2007 at 3:02 pm | Permalink

    Thanks for the corrections Adam :) much appreciated, wrote this up quite late, so the mistakes must have slipped through.

    Its a wonderful plugin indeed :)

  3. T
    Posted 3 August, 2007 at 9:49 pm | Permalink

    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?

  4. Posted 3 September, 2007 at 7:54 am | Permalink

    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!

  5. Posted 1 December, 2007 at 1:41 am | Permalink

    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

  6. Frederico Araujo
    Posted 3 December, 2007 at 7:59 pm | Permalink

    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

  7. Noah
    Posted 15 December, 2007 at 3:58 pm | Permalink

    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.

  8. Posted 3 January, 2008 at 10:41 am | Permalink

    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?

  9. Noah
    Posted 3 January, 2008 at 12:46 pm | Permalink

    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.

  10. Kris Nuttycombe
    Posted 5 January, 2008 at 6:44 am | Permalink

    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.

  11. Adam
    Posted 8 January, 2008 at 1:38 am | Permalink

    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?

  12. Sau Sheong
    Posted 27 January, 2008 at 1:41 pm | Permalink

    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.

  13. Posted 29 January, 2008 at 2:07 am | Permalink

    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

  14. Posted 16 February, 2008 at 6:01 am | Permalink

    This is a really great tutorial. Thanks.

  15. Posted 16 February, 2008 at 6:03 am | Permalink

    This is a really great tutorial. Thanks. You might want to check into your host, or perhaps something on the page here; it took a good 10 minutes to load for me. Then again, I had plenty to absorb as I did read :-)

  16. Chris
    Posted 27 February, 2008 at 9:52 pm | Permalink

    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

  17. Posted 11 March, 2008 at 9:11 pm | Permalink

    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.

  18. Posted 11 March, 2008 at 9:43 pm | Permalink

    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.

  19. Posted 15 May, 2008 at 5:40 pm | Permalink

    Thanks for this really useful article. I’ve been looking for some good documentation for this plugin for a while!

  20. Stephen
    Posted 17 May, 2008 at 6:00 am | Permalink

    can you use an observer to log and/or mail out state changes when they occur, since they’re not actual ActiveRecord hooks?

  21. dkmenace
    Posted 21 May, 2008 at 7:23 am | Permalink

    Has anyone got this plugin working with Rails 2.0.X?

  22. Posted 23 May, 2008 at 11:49 am | Permalink

    @Stephen -
    Why use an Observer? You can just send via an ActionMailer model — or whatever else you need to do — in a state change callback method. That’s the type of thing they’re there for :-) Example:

    state :forgetful, :enter => :do_forgot_password

    def do_forgot_password
    make_password_reset_code
    UserMailer.deliver_forgot_password(self)
    end

    @dkmenace -
    I’ve been using acts_as_state_machine with 2.0.2 and Edge and haven’t had problems.

  23. Resonante
    Posted 12 July, 2008 at 1:13 am | Permalink

    Epales!

    how can i list the states on the view ?

    Help!

    thanks.

  24. Posted 15 July, 2008 at 1:28 am | Permalink

    It would be great to use an observer because a model should not be responsible of the mail notification, that dependency should not exist, if we have many of those kind of dependencies the model gets too many responibilities to handle and the code starts to get unmantainable. Of course, in many cases, just using :enter is enough.

  25. Erik Petersen
    Posted 18 October, 2008 at 12:33 am | Permalink

    Just a addendum, the :enter callback takes a proc or symbol, the :after and :exit callbacks take a proc or an array of symbols thus you can chain callbacks:

    :after => [:this, :then, :that] # => Yes!
    :enter => [:that, :then, :this] # => No!

    Thanks for the writeup!

  26. Posted 17 May, 2009 at 9:42 am | Permalink

    Thanks for the great article.

  27. Posted 2 June, 2009 at 11:31 pm | Permalink

    What you refer to as adjectives are actually non-finite verb forms, or gerunds.

3 Trackbacks

  1. By Finite State Machines in Rails at Matt Didcoe on 10 June, 2007 at 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 [...]

  2. [...] Ruby on Rails Finite State Machine Plugin: acts_as_state_machine ยป rails symphonies (tags: rubyonrails plugin) [...]

  3. By acts_as_state_machine - state is always nil on 26 July, 2008 at 2:07 pm

    [...] Here’s more info on the acts_as_state_machine plugin. [...]

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*