Class: StateManager::Base

Inherits:
State
  • Object
show all
Extended by:
DSL::Base
Defined in:
lib/state_manager/base.rb,
lib/state_manager/dsl.rb,
lib/state_manager/serialization.rb,
lib/state_manager/serialization.rb

Overview

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

Instance Attribute Summary collapse

Attributes inherited from State

#name, #parent_state, #states

Class Method Summary collapse

Instance Method Summary collapse

Methods included from DSL::Base

resource_class, resource_name, state_property

Methods inherited from State

create_resource_accessor!, #enter, #entered, #exit, #exited, #find_state, #find_states, #has_event?, #initial_state, #leaf?, #path, #perform_event, #to_sym

Methods included from DSL::State

#event, #initial_state, #state

Methods included from DelayedJob::State

#delayed_events, #entered, #exited

Constructor Details

#initialize(resource, context = {}) ⇒ Base

Returns a new instance of Base.



18
19
20
21
22
23
24
25
26
27
# 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

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class StateManager::State

Instance Attribute Details

#context ⇒ Object

Returns the value of attribute context.



16
17
18
# File 'lib/state_manager/base.rb', line 16

def context
  @context
end

#resource ⇒ Object

Returns the value of attribute resource.



16
17
18
# File 'lib/state_manager/base.rb', line 16

def resource
  @resource
end

Class Method Details

.added_to_resource(klass, property, options) ⇒ Object



181
182
# File 'lib/state_manager/base.rb', line 181

def self.added_to_resource(klass, property, options)
end

.infer_resource_name! ⇒ Object



168
169
170
171
172
173
174
# File 'lib/state_manager/base.rb', line 168

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) ⇒ Object



176
177
178
179
# File 'lib/state_manager/base.rb', line 176

def self.inherited(base)
  super(base)
  base.infer_resource_name!
end

.yaml_new(klass, tag, val) ⇒ Object



7
8
9
# File 'lib/state_manager/serialization.rb', line 7

def self.yaml_new(klass, tag, val)
  klass.new(val['resource'], val['context'])
end

Instance Method Details

#available_events ⇒ Object

All events the current state will respond to



158
159
160
161
162
163
164
165
166
# File 'lib/state_manager/base.rb', line 158

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 ⇒ Object



76
77
78
79
# 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) ⇒ Object



81
82
83
# File 'lib/state_manager/base.rb', line 81

def current_state=(value)
  write_state(value)
end

#did_transition(from, to, event) ⇒ Object



154
155
# File 'lib/state_manager/base.rb', line 154

def did_transition(from, to, event)
end

#encode_with(coder) ⇒ Object



19
20
21
22
23
24
# File 'lib/state_manager/serialization.rb', line 19

def encode_with(coder)
  coder.map = {
    "resource" => resource,
    "context" => context
  }
end

#find_state_for_event(name) ⇒ Object



104
105
106
107
108
109
110
# File 'lib/state_manager/base.rb', line 104

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? ⇒ Boolean

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

Returns:

  • (Boolean)


135
136
137
# File 'lib/state_manager/base.rb', line 135

def has_state?
  !!current_state
end

#in_state?(path) ⇒ Boolean

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

Returns:

  • (Boolean)


130
131
132
# File 'lib/state_manager/base.rb', line 130

def in_state?(path)
  self.find_states(current_state.path).include? find_state(path) 
end

#init_with(coder) ⇒ Object



26
27
28
# File 'lib/state_manager/serialization.rb', line 26

def init_with(coder)
  initialize(coder["resource"], coder["context"])
end

#perform_initial_transition? ⇒ Boolean

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.

Returns:

  • (Boolean)


32
33
34
# File 'lib/state_manager/base.rb', line 32

def perform_initial_transition?
  !current_state
end

#persist_state ⇒ Object



148
149
# File 'lib/state_manager/base.rb', line 148

def persist_state
end

#read_state ⇒ Object



144
145
146
# File 'lib/state_manager/base.rb', line 144

def read_state
  resource.send self.class._state_property
end

#respond_to_event?(name) ⇒ Boolean

Returns:

  • (Boolean)


100
101
102
# File 'lib/state_manager/base.rb', line 100

def respond_to_event?(name)
  !!find_state_for_event(name)
end

#send_event(name, *args) ⇒ Object

Raises:



91
92
93
94
95
96
97
98
# File 'lib/state_manager/base.rb', line 91

def send_event(name, *args)
  self.current_event = name
  state = find_state_for_event(name)
  raise(InvalidEvent, name) unless state
  result = state.perform_event name, *args
  self.current_event = nil
  result
end

#send_event!(name, *args) ⇒ Object



85
86
87
88
89
# File 'lib/state_manager/base.rb', line 85

def send_event!(name, *args)
  result = send_event(name, *args)
  persist_state
  result
end

#state_manager ⇒ Object



112
113
114
# File 'lib/state_manager/base.rb', line 112

def state_manager
  self
end

#to_s ⇒ Object



116
117
118
119
# File 'lib/state_manager/base.rb', line 116

def to_s
  path = "#{current_state.path}" if current_state
  "#<%s:0x%x:%s>" % [self.class, object_id, path]
end

#to_yaml_properties ⇒ Object



11
12
13
# File 'lib/state_manager/serialization.rb', line 11

def to_yaml_properties
  ['@resource', '@context']
end

#transition_to(path, current_state = self.current_state) ⇒ Object

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

Raises:



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# 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, 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, 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) ⇒ Object



151
152
# File 'lib/state_manager/base.rb', line 151

def will_transition(from, to, event)
end

#write_state(value) ⇒ Object

These methods can be overriden by an adapter



140
141
142
# File 'lib/state_manager/base.rb', line 140

def write_state(value)
  resource.send "#{self.class._state_property.to_s}=", value.path
end