Lines of Code Code Status Dependency Status Build Status Coverage Status Downloads

StateJacket

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.

Install

gem install state_jacket

Example

Let's define states & transitions (i.e. the state transition system) & a state machine for a turnstyle.

Turnstyle

State Transition System

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

State Machine

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"