The Rails State Machine

Ruby on Rails recently added a built-in ActiveRecord::StateMachine implementation and even more recently tied it in to ActiveRecord. And, for being a built-in library, it’s pretty damned fully-featured. Really, if this one doesn’t do it for you, then you probably need to write it yourself, anyway.

And, if you’re thinking that you don’t know what a state machine is, or think that you’ve never used one, think again. If you’ve ever used the restful_authentication plugin, you’ve probably used a state machine. In that library, Users can be pending, active, inactive &mdash It’s all the same object, just acting differently depending on its current situation. That, in a nutshell, is a state machine.

Let’s start with an example that most people might recognize:

class TrafficLight < ActiveRecord::Base
  include ActiveRecord::StateMachine
  state_machine do
    state :red
    state :green
    state :yellow
    event :change_color do
      transitions :to => :red,    :from => [:yellow], :on_transition => :catch_runners
      transitions :to => :yellow, :from => [:green]
      transitions :to => :green,  :from => [:red]
  def catch_runners
    puts "That'll be $250."

light = TrafficLight.new
light.current_state       #=> :red
light.change_color!       #=> true
light.current_state       #=> :green
light.change_color!       #=> true
light.current_state       #=> :yellow
light.change_color!       #=> true
"That'll be $250."

So, that’s a basic state machine. The light changes from red to green to yellow to red. That’s it. It’s a boring life for a stop light. Certainly, this isn’t a glamorous example, but there are some interesting points here:

  1. The initial state for the TrafficLight is red, which is the first state defined.
  2. On a successful transition to red (from yellow), the local catch_runners method is executed.
  3. The model acts differently depending on its current state, for instance, the change_color! method has a different action depending on the current color of the light.

And that’s not all. The built-in StateMachine also supports success callbacks per event, enter and exit callbacks per state, guards on transitions, and a lot more:

event :sample, :success => :we_win do; ...; end
state :open, :enter => [:alert_twitter, :send_emails], :exit => :alert_twitter
event :close do
  # You may only close the store if the safe is locked!!
  transitions :to => :closed, :from => :open, :guard => :safe_locked?

So, if you’ve ever used the AASM plugin, you’ll probably feel right at home here with the ActiveRecord::StateMachine. I’m finding myself quite comfortable. In fact, I just might relocate.

- Nathaniel Bibler

08.05.09 ← See All Posts
blog comments powered by Disqus