Class: VeryTinyStateMachine
- Inherits:
-
Object
- Object
- VeryTinyStateMachine
- Defined in:
- lib/very_tiny_state_machine.rb
Overview
A mini state machine object that can be used to track a state flow.
The entire state machine lives in a separate variable, and does not pollute the class or the module of the caller. The state machine has the ability to dispatch callbacks when states are switched, the callbacks are dispatched to the given object.
@automaton = VeryTinyStateMachine.new(:initialized, self)
@automaton.permit_state :processing, :closing, :closed
@automaton.permit_transition :initialized => :processing, :processing => :closing
@automaton.permit_transition :closing => :closed
# Then, lower down the code
@automaton.transition! :processing
# This switches the internal state of the machine, and dispatches the following method
# calls on the object given as the second argument to the constructor, in the following order:
# self.leaving_initialized_state
# self.entering_processing_state
# self.transitioning_from_initialized_to_processing_state
# ..the state variable is switched here
# self.after_transitioning_from_initialized_to_processing_state
# self.after_leaving_initialized_state
# self.after_entering_processing_state
@automaton.transition :initialized # Will raise TinyStateMachine::InvalidFlow
@automaton.transition :something_odd # Will raise TinyStateMachine::UnknownState
@automaton.in_state?(:processing) #=> true
@automaton.in_state?(:initialized) #=> false
Constant Summary collapse
- VERSION =
'2.1.0'- InvalidFlow =
Gets raised when an impossible transition gets requested
Class.new(StandardError)
- UnknownState =
Gets raised when an unknown state gets requested
Class.new(StandardError)
Instance Method Summary collapse
-
#expect!(requisite_state) ⇒ TrueClass
Ensure the machine is in a given state, and if it isn’t raise an InvalidFlow.
-
#flow_so_far ⇒ Array
Returns the flow of the transitions the machine went through so far.
-
#in_state?(requisite_state) ⇒ Boolean
Tells whether the state machine is in a given state at the moment.
-
#initialize(initial_state, object_handling_callbacks = nil) ⇒ VeryTinyStateMachine
constructor
Initialize a new TinyStateMachine, with the initial state and the object that will receive callbacks.
-
#known?(state) ⇒ Boolean
Tells whether the state is known to this state machine.
-
#may_transition_to?(to_state) ⇒ Boolean
Tells whether a transition is permitted to the given state.
-
#permit_state(*states) ⇒ Set
Permit a single state or multiple states.
-
#permit_transition(from_to_hash) ⇒ Array
Permit a transition from one state to another.
-
#transition!(new_state) ⇒ Symbol
Transition to a given state.
-
#transition_or_maintain!(new_state) ⇒ void
Transition to a given state.
Constructor Details
#initialize(initial_state, object_handling_callbacks = nil) ⇒ VeryTinyStateMachine
Initialize a new TinyStateMachine, with the initial state and the object that will receive callbacks.
44 45 46 47 48 49 50 |
# File 'lib/very_tiny_state_machine.rb', line 44 def initialize(initial_state, object_handling_callbacks = nil) @state = initial_state.to_sym @flow = [@state] @permitted_states = Set.new([initial_state]) @permitted_transitions = Set.new @callbacks_via = object_handling_callbacks end |
Instance Method Details
#expect!(requisite_state) ⇒ TrueClass
Ensure the machine is in a given state, and if it isn’t raise an InvalidFlow
114 115 116 117 118 119 |
# File 'lib/very_tiny_state_machine.rb', line 114 def expect!(requisite_state) unless requisite_state.to_sym == @state raise InvalidFlow, "Must be in #{requisite_state.inspect} state, but was in #{@state.inspect}" end true end |
#flow_so_far ⇒ Array
Returns the flow of the transitions the machine went through so far
178 179 180 |
# File 'lib/very_tiny_state_machine.rb', line 178 def flow_so_far @flow.dup end |
#in_state?(requisite_state) ⇒ Boolean
Tells whether the state machine is in a given state at the moment
105 106 107 |
# File 'lib/very_tiny_state_machine.rb', line 105 def in_state?(requisite_state) @state == requisite_state.to_sym end |
#known?(state) ⇒ Boolean
Tells whether the state is known to this state machine
87 88 89 |
# File 'lib/very_tiny_state_machine.rb', line 87 def known?(state) @permitted_states.include?(state.to_sym) end |
#may_transition_to?(to_state) ⇒ Boolean
Tells whether a transition is permitted to the given state.
95 96 97 98 99 |
# File 'lib/very_tiny_state_machine.rb', line 95 def may_transition_to?(to_state) to_state = to_state.to_sym transition = {@state => to_state.to_sym} @permitted_states.include?(to_state) && @permitted_transitions.include?(transition) end |
#permit_state(*states) ⇒ Set
Permit a single state or multiple states
56 57 58 59 60 61 |
# File 'lib/very_tiny_state_machine.rb', line 56 def permit_state(*states) states_to_permit = Set.new(states.map(&:to_sym)) will_be_added = states_to_permit - @permitted_states @permitted_states += states_to_permit will_be_added end |
#permit_transition(from_to_hash) ⇒ Array
Permit a transition from one state to another. If you need to add multiple transitions from the same state, just call the method multiple times:
@machine.permit_transition :initialized => :failed, :running => :closed
@machine.permit_transition :initialized => :running
71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/very_tiny_state_machine.rb', line 71 def permit_transition(from_to_hash) transitions_to_permit = Set.new from_to_hash.each_pair do | from_state, to_state | raise UnknownState, from_state unless @permitted_states.include?(from_state.to_sym) raise UnknownState, to_state unless @permitted_states.include?(to_state.to_sym) transitions_to_permit << {from_state.to_sym => to_state.to_sym} end additions = transitions_to_permit - @permitted_transitions @permitted_transitions += transitions_to_permit additions end |
#transition!(new_state) ⇒ Symbol
Transition to a given state. Will raise an InvalidFlow exception if the transition is impossible. Additionally, if you want to transition to a state that is already activated, an InvalidFlow will be raised if you did not permit this transition explicitly. If you want to transition to a state OR stay in it if it is already active use TinyStateMachine#transition_or_maintain!
During transitions the before callbacks will be called on the @callbacks_via instance variable. If you are transitioning from “initialized” to “processing” for instance, the following callbacks will be dispatched:
-
leaving_initialized_state
-
entering_processing_state
-
transitioning_from_initialized_to_processing_state
..the state variable is switched here
-
after_transitioning_from_initialized_to_processing_state
-
after_leaving_initialized_state
-
after_entering_processing_state
The return value of the callbacks does not matter.
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/very_tiny_state_machine.rb', line 143 def transition!(new_state) new_state = new_state.to_sym raise UnknownState, new_state.inspect unless known?(new_state) if may_transition_to?(new_state) dispatch_callbacks_before_transition(new_state) if @callbacks_via previous = @state @state = new_state.to_sym @flow << new_state.to_sym dispatch_callbacks_after_transition(previous) if @callbacks_via previous else raise InvalidFlow, "Cannot change states from #{@state} to #{new_state} (flow so far: #{@flow.join(' > ')})" end end |
#transition_or_maintain!(new_state) ⇒ void
This method returns an undefined value.
Transition to a given state. If the machine already is in that state, do nothing. If the transition has to happen (the requested state is different than the current) transition! will be called instead.
170 171 172 173 |
# File 'lib/very_tiny_state_machine.rb', line 170 def transition_or_maintain!(new_state) return if in_state?(new_state) transition! new_state end |