Edge State Machine

Edge State Machine is a complete state machine solution. It offers support for ActiveRecord, Mongoid and MongoMapper for persistence.

<img src=“https://secure.travis-ci.org/danpersa/edge-state-machine.png”/>

Supported Features

  • Multiple state machines per class each of them acting independently

  • Find errors in state machine definitions as early as possible

  • Transition guards

  • Multiple actions executed on transitions

  • Multiple actions executed on entering and exiting a state

  • No other dependencies for non-persistent state machines

  • Minimal dependencies for persistent ones

Installation

If you’re using Rails + ActiveRecord + Bundler

# in your Gemfile
gem "edge-state-machine", :require => ["edge-state-machine", "active_record/edge-state-machine"]

# in your AR models that will use the state machine
include ::EdgeStateMachine
include ActiveRecord::EdgeStateMachine

state_machine do
  state :available # first one is initial state
  state :out_of_stock
  state :discontinue

  event :discontinue do
    transitions :to => :discontinue, :from => [:available, :out_of_stock], :on_transition => :do_discontinue
  end
  event :out_of_stock do
    transitions :to => :out_of_stock, :from => [:available, :discontinue]
  end
  event :available do
    transitions :to => :available, :from => [:out_of_stock], :on_transition => :send_alerts
  end
end

If you’re using Rails + Mongoid + Bundler

# in your Gemfile
gem "edge-state-machine", :require => ["edge-state-machine", "mongoid/edge-state-machine"]

# in your AR models that will use the state machine
include ::EdgeStateMachine
include Mongoid::EdgeStateMachine

state_machine do
  state :available # first one is initial state
  state :out_of_stock
  state :discontinue

  event :discontinue do
    transitions :to => :discontinue, :from => [:available, :out_of_stock], :on_transition => :do_discontinue
  end
  event :out_of_stock do
    transitions :to => :out_of_stock, :from => [:available, :discontinue]
  end
  event :available do
    transitions :to => :available, :from => [:out_of_stock], :on_transition => :send_alerts
  end
end

If you’re using Rails + MongoMapper + Bundler

# in your Gemfile
gem "edge-state-machine", :require => ["edge-state-machine", "mongo_mapper/edge-state-machine"]

# in your models that will use the state machine
include ::EdgeStateMachine
include MongoMapper::EdgeStateMachine

state_machine do
  state :available # first one is initial state
  state :out_of_stock
  state :discontinue

  event :discontinue do
    transitions :to => :discontinue, :from => [:available, :out_of_stock], :on_transition => :do_discontinue
  end
  event :out_of_stock do
    transitions :to => :out_of_stock, :from => [:available, :discontinue]
  end
  event :available do
    transitions :to => :available, :from => [:out_of_stock], :on_transition => :send_alerts
  end
end

State Machine Examples

Microwave State Machine

class Microwave
  state_machine :microwave do  # name should be optional, if the name is not present, it should have a default name
                               # we give state machines names, so we can pun many machines inside a class
    initial_state :unplugged # initial state should be optional, if the initial state is not present, the initial state will be the first defined state

    state :unplugged

    state :plugged

    state :door_opened do
      enter :light_on            # enter should be executed on entering the state
      exit  :light_off           # exit method should be executed on exiting the state
    end

    state :door_closed

    state :started_in_grill_mode do
      enter lambda { |t| p "Entering hate" }   # should have support for Procs
      exit  :grill_off
    end

    state :started do
      enter :microwaves_on
      exit  :microwaves_off
    end

    event :plug_in do
      transition :from => :unplugged, :to => :plugged
    end

    event :open_door do
      transition :from => :plugged, :to => :door_opened
    end

    event :close_door do
      transition :from => :door_opened, :to => :door_closed,
                 :on_transition => :put_food_in_the_microwave # we can put many actions in an Array for the on_transition parameter
    end

    event :start do
      transition :from => :door_closed, :to => [:started, :started_in_grill_mode],
                 :on_transition => :start_spinning_the_food,
                 :guard => :grill_button_pressed?    # the method grill_button_pressed? should choose the next state 
    end

    event :stop do
        transition :from => [:started, :started_in_grill_mode], :to => :door_closed
    end
  end  
end

Dice State Machine

class Dice

  state_machine do
    state :one
    state :two
    state :three
    state :four
    state :five
    state :six

    event :roll do
      transition :from => [:one, :two, :three, :four, :five, :six],
                 :to => [:one, :two, :three, :four, :five, :six],
                 :guard => :roll_result,
                 :on_transition => :display_dice_rolling_animation
    end
  end

  def roll_result
    # return one of the states
  end

  def display_dice_rolling_animation
    # draw the new position of the dice
  end
end

class User
  state_machine do
    state :pending # first one is initial state
    state :active
    state :blocked # the user in this state can't sign in

    event :activate do
      transition :from => [:pending], :to => :active, :on_transition => :do_activate
    end

    event :block do
      transition :from => [:pending, :active], :to => :blocked
    end
  end    
end

Other Examples

For other (more complex) examples, please check the following links:

Notes

For classes with multiple state machines, the state names, machine names must be unique per class.

The same thing with the event names.

Credits

The gem is based on Rick Olson’s code of ActiveModel::StateMachine, axed from ActiveModel in this commit.

And on Krzysiek Heród’s gem, Transitions, which added Mongoid support.