Agile Web Development

Build it. Launch it. Love it.

Acts As State Machine

Summary

Adds state machine functionality to any model. This makes it much easier to model complex constraints and behaviours.

Example

Here’s a picture of the state machine we want: http://www-128.ibm.com/developerworks/java/library/j-cb03137/figure1.gif

And here’s what the code would look like:

  class Nonprofit < ActiveRecord::Base

    acts_as_state_machine :initial => :created, :column => 'status'

    # These are all of the states for the existing system.
    state :submitted
    state :processing
    state :nonprofit_reviewing
    state :accepted

    event :accept do
      transitions :from => :processing, :to => :accepted
      transitions :from => :nonprofit_reviewing, :to => :accepted
    end

    event :receive do
      transitions :from => :submitted, :to => :processing
    end

    # either a CTP  or nonprofit user edits the entry, requiring a review
    event :send_for_review do
      transitions :from => :processing, :to => :nonprofit_reviewing
      transitions :from => :nonprofit_reviewing, :to => :processing
      transitions :from => :accepted, :to => :nonprofit_reviewing
    end

  end

(Image/example taken from an IBM article: http://www-128.ibm.com/developerworks/java/library/j-cb03137/index.html)

Vitals

Home http://github.com/rubyist/aasm/tree/master
Repository git://github.com/rubyist/aasm.git
License Rails' (MIT)
Tags Tag_red acts_as machine state workflow
Rating (88 votes)
Created 20 April 2006

Comments

  • Avatar
    30 November 2007

    Hi, I added a couple of enhancements which may be useful to others:

    • An on_transition callback for doing some work while transitioning.
    • Being able to specify a next state while firing an event. More details at http://justbarebones.blogspot.com/2007/11/actsasstatemachine-enhancements.html

    • c

  • Avatar
    jkaplan
    9 January 2008

    Thanks for the great work Scott. Shouldn't the runinitialstateactions method be attached to the beforecreate callback (after setinitialstate)? Otherwise, one can't attach logic to the model that affects its attributes without saving the object at least twice.

  • Avatar
    28 March 2008

    The home is no longer valid.

  • Avatar
    gregh
    22 April 2008

    I'm looking for something that supports creating validations back to the user if they try to make an update that doesn't satisfy state criteria. For example if I have a model for personal expenses ("Expense") and that if I want to transition it from PAID to BANKRECONCILED then I was to make sure that the BANKRECONCILED_DATE field is populated before allowing this to proceed. So perhaps I really want something like:

    • User updates an Expense and changes the status to BANKRECONCILED but doesn't add a BANKRECONCILED_DATE
    • model update sees change in status and calls the "bank_reconciled!" action
    • this action either works, or else provides a set of validations back

    Is this a reasonable approach? Or do people think it would be best to handle this with the normal Rails basic validation approach (i.e. and not use "acts_as_statemachine")

  • Avatar
    Bill
    24 April 2008

    I did one of these in Java last week. Yours is extremely similar.

    After the base (what you have) I made a revision that turned out to be fairly easy, you might want to consider something like it.

    I actually used a state table to define the transitions, rather than your manual transition calls.

    So you still define all the states and all the events, but then you create the transitions like this:

    a, b, c,
    

    e1, b, x, x, e2, x, c, x, e3, x, a, a

    In Java I had to put all that into an array, so the whole thing ende up looking somewhat like this:

    State a, b, c; Event e1, e2, e3; Object x=null;

    new StateEngine(new Object[]={ a, b, c, e1, b, x, x, e2, x, c, x, e3, x, a, a });

    My own little DSL; but in ruby I bet you could clean that up a bit. I'd love to get rid of the comas, and it wouldn't hurt to have colons after the events. My ideal would have been to have the table look like this:

    states: a b c e1: b x x e2: x c x e3: x a a

    I would have made it look like that in java (by assigning that whole mess to a string, I think), but the JVM I'm working on doesn't support reflection.

  • Avatar
    bill
    28 April 2008

    Sorry, the system reformatted my text. Those long initializers are supposed to be shaped like a matrix.

    It's a State Transition Table translated directly to code. Wikipedia has an article on State Transition Tables with examples.

  • Avatar
    1 May 2008

    Great plugin ... we've been using it a lot. I thought I'd share this one little enhancement that has been useful to me. It allows you to call <code>possible_events</code> on any state machine object and you will get back an array of events that can be called, considering the object's state.

    Here's the code, it goes in actsas_statemachine.rb in the InstanceMethods module.

    <pre>

    Returns all possible events that can be called on the object in it's current state, as a Ruby symbol

    def possible_events returning Array.new do |events| self.class.readinheritableattribute(:transitiontable).eachpair do |event, value| events << event if value.detect{|transition| transition.from == current_state } end end end </pre>

  • Avatar
    Xin
    5 May 2008

    Thanks for the great plugin!

    I would be beneficial having the following features: 1) specify next action for a state. This can be used to explain to the user what action is waiting to happen. i.e. for submitted state, it's waiting to be reviewed.

    2) Order states so can be shown in a drop down list.

    I'll have a go at implementing these when I get some time.

  • Avatar
    Xin
    5 May 2008

    Thanks for the great plugin!

    I would be beneficial having the following features: 1) specify next action for a state. This can be used to explain to the user what action is waiting to happen. i.e. for submitted state, it's waiting to be reviewed.

    2) Order states so can be shown in a drop down list.

    I'll have a go at implementing these when I get some time.

  • Avatar
    21 May 2008

    Great plugin. I added a entry on my blog that describes how to use metaprogramming with state machine ;)

  • Avatar
    11 June 2008

    Dead link, has this project moved?

  • Avatar
    9 August 2008

    Svn is working normally. You can also find some forks at git: http://github.com/search?q=actsas_statemachine&x=0&y=0 or you can use something similar (better?) http://github.com/ryan-allen/workflow/tree/master dk

  • Avatar
    Arshak Navruzyan
    3 November 2008

    The return type for a transition is confusing. It seems to return one of the following 3:

    1. true (no false)
    2. []
    3. StateTransition object

    Would it make more sense to turn a true / false depending on whether the transition actually happened and populate object.errors with the guard piece (also some meta data that helps map different guard statements to something end-user readable would be helpful)

  • Avatar
    30 December 2008

    It seems that the updated url for this project is: http://github.com/rubyist/aasm/tree/master

  • Avatar
    Dan
    12 January 2009

    Great idea, great execution

  • Pete
    24 January 2009

    If you want to do something like User.new.register! you will end up in state passive and not in pending like you should be.

    Workaround is to do:

    def setinitialstatewithoverride end aliasmethodchain :setinitialstate, :override

  • Pete
    24 January 2009

    Why is the exit action run after the enter action...and after the update_attributes call. For a brief period, we're in 2 states!

  • Avatar
    24 March 2009

    Excellent plugin. Makes modelling some complex states extremely simple and intuitive. However, does it adhere to before/after_save's transactional behaviour? (ie if exception is raised, does the db call a rollback?)

Add a comment