module Statesman::Machine

The main module, that should be `extend`ed in to state machine classes.

Public Class Methods

included(base) click to toggle source
# File lib/statesman/machine.rb, line 12
def self.included(base)
  base.extend(ClassMethods)
  base.send(:attr_reader, :object)
end
new(object, options = { transition_class: Statesman::Adapters::MemoryTransition, }) click to toggle source
# File lib/statesman/machine.rb, line 181
def initialize(object,
               options = {
                 transition_class: Statesman::Adapters::MemoryTransition,
               })
  @object = object
  @transition_class = options[:transition_class]
  @storage_adapter = adapter_class(@transition_class).new(
    @transition_class, object, self, options
  )
  send(:after_initialize) if respond_to? :after_initialize
end
retry_conflicts(max_retries = 1) { || ... } click to toggle source

Retry any transitions that fail due to a TransitionConflictError

# File lib/statesman/machine.rb, line 18
def self.retry_conflicts(max_retries = 1)
  retry_attempt = 0

  begin
    yield
  rescue TransitionConflictError
    retry_attempt += 1
    retry_attempt <= max_retries ? retry : raise
  end
end

Public Instance Methods

allowed_transitions(metadata = {}) click to toggle source
# File lib/statesman/machine.rb, line 202
def allowed_transitions(metadata = {})
  successors_for(current_state).select do |state|
    can_transition_to?(state, metadata)
  end
end
can_transition_to?(new_state, metadata = {}) click to toggle source
# File lib/statesman/machine.rb, line 216
def can_transition_to?(new_state, metadata = {})
  validate_transition(from: current_state,
                      to: new_state,
                      metadata: metadata)
  true
rescue TransitionFailedError, GuardFailedError
  false
end
current_state(force_reload: false) click to toggle source
# File lib/statesman/machine.rb, line 193
def current_state(force_reload: false)
  last_action = last_transition(force_reload: force_reload)
  last_action ? last_action.to_state : self.class.initial_state
end
execute(phase, initial_state, new_state, transition) click to toggle source
# File lib/statesman/machine.rb, line 253
def execute(phase, initial_state, new_state, transition)
  callbacks = callbacks_for(phase, from: initial_state, to: new_state)
  callbacks.each { |cb| cb.call(@object, transition) }
end
execute_on_failure(phase, initial_state, new_state, exception) click to toggle source
# File lib/statesman/machine.rb, line 248
def execute_on_failure(phase, initial_state, new_state, exception)
  callbacks = callbacks_for(phase, from: initial_state, to: new_state)
  callbacks.each { |cb| cb.call(@object, exception) }
end
history() click to toggle source
# File lib/statesman/machine.rb, line 225
def history
  @storage_adapter.history
end
in_state?(*states) click to toggle source
# File lib/statesman/machine.rb, line 198
def in_state?(*states)
  states.flatten.any? { |state| current_state == state.to_s }
end
last_transition(force_reload: false) click to toggle source
# File lib/statesman/machine.rb, line 208
def last_transition(force_reload: false)
  @storage_adapter.last(force_reload: force_reload)
end
last_transition_to(state) click to toggle source
# File lib/statesman/machine.rb, line 212
def last_transition_to(state)
  history.reverse.find { |transition| transition.to_state.to_sym == state.to_sym }
end
reset() click to toggle source
# File lib/statesman/machine.rb, line 264
def reset
  @storage_adapter.reset
end
transition_to(new_state, metadata = {}) click to toggle source
# File lib/statesman/machine.rb, line 258
def transition_to(new_state, metadata = {})
  transition_to!(new_state, metadata)
rescue TransitionFailedError, GuardFailedError
  false
end
transition_to!(new_state, metadata = {}) click to toggle source
# File lib/statesman/machine.rb, line 229
def transition_to!(new_state, metadata = {})
  initial_state = current_state
  new_state = new_state.to_s

  validate_transition(from: initial_state,
                      to: new_state,
                      metadata: metadata)

  @storage_adapter.create(initial_state, new_state, metadata)

  true
rescue TransitionFailedError => e
  execute_on_failure(:after_transition_failure, initial_state, new_state, e)
  raise
rescue GuardFailedError => e
  execute_on_failure(:after_guard_failure, initial_state, new_state, e)
  raise
end

Private Instance Methods

adapter_class(transition_class) click to toggle source
# File lib/statesman/machine.rb, line 270
def adapter_class(transition_class)
  if transition_class == Statesman::Adapters::MemoryTransition
    Adapters::Memory
  else
    Statesman.storage_adapter
  end
end
callbacks_for(phase, options = { from: nil, to: nil }) click to toggle source
# File lib/statesman/machine.rb, line 286
def callbacks_for(phase, options = { from: nil, to: nil })
  select_callbacks_for(self.class.callbacks[phase], options)
end
guards_for(options = { from: nil, to: nil }) click to toggle source
# File lib/statesman/machine.rb, line 282
def guards_for(options = { from: nil, to: nil })
  select_callbacks_for(self.class.callbacks[:guards], options)
end
select_callbacks_for(callbacks, options = { from: nil, to: nil }) click to toggle source
# File lib/statesman/machine.rb, line 290
def select_callbacks_for(callbacks, options = { from: nil, to: nil })
  from = to_s_or_nil(options[:from])
  to   = to_s_or_nil(options[:to])
  callbacks.select { |callback| callback.applies_to?(from: from, to: to) }
end
successors_for(from) click to toggle source
# File lib/statesman/machine.rb, line 278
def successors_for(from)
  self.class.successors[from] || []
end
to_s_or_nil(input) click to toggle source
# File lib/statesman/machine.rb, line 309
def to_s_or_nil(input)
  input.nil? ? input : input.to_s
end
validate_transition(options = { from: nil, to: nil, metadata: nil }) click to toggle source
# File lib/statesman/machine.rb, line 296
def validate_transition(options = { from: nil, to: nil, metadata: nil })
  from = to_s_or_nil(options[:from])
  to   = to_s_or_nil(options[:to])

  successors = self.class.successors[from] || []
  raise TransitionFailedError.new(from, to) unless successors.include?(to)

  # Call all guards, they raise exceptions if they fail
  guards_for(from: from, to: to).each do |guard|
    guard.call(@object, last_transition, options[:metadata])
  end
end