SimpleMachine

SimpleMachine is module for Ruby which injects simple state machine behavior in any class that includes it. It can be defined on multiple fields in the same class

Installation

Install it with “gem install simple_machine” then require ‘simple_machine’ in your project, or if you use Bundler add this to your Gemfile:

gem "simple_machine"

Usage

After requiring ‘simple_machine’ inject state machine in your class like this:

require 'simple_machine'

class Phone
  include SimpleMachine  
  implement_state_machine_for :my_state do
    initial_state :off
    other_states :ready, :dialing, :busy
    allow_transition :turn_on,  :from => :off,     :to => :ready
    allow_transition :dial,     :from => :ready,   :to => :dialing
    allow_transition :hangup,   :from => :dialing, :to => :ready 
    allow_transition :hangup,   :from => :busy,    :to => :ready
    allow_transition :turn_off, :from => :ready,   :to => :off do
      # self is set to phone instance
      puts my_state #=> :ready  
      # do something before transition ...
      true # state will be changed to :off if block returns true  
    end
    after_transition do
      # self is set to phone instance
      puts "New state is '#{my_state}'"
    end
  end	
end

This chunk of code produces following effects:

Phone.my_state_default_state #=> :off
phone = Phone.new
phone.my_state #=> :off
phone.my_state_machine.allowed_transitions #=> [:turn_on]
phone.my_state_machine.can_dial? #=> false
phone.my_state_machine.dial #=> raises exception: 'Invalid transition #dial from 'off' state'
phone.my_state_machine.can_turn_on? #=> true
phone.my_state_machine.turn_on #=> :ready
phone.my_state #=> :ready

Transition Guards

You can also implement transition guards in your class like this:

class Phone
  def guard_for_dial_on_my_state; false; end
end

In this case even if dial is allowed transition from ready state this is what will happen:

phone.my_state #=> :ready
phone.my_state_machine.allowed_transitions #=> [:hangup]
phone.my_state.machine.can_dial? #=> false
phone.my_state_machine.dial #=> raises exception: 'Unable to dial due to guard'

Callback

After each transition callback is invoked if it is defined:

class Phone
  include SimpleMachine  
  implement_state_machine_for :my_state do
    # ...
    after_transition do
      # self is set to phone instance
      puts "New state is '#{my_state}'"
    end
  end
end

Test

If you want to run tests you will need ‘rspec’ and ‘mocha’ gems

Disclaimer

This library was made without any pretend to be big-enterprise-sega-mega solution for State machine, but I still hope you will find this small library useful in some cases (I already do :). If you have any issues or ideas feel free to fork the project and send a pull request.