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

#contextObject

Returns the value of attribute context.



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

def context
  @context
end

#resourceObject

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



188
189
# File 'lib/state_manager/base.rb', line 188

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

.infer_resource_name!Object



175
176
177
178
179
180
181
# 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) ⇒ Object



183
184
185
186
# File 'lib/state_manager/base.rb', line 183

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

#around_event(event, *args, &block) ⇒ Object



160
161
162
# File 'lib/state_manager/base.rb', line 160

def around_event(event, *args, &block)
  yield
end

#available_eventsObject

All events the current state will respond to



165
166
167
168
169
170
171
172
173
# 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_stateObject



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



157
158
# File 'lib/state_manager/base.rb', line 157

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



107
108
109
110
111
112
113
# 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?Boolean

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

Returns:

  • (Boolean)


138
139
140
# File 'lib/state_manager/base.rb', line 138

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)


133
134
135
# 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) ⇒ 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_stateObject



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

def persist_state
end

#read_stateObject



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

def read_state
  resource.send self.class._state_property
end

#respond_to_event?(name) ⇒ Boolean

Returns:

  • (Boolean)


103
104
105
# File 'lib/state_manager/base.rb', line 103

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

#send_event(name, *args) ⇒ Object

Raises:



93
94
95
96
97
98
99
100
101
# 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) ⇒ Object



85
86
87
88
89
90
91
# 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_managerObject



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

def state_manager
  self
end

#to_sObject



119
120
121
122
# 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_propertiesObject



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

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

#transition_error(state) ⇒ Object



191
192
193
# 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) ⇒ 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, 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) ⇒ Object



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

def will_transition(from, to, event)
end

#write_state(value) ⇒ Object

These methods can be overriden by an adapter



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

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