The Rails State Machine

8.5.09 by Nathaniel Bibler

Go and StopRuby 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 … 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]
    end
  end

  def catch_runners
    puts "That'll be $250."
  end
end

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?
end

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.

Photo credit: Go and Stop

Related posts:

  1. Getting Started with the Rails 3 BugMash
  2. Scaling Rails – On The Edge – Part 2
  3. Scaling Rails – On The Edge – Part 1

81 Responses to “The Rails State Machine”

Comments 10 Other Comments

  1. nachokb says:

    Excellent… will it be backported for 2.3.x? or is it a 3.0-only feature?

    – nachokb

  2. It’s interesting that they have rolled this into core, where in the past they have extracted such items such as “acts_as_tree” into plugins.

  3. Avdi says:

    I’m pleased to see the influence of AlterEgo (http://alter-ego.rubyforge.org/), in the form of state-based polymorphism and my much-abused traffic light example, making it into ActiveRecord :-)

  4. Elad Meidar says:

    Available…. when ?

  5. @nachokb and @elad: Exactly when it will be released into a formal release, I can’t say for sure. It is already in the mainline release branch, so I would assume that it would hit the next 2.3.x release unless 3.0 were to be released before that time. It shouldn’t be necessary to back-port.

    @avdi: Thanks for stopping in and looking this over. I must admit that I’m a state machine junkie. I think alter-ego is probably the closest to the true Gang of Four implementation that I’ve seen of a SM in Ruby. Great job on it. I certainly apologize for the traffic light example, it was inspired by the test cases in Rails for the new StateMachine module. Which, probably, was inspired by your Alter Ego post. :)

  6. Andrea says:

    Finally some updates, thanks Nathaniel, you are doing a very good work :)

    This comment was originally posted on Riding Rails

  7. simmsy says:

    I’m really pleased that the core has a state machine, however I think there is a better impl which is http://github.com/sbfaulkner/has_states/tree/master, you can have multiple state columns and it overcomes the following limitations with aasm (and possibly AR:SM), “Validations and guards were redundant, transitions were not observable and direct manipulation of state attributes did not result in transitions.” The latter especially useful in RESTful manipulation of states. Maybe these limitations have been overcome in the core version, but at a glace it looked like a port of aasm. Cheers.

  8. [...] The Rails State Machine « Envy Labs A interesting writeup on the new ActiveModel::StateMachine implementation [...]

  9. Dan Pickett says:

    Am I the only one that has a problem with this? I thought we were supposed to be taking bloat out of core rather than putting more into it. A state machine implementation really doesn’t seem like core functionality to me. Additionally, I might prefer one state machine implementation over the other.

    I don’t get it.

  10. V says:

    Кириллица is great! :)

    This comment was originally posted on Riding Rails

  11. Nathaniel says:

    Thanks Andrea. I’m aiming to get an Edge Rails post out around once every week for the foreseeable future.

    This comment was originally posted on Riding Rails

  12. Jacek Becela says:

    Dan: You’re not the only one. I don’t get it too.

  13. @dan I think it’s important to note that a simple state machine implementation would likely be quite useful internally to core. Often, SMs can clean up logic and make intent more easily conveyed. But, as far as I’ve seen, it hasn’t yet been used anywhere in the core.

    @simmsy I can certainly agree with some of your feelings towards AASM, although I’m not sure I’d be on board with “manipulation of state attributes” resulting in transitions. That seems a little awkward to me. I would rather my state attribute be closed to the world and entirely private to the instance. I prefer always transitioning my SMs via a transition method, which allows for callbacks and more complex logic than just change A to B. It seems like bad form to potentially allow an external party to randomly define your state value.

  14. Rails Fanboy says:

    Clean, concise and to the point.Nice Post Nathaniel!

    This comment was originally posted on Riding Rails

  15. Avdi says:

    @nathaniel: No apology needed, since I think I lifted the traffic light example from the Gang of Four :-)

  16. Dr Nic says:

    A feature best demonstrated with a silly example or a fib(n) example is perhaps a feature pushed into a plugin :)

    This comment was originally posted on Riding Rails

  17. I also don’t like it as a core feature, hope it would be extracted into a plugin.

  18. austinfromboston says:

    @DanPickett: A state machine is such a frequently used pattern that having one in the core seems like a bonus. If you’re a savvy developer with a preferred implementation you’re still welcome to use whatever you like.

  19. Christian Romney says:

    I think we have to look at this addition in context of the other changes in Rails 3. Specifically, Rails 3 is becoming more modular so you’ll be able to take what you need and leave what you don’t. If you look at the example, he had to mix in the functionality – it wasn’t bloating AR::Base by default. With that in mind, it’s pretty much a win all around. State machines are so common (multi-step processes like signups and checkouts are the norm), it’s great to have the functionality in the framework. Rails will always let you choose something else but having an officially supported state machine implementation out of the box is a nice bonus.

  20. Aleco says:

    Just for clarification: is ‘edge’ the next 2.3.x release, or have all these features/changes only made it into the 3.x branch?

    This comment was originally posted on Riding Rails

  21. Michael Johann says:

    @Dr NicGood point. Could be a rule of thumb.@NathanielGreat job, we really do need more of these posts.Thanks

    This comment was originally posted on Riding Rails

  22. Mike Gunderloy says:

    Glad to see TWIER starting up again. Have fun!

    This comment was originally posted on Riding Rails

  23. Ches Martin says:

    austinfromboston and Christian Romney++ Glad I refreshed the article I’d had open for awhile before replying :-)

    As long as it’s a mixin, it’s a welcome addition to me. One less plugin needed for pretty much every app I have with user signups, and it’s a well-defined, archetypal pattern where a stable API in core can stand as a reference implementation.

  24. [...] The Rails State Machine Rails edge recently added built-in state machine functionality. Here’s a few examples of how you might take advantage of this feature. [...]

  25. I’m glad to see this getting added to the core. A preferred state machine is a win.

  26. Chris says:

    I’m happy too! …never have to worry about it not working with future versions..

  27. Paul Barry says:

    Add me to the list of guys who don’t get why this is in core

  28. Jay Levitt says:

    What I don’t get is why this is in core *but authentication isn’t*.

  29. August Lilleaas says:

    Encoding issues still annoys the hell out of me. I hope I don’t have to do things like http://bit.ly/zr1Pg in 3.0.With that said, 3.0 will solve so many problems. You should jump straight to 7.0 or something, to reflect the awesomeness.

    This comment was originally posted on Riding Rails

  30. Kieran P says:

    It would be nicer as a plugin, but I’m not fussed. These defaults are what makes Rails so great! People are more likely to use something built into Rails and compatibility maintained between versions. Makes things easier for new developers joining a project. I read somewhere that 3.0 will include a background task system too, which will be great!

  31. [...] ActiveRecord::StateMachine (announced) [...]

  32. Jeff Dean says:

    I think core should have released this as a gem.

    Between app templates, geminstaller and config.gems any pain associated with installing gems is reduced to almost nothing. It would add seconds to an app developers time, but it will add to the burden of developers who want to contribute to rails since it’s one more thing that could break.

    And if we’re talking about adding to the framework, how about pulling in will_paginate. Every rails app I’ve ever seen uses will_paginate, and there are no other viable alternatives to my knowledge, nor are there any serious arguments about it’s implementation. But will_paginate is not in core.

    From a support standpoint, I think rails core just added more work for themselves unecessarily. If it were a gem, they could open up the core contributor list to people like authors of other state_machine gems, managed it in a separate lighthouse project and if it fails, let it die gracefully, like acts_as_nested_set. If this makes it into a release, it’s just one more piece of noise that will take up core members time and divert attention from making less controversial parts of the framework better.

  33. Renoke says:

    It’s quite interesting, thanks Nathaniel. But as Jeff Dean, I don’t see the point to include this in the core framework. This is making it more fat. Plugin or gem are better place as it’s fit for specific situations.

  34. Nicolas Blanco says:

    When Rails 2 was released, all parts that were not needed and could be released as a plugin/gem like acts_as_list, acts_as_nested_set were externalized.Now, there are implementing states machines in ActiveRecord, but many plugins already exist for that (AASM, PluginaWeek state_machine)…I don’t understand the current philosophy of Rails Core.

    This comment was originally posted on Riding Rails

  35. Guoliang Cao says:

    Totally agree with Jeff and Renoke. Even this can be very useful, it is probably better to be released as a gem. What about ActiveStateMachine ? :)

  36. [...] The Rails State Machine « Envy Labs [...]

  37. Most of us are coding state machine support code into models anyway. after_saves and the like running conditionally in our models are doing what this DSL can express more elegantly. I’m hanging out for the controller equivalent for stateful navigation to clean up before_filter support logic.

    Would core benefit from inclusion? Six of one, half a dozen of the other. Simply ’six’ is more direct IMHO.

  38. Andrea Sancio says:

    I like it in the core, i use it quite often and a i think simple state machine fit well in activerecord.
    Thanks for the post.

  39. Omar says:

    Interesting. Please consider taking my 5 minute survey on models that includes questions on state machines use.

    http://spreadsheets.google.com/viewform?hl=en&cfg=true&formkey=cFZqeVg0cGZzZkZpSFl1SVNjaFI2UEE6MA..

  40. Omar says:

    any information on how the state machine is implemented inside ruby?

  41. Silas Baronda says:

    your example doesn’t work anymore.
    You need to change:
    include ActiveRecord::StateMachine
    to:
    include ActiveModel::StateMachine

  42. @silas Thanks. The post has been updated to reflect the move to ActiveModel from ActiveRecord.

  43. Silas Baronda says:

    Actually I was incorrect. ActiveModel isn’t persistent while ActiveRecord is.

  44. Seth Ladd says:

    It appears that as of Rails 2.3.4 this functionality is not included. Is this slated for 2.3.x or 3.0 ?

  45. lobo_tuerto says:

    I also agree that this little utility should be in the core. Come on, you always end up needing a state machine, one way or another.

  46. Here you have a state pattern based alternative that integrates with ActiveRecord: http://github.com/dcadenas/active_record_state_pattern

    The library intentionally follows the classic state pattern implementation (no state mixins, classical delegation to simple state classes, etc.) believing that by doing so we increase flexibility (internal DSL constraints vs plain object oriented Ruby power), simplicity and clarity.

  47. Casey Helbling says:

    Any update about when this is slated for release – 3.0? Where is the official ‘timeline’ for release feature sets – lighthouse?

  48. Keith Pitt says:

    I don’t get why people are complaining about why its in core… Its not a big deal! It’s a mixin! Its not in by default, it doesn’t bloat anything, its just an extra piece of love that you can use if you want. Every project I’ve ever done needed a state machine, one way or another – so I welcome this functionality with open arms.

  49. carlivar says:

    This looks great but I too am wondering if I should wait for a 2.3.x minor release or if this is a 3.0 feature. I spent a little while trying to find that information with no luck.

  50. tonymi says:

    As per the rails 3.0 beta release notes ActiveRecord::StateMachine has been removed from the rails 3.0 release. Does anyone know whether this module will be released as a separate gem?

Leave a Reply

* Required Fields

Additional comments powered by BackType