Skip to content

Latest commit

 

History

History
134 lines (98 loc) · 4 KB

README.rdoc

File metadata and controls

134 lines (98 loc) · 4 KB

SimpleStateMachine

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.

Example usage

Write a method, arguments are allowed:

def activate_account(activation_code)
  # call other methods, no need to add these in callbacks
  ...
  log.debug "Try to activate account with #{activation_code}"
end

Now mark the method as an event and specify how the state should transition when the method is called. In this example, the activate_account method will set the state to :active if the initial state is :pending.

event :activate_account, :pending => :active

That’s it! You can now call the method and the state will automatically change. If the state change is not allowed, a SimpleStateMachine::Error is raised.

Example usage with ActiveRecord / ActiveModel

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

def activate_account(activation_code)
  if activation_code_invalid?(activation_code)
    errors.add(:activation_code, 'Invalid')
  end
end

Example usage with exceptions

You can rescue exceptions and specify the failure state

def download_data
  Service.download_data
end
event :download_data, :pending => :downloaded,
      Service::ConnectionError => :download_failed

More complete implementation

To add a state machine:

  • extend SimpleStateMachine

  • set the initial state

  • turn methods into events

    class LampWithHotelSwitch
    
      extend SimpleStateMachine
    
      def initialize
        self.state = :off
      end
    
      def push_switch_1
        puts 'pushed switch 1 #{state}'
      end
      event :push_switch_1, :off => :on,
                            :on  => :off
    
      # define another event
      # note that implementation of :push_switch_2 is optional
      event :push_switch_2, :off => :on,
                            :on  => :off
    
    end
    

ActiveRecord Example

To add a state machine with ActiveRecord persistence:

  • extend SimpleStateMachine::ActiveRecord,

  • set the initial state in after_initialize,

  • turn methods into events

    class User < ActiveRecord::Base
    
      extend SimpleStateMachine::ActiveRecord
      # define a custum state_method (state is default)
      state_machine_definition.state_method = :ssm_state
    
      def after_initialize
        self.ssm_state ||= 'new'
        # if you get an ActiveRecord::MissingAttributeError
        # you'll probably need to do (http://bit.ly/35q23b):
        #   write_attribute(:ssm_state, "new") unless read_attribute(:ssm_state)
      end
    
      def invite
        self.activation_code = Digest::SHA1.hexdigest("salt #{Time.now.to_f}")
        #send_activation_email
      end
      event :invite, :new => :invited
    
      def confirm_invitation activation_code
        if self.activation_code != activation_code
          errors.add 'activation_code', 'is invalid'
        end
      end
      event :confirm_invitation, :invited => :active
    
      # :all can be used to catch all from states
      event :suspend, :all => :suspended
    end
    

This generates the following methods

  • {event}_and_save works like save

  • {event}_and_save! works like save!

  • {event}! works the same as {event}_and_save!

  • {state}? whether or not the current state is {state}

This code was just released, we do not claim it to be stable.

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.