A simple DSL to decorate existing methods with state transition guards.

Instead of using a DSL to define events, SimpleStateMachine decorates methods to help you encapsulate state and guard state transitions.

It supports exception rescuing, google chart visualization and mountable state machines.


Use gem install:

gem install simple_state_machine

Or add it to your Gemfile:

gem 'simple_state_machine'


Define an event and specify how the state should transition. If we want the state to change from :pending to :active we write:

event :activate_account, :pending => :active

That's it. You can now call activate_account and the state will automatically change. If the state change is not allowed, a SimpleStateMachine::IllegalStateTransitionError is raised.

Methods with arguments

If you want to pass arguments and call other methods before the state transition, define your event as a method.

def activate_account(activation_code)
  # call other methods, no need to add these in callbacks

Now mark the method as an event and specify how the state should transition when the method is called.

event :activate_account, :pending => :active

Basic example

class LampSwitch

   extend SimpleStateMachine

   def initialize
     self.state = 'off'

   event :push_switch, :off => :on,
                       :on  => :off


lamp =
lamp.state          # => 'off'           # => true
lamp.push_switch    #
lamp.state          # => 'on'
lamp.on?            # => true
lamp.push_switch    #           # => true



To add a state machine to an ActiveRecord class, you will have to:

  • extend SimpleStateMachine::ActiveRecord,

  • set the initial state in after_initialize,

  • turn methods into events

    class User < ActiveRecord::Base
      extend SimpleStateMachine::ActiveRecord
      def after_initialize
        self.state ||= 'pending'
      def invite
        self.activation_code = Digest::SHA1.hexdigest("salt #{}")
      event :invite, :pending => :invited
    user =
    user.pending?         # => true
    user.invite           # => true
    user.invited?         # => true
    user.activation_code  # => 'SOMEDIGEST'

For the invite method this generates the following event methods

  • invite (behaves like ActiveRecord save )

  • invite! (behaves like ActiveRecord save!)

If you want to be more verbose you can also use:

  • invite_and_save (alias for invite)

  • invite_and_save! (alias for invite!)

Using ActiveRecord / ActiveModel validations

When using ActiveRecord / ActiveModel you can add an error to the errors object. This will prevent the state from being changed.

If we add an activate_account method to User

class User  < ActiveRecord::Base
  def activate_account(activation_code)
    if activation_code_invalid?(activation_code)
      errors.add(:activation_code, 'Invalid')
  event :activate_account, :invited => :confirmed

user.confirm_invitation!('INVALID') # => raises ActiveRecord::RecordInvalid,
                                    #           "Validation failed: Activation code is invalid"
user.confirmed?                     # => false
user.confirmed?                     # => true

Mountable StateMachines

If you like to separate your state machine from your model class, you can do so as following:

class MyStateMachine < SimpleStateMachine::StateMachineDefinition

  event :invite,             :new     => :invited
  event :confirm_invitation, :invited => :active

  def decorator_class

class User < ActiveRecord::Base

  extend SimpleStateMachine::Mountable
  mount_state_machine MyStateMachine

  def after_initialize
    self.state ||= 'new'



Catching all from states

If an event should transition from all other defined states, you can use :all as from state:

event :suspend, :all => :suspended

Catching exceptions

You can let the state machine handle exceptions by specifying the failure state for an Error:

def download_data
  raise Service::ConnectionError
event :download_data, Service::ConnectionError => :download_failed

download_data               # catches Service::ConnectionError
state                       # => "download_failed"
state_machine.raised_error  # the raised error

Default error state

To automatically change all states to a default error state use default_error_state:

state_machine_definition.default_error_state = :failed


If you want to run events in transactions run them in a transaction block:

user.transaction { user.invite }


Generating state diagrams

When using Rails/ActiveRecord you can generate a state diagram of the state machine via the built in rake tasks. For details run:

rake -T ssm

A Googlechart example:

Note on Patches/Pull Requests

  • Fork the project.

  • Make your feature addition or bug fix.

  • Add tests for it. This is important so I don't break it in a future version unintentionally.

  • Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)

  • Send me a pull request. Bonus points for topic branches.

Copyright © 2010 Marek & Petrik. See LICENSE for details.