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.