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