An Intuitive State Transition System & State Machine
StateJacket provides an intuitive approach to building complex state machines by isolating the concerns of the state transition system & state machine.
gem install state_jacket
Let's define states & transitions (i.e. the state transition system) & a state machine for a turnstyle.
system = StateJacket::StateTransitionSystem.new
system.add :opened => [:closed, :errored]
system.add :closed => [:opened, :errored]
system.lock # prevent further changes
system.to_h.inspect # => {"opened"=>["closed", "errored"], "closed"=>["opened", "errored"], "errored"=>nil}
system.transitioners # => ["opened", "closed"]
system.terminators # => ["errored"]
system.can_transition? :opened => :closed # => true
system.can_transition? :closed => :opened # => true
system.can_transition? :errored => :opened # => false
system.can_transition? :errored => :closed # => false
Define the events that trigger transitions defined by the state transition system (i.e. the state machine).
machine = StateJacket::StateMachine.new(system, state: "closed")
machine.on :open, :closed => :opened
machine.on :close, :opened => :closed
machine.lock # prevent further changes
machine.to_h.inspect # => {"open"=>[{"closed"=>"opened"}], "close"=>[{"opened"=>"closed"}]}
machine.events # => ["open", "close"]
machine.state # => "closed"
machine.is_event? :open # => true
machine.is_event? :close # => true
machine.is_event? :other # => false
machine.can_trigger? :open # => true
machine.can_trigger? :close # => false
machine.state # => "closed"
machine.trigger :open # => "opened"
machine.state # => "opened"
# you can also pass a block when triggering events
machine.trigger :close do |from_state, to_state|
# custom logic can be placed here
from_state # => "opened"
to_state # => "closed"
end
machine.state # => "closed"
# this is a noop because can_trigger?(:close) is false
machine.trigger :close # => nil
machine.state # => "closed"
begin
machine.trigger :open do |from_state, to_state|
raise # the transition isn't performed if an error occurs in the block
end
rescue
end
machine.state # => "closed"