class StateManager::Base

The base StateManager class is responsible for tracking the current state of an object as well as managing the transitions between states.

Attributes

context[RW]
current_event[RW]
resource[RW]

Public Class Methods

added_to_resource(klass, property, options) click to toggle source
# File lib/state_manager/base.rb, line 188
def self.added_to_resource(klass, property, options)
end
infer_resource_name!() click to toggle source
# File lib/state_manager/base.rb, line 175
def self.infer_resource_name!
  return if _resource_name
  if name =~ /States/
    self._resource_name = name.demodulize.gsub(/States/, '').underscore
    create_resource_accessor!(_resource_name)
  end
end
inherited(base) click to toggle source
Calls superclass method
# File lib/state_manager/base.rb, line 183
def self.inherited(base)
  super(base)
  base.infer_resource_name!
end
new(resource, context={}) click to toggle source
Calls superclass method
# File lib/state_manager/base.rb, line 18
def initialize(resource, context={})
  super(nil, nil)
  self.resource = resource
  self.context = context

  if perform_initial_transition?
    initial_path = current_state && current_state.path || initial_state.path
    transition_to initial_path, nil
  end
end
yaml_new(klass, tag, val) click to toggle source
# File lib/state_manager/serialization.rb, line 7
def self.yaml_new(klass, tag, val)
  klass.new(val['resource'], val['context'])
end

Public Instance Methods

around_event(event, *args) { || ... } click to toggle source
# File lib/state_manager/base.rb, line 160
def around_event(event, *args, &block)
  yield
end
available_events() click to toggle source

All events the current state will respond to

# File lib/state_manager/base.rb, line 165
def available_events
  state = current_state
  ret = {}
  while(state) do
    ret = state.class.specification.events.merge(ret)
    state = state.parent_state
  end
  ret
end
current_state() click to toggle source
# File lib/state_manager/base.rb, line 76
def current_state
  path = read_state
  find_state(path) if path && !path.empty?
end
current_state=(value) click to toggle source
# File lib/state_manager/base.rb, line 81
def current_state=(value)
  write_state(value)
end
did_transition(from, to, event) click to toggle source
# File lib/state_manager/base.rb, line 157
def did_transition(from, to, event)
end
encode_with(coder) click to toggle source
# File lib/state_manager/serialization.rb, line 19
def encode_with(coder)
  coder.map = {
    "resource" => resource,
    "context" => context
  }
end
find_state_for_event(name) click to toggle source
# File lib/state_manager/base.rb, line 107
def find_state_for_event(name)
  state = current_state
  while(state) do
    return state if state.has_event?(name)
    state = state.parent_state
  end
end
has_state?() click to toggle source

Will not have a state if the state is invalid or nil

# File lib/state_manager/base.rb, line 138
def has_state?
  !!current_state
end
in_state?(path) click to toggle source

Returns true if the underlying object is in the state specified by the given path. An object is 'in' a state if the state lies at any point of the current state's path. E.g:

state_manager.current_state.path # returns 'outer.inner'
state_manager.in_state? 'outer' # true
state_manager.in_state? 'outer.inner' # true
state_manager.in_state? 'inner' # false
# File lib/state_manager/base.rb, line 133
def in_state?(path)
  self.find_states(current_state.path).include? find_state(path)
end
init_with(coder) click to toggle source
# File lib/state_manager/serialization.rb, line 26
def init_with(coder)
  initialize(coder["resource"], coder["context"])
end
perform_initial_transition?() click to toggle source

In the case of a new model, we wan't to transition into the initial state and fire the appropriate callbacks. The default behavior is to just check if the state field is nil.

# File lib/state_manager/base.rb, line 32
def perform_initial_transition?
  !current_state
end
persist_state() click to toggle source
# File lib/state_manager/base.rb, line 151
def persist_state
end
read_state() click to toggle source
# File lib/state_manager/base.rb, line 147
def read_state
  resource.send self.class._state_property
end
respond_to_event?(name) click to toggle source
# File lib/state_manager/base.rb, line 103
def respond_to_event?(name)
  !!find_state_for_event(name)
end
send_event(name, *args) click to toggle source
# File lib/state_manager/base.rb, line 93
def send_event(name, *args)
  self.current_event = name
  state = find_state_for_event(name)

  raise(InvalidEvent, transition_error(name)) unless state
  result = state.perform_event name, *args
  self.current_event = nil
  result
end
send_event!(name, *args) click to toggle source
# File lib/state_manager/base.rb, line 85
def send_event!(name, *args)
  around_event(name, *args) do
    result = send_event(name, *args)
    persist_state
    result
  end
end
state_manager() click to toggle source
# File lib/state_manager/base.rb, line 115
def state_manager
  self
end
to_s() click to toggle source
# File lib/state_manager/base.rb, line 119
def to_s
  path = "#{current_state.path}" if current_state
  "#<%s:0x%x:%s>" % [self.class, object_id, path]
end
to_yaml_properties() click to toggle source
# File lib/state_manager/serialization.rb, line 11
def to_yaml_properties
  ['@resource', '@context']
end
transition_error(state) click to toggle source
# File lib/state_manager/base.rb, line 191
def transition_error(state)
  "Unable to transition from #{current_state} to #{state}"
end
transition_to(path, current_state=self.current_state) click to toggle source

Transitions to the state at the specified path. The path can be relative to any state along the current state's path.

# File lib/state_manager/base.rb, line 38
def transition_to(path, current_state=self.current_state)
  path = path.to_s
  state = current_state || self
  exit_states = []

  # Find the nearest parent state on the path of the current state which
  # has a sub-state at the given path
  new_states = state.find_states(path)
  while(!new_states) do
    exit_states << state
    state = state.parent_state
    raise(StateNotFound, transition_error(path)) unless state
    new_states = state.find_states(path)
  end

  # The first time we enter a state, the state_manager gets entered as well
  new_states.unshift(self) unless has_state?

  # Can only transition to leaf states
  # TODO: transition to the initial_state of the state?
  raise(InvalidTransition, transition_error(path)) unless new_states.last.leaf?

  enter_states = new_states - exit_states
  exit_states = exit_states - new_states

  from_state = current_state
  # TODO: does it make more sense to throw an error instead of allowing
  # a transition to the current state?
  to_state = enter_states.last || from_state

  run_before_callbacks(from_state, to_state, current_event, enter_states, exit_states)

  # Set the state on the underlying resource
  self.current_state = to_state

  run_after_callbacks(from_state, to_state, current_event, enter_states, exit_states)
end
will_transition(from, to, event) click to toggle source
# File lib/state_manager/base.rb, line 154
def will_transition(from, to, event)
end
write_state(value) click to toggle source

These methods can be overriden by an adapter

# File lib/state_manager/base.rb, line 143
def write_state(value)
  resource.send "#{self.class._state_property.to_s}=", value.path
end

Protected Instance Methods

run_after_callbacks(from_state, to_state, current_event, enter_states, exit_states) click to toggle source
# File lib/state_manager/base.rb, line 205
def run_after_callbacks(from_state, to_state, current_event, enter_states, exit_states)
  exit_states.each{ |s| s.exited }
  enter_states.each{ |s| s.entered }
  did_transition(from_state, to_state, current_event)
end
run_before_callbacks(from_state, to_state, current_event, enter_states, exit_states) click to toggle source
# File lib/state_manager/base.rb, line 199
def run_before_callbacks(from_state, to_state, current_event, enter_states, exit_states)
  will_transition(from_state, to_state, current_event)
  exit_states.each{ |s| s.exit }
  enter_states.each{ |s| s.enter }
end