state_machine 0.4.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.rdoc +17 -0
- data/LICENSE +1 -1
- data/README.rdoc +54 -84
- data/Rakefile +1 -1
- data/examples/Car_state.png +0 -0
- data/examples/Vehicle_state.png +0 -0
- data/examples/auto_shop.rb +11 -0
- data/examples/car.rb +19 -0
- data/examples/traffic_light.rb +9 -0
- data/examples/vehicle.rb +35 -0
- data/lib/state_machine.rb +65 -52
- data/lib/state_machine/assertions.rb +1 -1
- data/lib/state_machine/callback.rb +13 -9
- data/lib/state_machine/eval_helpers.rb +4 -3
- data/lib/state_machine/event.rb +51 -33
- data/lib/state_machine/extensions.rb +2 -2
- data/lib/state_machine/guard.rb +47 -41
- data/lib/state_machine/integrations.rb +67 -0
- data/lib/state_machine/integrations/active_record.rb +62 -36
- data/lib/state_machine/integrations/active_record/observer.rb +41 -0
- data/lib/state_machine/integrations/data_mapper.rb +23 -37
- data/lib/state_machine/integrations/data_mapper/observer.rb +23 -9
- data/lib/state_machine/integrations/sequel.rb +23 -24
- data/lib/state_machine/machine.rb +380 -277
- data/lib/state_machine/node_collection.rb +142 -0
- data/lib/state_machine/state.rb +114 -69
- data/lib/state_machine/state_collection.rb +38 -0
- data/lib/state_machine/transition.rb +36 -17
- data/test/active_record.log +2940 -85664
- data/test/functional/state_machine_test.rb +49 -53
- data/test/sequel.log +747 -11990
- data/test/unit/assertions_test.rb +2 -1
- data/test/unit/callback_test.rb +14 -12
- data/test/unit/eval_helpers_test.rb +25 -6
- data/test/unit/event_test.rb +144 -124
- data/test/unit/guard_test.rb +118 -140
- data/test/unit/integrations/active_record_test.rb +102 -68
- data/test/unit/integrations/data_mapper_test.rb +48 -37
- data/test/unit/integrations/sequel_test.rb +34 -25
- data/test/unit/integrations_test.rb +42 -0
- data/test/unit/machine_test.rb +460 -531
- data/test/unit/node_collection_test.rb +208 -0
- data/test/unit/state_collection_test.rb +167 -0
- data/test/unit/state_machine_test.rb +1 -1
- data/test/unit/state_test.rb +223 -200
- data/test/unit/transition_test.rb +81 -46
- metadata +17 -3
- data/test/data_mapper.log +0 -30860
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'state_machine/assertions'
|
2
|
+
|
3
|
+
module StateMachine
|
4
|
+
# Represents a collection of nodes in a state machine, be it events or states.
|
5
|
+
class NodeCollection
|
6
|
+
include Enumerable
|
7
|
+
include Assertions
|
8
|
+
|
9
|
+
# Creates a new collection of nodes for the given state machine. By default,
|
10
|
+
# the collection is empty.
|
11
|
+
#
|
12
|
+
# Configuration options:
|
13
|
+
# * <tt>:index</tt> - One or more attributes to automatically generate
|
14
|
+
# hashed indices for in order to perform quick lookups. Default is to
|
15
|
+
# index by the :name attribute
|
16
|
+
def initialize(options = {})
|
17
|
+
assert_valid_keys(options, :index)
|
18
|
+
options = {:index => :name}.merge(options)
|
19
|
+
|
20
|
+
@nodes = []
|
21
|
+
@indices = Array(options[:index]).inject({}) {|indices, attribute| indices[attribute] = {}; indices}
|
22
|
+
@default_index = Array(options[:index]).first
|
23
|
+
end
|
24
|
+
|
25
|
+
# Creates a copy of this collection such that modifications don't affect
|
26
|
+
# the original collection
|
27
|
+
def initialize_copy(orig) #:nodoc:
|
28
|
+
super
|
29
|
+
|
30
|
+
nodes = @nodes
|
31
|
+
@nodes = []
|
32
|
+
@indices = @indices.inject({}) {|indices, (name, index)| indices[name] = {}; indices}
|
33
|
+
nodes.each {|node| self << node.dup}
|
34
|
+
end
|
35
|
+
|
36
|
+
# Changes the current machine associated with the collection. In turn, this
|
37
|
+
# will change the state machine associated with each node in the collection.
|
38
|
+
def machine=(new_machine)
|
39
|
+
each {|node| node.machine = new_machine}
|
40
|
+
end
|
41
|
+
|
42
|
+
# Gets the number of nodes in this collection
|
43
|
+
def length
|
44
|
+
@nodes.length
|
45
|
+
end
|
46
|
+
|
47
|
+
# Gets the set of unique keys for the given index
|
48
|
+
def keys(index_name = @default_index)
|
49
|
+
index(index_name).keys
|
50
|
+
end
|
51
|
+
|
52
|
+
# Adds a new node to the collection. By doing so, this will also add it to
|
53
|
+
# the configured indices.
|
54
|
+
def <<(node)
|
55
|
+
@nodes << node
|
56
|
+
@indices.each {|attribute, index| index[node.send(attribute)] = node}
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
# Updates the indexed keys for the given node. If the node's attribute
|
61
|
+
# has changed since it was added to the collection, the old indexed keys
|
62
|
+
# will be replaced with the updated ones.
|
63
|
+
def update(node)
|
64
|
+
@indices.each do |attribute, index|
|
65
|
+
old_key = index.respond_to?(:key) ? index.key(node) : index.index(node)
|
66
|
+
new_key = node.send(attribute)
|
67
|
+
|
68
|
+
# Only replace the key if it's changed
|
69
|
+
if old_key != new_key
|
70
|
+
index.delete(old_key)
|
71
|
+
index[new_key] = node
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Calls the block once for each element in self, passing that element as a
|
77
|
+
# parameters.
|
78
|
+
#
|
79
|
+
# states = StateMachine::NodeCollection.new
|
80
|
+
# states << StateMachine::State.new(machine, :parked)
|
81
|
+
# states << StateMachine::State.new(machine, :idling)
|
82
|
+
# states.each {|state| puts state.name, ' -- '}
|
83
|
+
#
|
84
|
+
# ...produces:
|
85
|
+
#
|
86
|
+
# parked -- idling --
|
87
|
+
def each
|
88
|
+
@nodes.each {|node| yield node}
|
89
|
+
self
|
90
|
+
end
|
91
|
+
|
92
|
+
# Gets the node at the given index.
|
93
|
+
#
|
94
|
+
# states = StateMachine::NodeCollection.new
|
95
|
+
# states << StateMachine::State.new(machine, :parked)
|
96
|
+
# states << StateMachine::State.new(machine, :idling)
|
97
|
+
#
|
98
|
+
# states.at(0).name # => :parked
|
99
|
+
# states.at(1).name # => :idling
|
100
|
+
def at(index)
|
101
|
+
@nodes[index]
|
102
|
+
end
|
103
|
+
|
104
|
+
# Gets the node indexed by the given key. By default, this will look up the
|
105
|
+
# key in the first index configured for the collection. A custom index can
|
106
|
+
# be specified like so:
|
107
|
+
#
|
108
|
+
# collection['parked', :value]
|
109
|
+
#
|
110
|
+
# The above will look up the "parked" key in a hash indexed by each node's
|
111
|
+
# +value+ attribute.
|
112
|
+
#
|
113
|
+
# If the key cannot be found, then nil will be returned.
|
114
|
+
def [](key, index_name = @default_index)
|
115
|
+
index(index_name)[key]
|
116
|
+
end
|
117
|
+
|
118
|
+
# Gets the node indexed by the given key. By default, this will look up the
|
119
|
+
# key in the first index configured for the collection. A custom index can
|
120
|
+
# be specified like so:
|
121
|
+
#
|
122
|
+
# collection['parked', :value]
|
123
|
+
#
|
124
|
+
# The above will look up the "parked" key in a hash indexed by each node's
|
125
|
+
# +value+ attribute.
|
126
|
+
#
|
127
|
+
# If the key cannot be found, then an IndexError exception will be raised:
|
128
|
+
#
|
129
|
+
# collection['invalid', :value] # => IndexError: "invalid" is an invalid value
|
130
|
+
def fetch(key, index_name = @default_index)
|
131
|
+
self[key, index_name] || raise(ArgumentError, "#{key.inspect} is an invalid #{index_name}")
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
# Gets the given index. If the index does not exist, then an ArgumentError
|
136
|
+
# is raised.
|
137
|
+
def index(name)
|
138
|
+
raise ArgumentError, 'No indices configured' unless @indices.any?
|
139
|
+
@indices[name] || raise(ArgumentError, "Invalid index: #{name.inspect}")
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
data/lib/state_machine/state.rb
CHANGED
@@ -3,54 +3,55 @@ require 'state_machine/assertions'
|
|
3
3
|
module StateMachine
|
4
4
|
# A state defines a value that an attribute can be in after being transitioned
|
5
5
|
# 0 or more times. States can represent a value of any type in Ruby, though
|
6
|
-
# the most common type is String.
|
6
|
+
# the most common (and default) type is String.
|
7
7
|
#
|
8
|
-
# In addition to defining the machine's value,
|
8
|
+
# In addition to defining the machine's value, a state can also define a
|
9
9
|
# behavioral context for an object when that object is in the state. See
|
10
10
|
# StateMachine::Machine#state for more information about how state-driven
|
11
11
|
# behavior can be utilized.
|
12
12
|
class State
|
13
13
|
include Assertions
|
14
14
|
|
15
|
-
class << self
|
16
|
-
# Generates a unique identifier for the given state value. Ids are based
|
17
|
-
# on the object type:
|
18
|
-
# * Proc - "lambda#{object_id}"
|
19
|
-
# * NilClass - "nil"
|
20
|
-
# * Everything else - Stringified version of the object
|
21
|
-
def id_for(value)
|
22
|
-
case value
|
23
|
-
when Proc
|
24
|
-
"lambda#{value.object_id.abs}"
|
25
|
-
when nil
|
26
|
-
'nil'
|
27
|
-
else
|
28
|
-
value.to_s
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
15
|
# The state machine for which this state is defined
|
34
16
|
attr_accessor :machine
|
35
17
|
|
18
|
+
# The unique identifier for the state used in event and callback definitions
|
19
|
+
attr_reader :name
|
20
|
+
|
21
|
+
# The value that is written to a machine's attribute when an object
|
22
|
+
# transitions into this state
|
23
|
+
attr_writer :value
|
24
|
+
|
25
|
+
# Whether or not this state is the initial state to use for new objects
|
26
|
+
attr_accessor :initial
|
27
|
+
|
28
|
+
# A custom lambda block for determining whether a given value matches this
|
29
|
+
# state
|
30
|
+
attr_accessor :matcher
|
31
|
+
|
36
32
|
# Tracks all of the methods that have been defined for the machine's owner
|
37
33
|
# class when objects are in this state.
|
38
34
|
#
|
39
|
-
# Maps
|
35
|
+
# Maps :method_name => UnboundMethod
|
40
36
|
attr_reader :methods
|
41
37
|
|
42
|
-
#
|
43
|
-
attr_accessor :initial
|
44
|
-
|
45
|
-
# Creates a new state within the context of the given machine
|
38
|
+
# Creates a new state within the context of the given machine.
|
46
39
|
#
|
47
40
|
# Configuration options:
|
48
|
-
# *
|
49
|
-
|
50
|
-
|
41
|
+
# * <tt>:initial</tt> - Whether this state is the beginning state for the
|
42
|
+
# machine. Default is false.
|
43
|
+
# * <tt>:value</tt> - The value to store when an object transitions to this
|
44
|
+
# state. Default is the name (stringified).
|
45
|
+
# * <tt>:if</tt> - Determines whether a value matches this state
|
46
|
+
# (e.g. :value => lambda {Time.now}, :if => lambda {|state| !state.nil?}).
|
47
|
+
# By default, the configured value is matched.
|
48
|
+
def initialize(machine, name, options = {}) #:nodoc:
|
49
|
+
assert_valid_keys(options, :initial, :value, :if)
|
51
50
|
|
52
51
|
@machine = machine
|
53
|
-
@
|
52
|
+
@name = name
|
53
|
+
@value = options.include?(:value) ? options[:value] : name && name.to_s
|
54
|
+
@matcher = options[:if]
|
54
55
|
@methods = {}
|
55
56
|
@initial = options.include?(:initial) && options[:initial]
|
56
57
|
|
@@ -61,14 +62,53 @@ module StateMachine
|
|
61
62
|
# methods to prevent conflicts across different states.
|
62
63
|
def initialize_copy(orig) #:nodoc:
|
63
64
|
super
|
64
|
-
@methods =
|
65
|
+
@methods = methods.dup
|
65
66
|
end
|
66
67
|
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
|
71
|
-
|
68
|
+
# Generates a human-readable description of this state's name / value:
|
69
|
+
#
|
70
|
+
# For example,
|
71
|
+
#
|
72
|
+
# State.new(machine, :parked).description # => "parked"
|
73
|
+
# State.new(machine, :parked, :value => :parked).description # => "parked"
|
74
|
+
# State.new(machine, :parked, :value => nil).description # => "parked (nil)"
|
75
|
+
# State.new(machine, :parked, :value => 1).description # => "parked (1)"
|
76
|
+
# State.new(machine, :parked, :value => lambda {Time.now}).description # => "parked (*)
|
77
|
+
def description
|
78
|
+
description = name ? name.to_s : name.inspect
|
79
|
+
description << " (#{@value.is_a?(Proc) ? '*' : @value.inspect})" unless name.to_s == @value.to_s
|
80
|
+
description
|
81
|
+
end
|
82
|
+
|
83
|
+
# The value that represents this state. If the value is a lambda block,
|
84
|
+
# then it will be evaluated at this time. Otherwise, the static value is
|
85
|
+
# returned.
|
86
|
+
#
|
87
|
+
# For example,
|
88
|
+
#
|
89
|
+
# State.new(machine, :parked, :value => 1).value # => 1
|
90
|
+
# State.new(machine, :parked, :value => lambda {Time.now}).value # => Tue Jan 01 00:00:00 UTC 2008
|
91
|
+
def value
|
92
|
+
@value.is_a?(Proc) ? @value.call : @value
|
93
|
+
end
|
94
|
+
|
95
|
+
# Determines whether this state matches the given value. If no matcher is
|
96
|
+
# configured, then this will check whether the values are equivalent.
|
97
|
+
# Otherwise, the matcher will determine the result.
|
98
|
+
#
|
99
|
+
# For example,
|
100
|
+
#
|
101
|
+
# # Without a matcher
|
102
|
+
# state = State.new(machine, :parked, :value => 1)
|
103
|
+
# state.matches?(1) # => true
|
104
|
+
# state.matches?(2) # => false
|
105
|
+
#
|
106
|
+
# # With a matcher
|
107
|
+
# state = State.new(machine, :parked, :value => lambda {Time.now}, :if => lambda {|value| !value.nil?})
|
108
|
+
# state.matches?(nil) # => false
|
109
|
+
# state.matches?(Time.now) # => true
|
110
|
+
def matches?(other_value)
|
111
|
+
matcher ? matcher.call(other_value) : other_value == value
|
72
112
|
end
|
73
113
|
|
74
114
|
# Defines a context for the state which will be enabled on instances of the
|
@@ -77,7 +117,6 @@ module StateMachine
|
|
77
117
|
# This can be called multiple times. Each time a new context is created, a
|
78
118
|
# new module will be included in the owner class.
|
79
119
|
def context(&block)
|
80
|
-
value = self.value
|
81
120
|
owner_class = machine.owner_class
|
82
121
|
attribute = machine.attribute
|
83
122
|
|
@@ -95,15 +134,14 @@ module StateMachine
|
|
95
134
|
# not possible with lambdas in Ruby 1.8.6.
|
96
135
|
owner_class.class_eval <<-end_eval, __FILE__, __LINE__
|
97
136
|
def #{method}(*args, &block)
|
98
|
-
attribute
|
99
|
-
self.class.state_machines[attribute].state(send(attribute)).call(self, #{method.dump}, *args, &block)
|
137
|
+
self.class.state_machines[#{attribute.inspect}].state_for(self).call(self, #{method.inspect}, *args, &block)
|
100
138
|
end
|
101
139
|
end_eval
|
102
140
|
end
|
103
141
|
|
104
142
|
# Track the method defined for the context so that it can be invoked
|
105
143
|
# at a later point in time
|
106
|
-
methods[method] = context.instance_method(method)
|
144
|
+
methods[method.to_sym] = context.instance_method(method)
|
107
145
|
end
|
108
146
|
|
109
147
|
# Include the context so that it can be bound to the owner class (the
|
@@ -112,7 +150,7 @@ module StateMachine
|
|
112
150
|
include context
|
113
151
|
end
|
114
152
|
|
115
|
-
|
153
|
+
context
|
116
154
|
end
|
117
155
|
|
118
156
|
# Calls a method defined in this state's context on the given object. All
|
@@ -121,53 +159,60 @@ module StateMachine
|
|
121
159
|
# If the method has never been defined for this state, then a NoMethodError
|
122
160
|
# will be raised.
|
123
161
|
def call(object, method, *args, &block)
|
124
|
-
if context_method = methods[method.
|
162
|
+
if context_method = methods[method.to_sym]
|
125
163
|
# Method is defined by the state: proxy it through
|
126
164
|
context_method.bind(object).call(*args, &block)
|
127
165
|
else
|
128
166
|
# Raise exception as if the method never existed on the original object
|
129
|
-
raise NoMethodError, "undefined method '#{method}' for #{object} in state #{
|
167
|
+
raise NoMethodError, "undefined method '#{method}' for #{object} in state #{machine.state_for(object).name.inspect}"
|
130
168
|
end
|
131
169
|
end
|
132
170
|
|
133
171
|
# Draws a representation of this state on the given machine. This will
|
134
172
|
# create a new node on the graph with the following properties:
|
135
|
-
# * +label+ -
|
173
|
+
# * +label+ - The human-friendly description of the state.
|
136
174
|
# * +width+ - The width of the node. Always 1.
|
137
175
|
# * +height+ - The height of the node. Always 1.
|
138
|
-
# * +fixedsize+ - Whether the size of the node stays the same regardless of
|
139
|
-
#
|
176
|
+
# * +fixedsize+ - Whether the size of the node stays the same regardless of
|
177
|
+
# its contents. Always true.
|
178
|
+
# * +shape+ - The actual shape of the node. If the state is the initial
|
179
|
+
# state, then "doublecircle", otherwise "circle".
|
140
180
|
#
|
141
181
|
# The actual node generated on the graph will be returned.
|
142
182
|
def draw(graph)
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
183
|
+
graph.add_node(name ? name.to_s : 'nil',
|
184
|
+
:label => description,
|
185
|
+
:width => '1',
|
186
|
+
:height => '1',
|
187
|
+
:fixedsize => 'true',
|
188
|
+
:shape => initial ? 'doublecircle' : 'circle'
|
189
|
+
)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Generates a nicely formatted description of this state's contents.
|
193
|
+
#
|
194
|
+
# For example,
|
195
|
+
#
|
196
|
+
# state = StateMachine::State.new(machine, :parked, :value => 1, :initial => true)
|
197
|
+
# state # => #<StateMachine::State name=:parked value=1 initial=true>
|
198
|
+
def inspect
|
199
|
+
"#<#{self.class} #{%w(name value initial).map {|attr| "#{attr}=#{instance_variable_get("@#{attr}").inspect}"} * ' '}>"
|
154
200
|
end
|
155
201
|
|
156
202
|
private
|
157
|
-
# Adds a predicate method to the owner class
|
158
|
-
#
|
203
|
+
# Adds a predicate method to the owner class so long as a name has
|
204
|
+
# actually been configured for the state
|
159
205
|
def add_predicate
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
end unless method_defined?(name) || private_method_defined?(name)
|
206
|
+
return unless name
|
207
|
+
|
208
|
+
attribute = machine.attribute
|
209
|
+
qualified_name = name = self.name
|
210
|
+
qualified_name = "#{machine.namespace}_#{name}" if machine.namespace
|
211
|
+
|
212
|
+
machine.owner_class.class_eval do
|
213
|
+
# Checks whether the current value matches this state
|
214
|
+
define_method("#{qualified_name}?") do
|
215
|
+
self.class.state_machines[attribute].state(name).matches?(send(attribute))
|
171
216
|
end
|
172
217
|
end
|
173
218
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'state_machine/node_collection'
|
2
|
+
|
3
|
+
module StateMachine
|
4
|
+
# Represents a collection of states in a state machine
|
5
|
+
class StateCollection < NodeCollection
|
6
|
+
def initialize #:nodoc:
|
7
|
+
super(:index => [:name, :value])
|
8
|
+
end
|
9
|
+
|
10
|
+
# Gets the order in which states should be displayed based on where they
|
11
|
+
# were first referenced. This will order states in the following priority:
|
12
|
+
#
|
13
|
+
# 1. Initial state
|
14
|
+
# 2. Event transitions (:from, :except_from, :to, :except_to options)
|
15
|
+
# 3. States with behaviors
|
16
|
+
# 4. States referenced via +state+ or +other_states+
|
17
|
+
# 5. States referenced in callbacks
|
18
|
+
#
|
19
|
+
# This order will determine how the GraphViz visualizations are rendered.
|
20
|
+
def by_priority
|
21
|
+
if first = @nodes.first
|
22
|
+
machine = first.machine
|
23
|
+
order = select {|state| state.initial}.map {|state| state.name}
|
24
|
+
|
25
|
+
machine.events.each {|event| order += event.known_states}
|
26
|
+
order += select {|state| state.methods.any?}.map {|state| state.name}
|
27
|
+
order += keys(:name) - machine.callbacks.values.flatten.map {|callback| callback.known_states}.flatten
|
28
|
+
order += keys(:name)
|
29
|
+
|
30
|
+
order.uniq!
|
31
|
+
order.map! {|name| self[name]}
|
32
|
+
order
|
33
|
+
else
|
34
|
+
[]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -4,6 +4,7 @@ module StateMachine
|
|
4
4
|
end
|
5
5
|
|
6
6
|
# A transition represents a state change for a specific attribute.
|
7
|
+
#
|
7
8
|
# Transitions consist of:
|
8
9
|
# * An event
|
9
10
|
# * A starting state
|
@@ -18,19 +19,27 @@ module StateMachine
|
|
18
19
|
# The event that caused the transition
|
19
20
|
attr_reader :event
|
20
21
|
|
21
|
-
# The original state *before* the transition
|
22
|
+
# The original state value *before* the transition
|
22
23
|
attr_reader :from
|
23
24
|
|
24
|
-
# The
|
25
|
+
# The original state name *before* the transition
|
26
|
+
attr_reader :from_name
|
27
|
+
|
28
|
+
# The new state value *after* the transition
|
25
29
|
attr_reader :to
|
26
30
|
|
31
|
+
# The new state name *after* the transition
|
32
|
+
attr_reader :to_name
|
33
|
+
|
27
34
|
# Creates a new, specific transition
|
28
|
-
def initialize(object, machine, event,
|
35
|
+
def initialize(object, machine, event, from_name, to_name) #:nodoc:
|
29
36
|
@object = object
|
30
37
|
@machine = machine
|
31
38
|
@event = event
|
32
|
-
@from =
|
33
|
-
@
|
39
|
+
@from = object.send(machine.attribute)
|
40
|
+
@from_name = from_name
|
41
|
+
@to = machine.states[to_name].value
|
42
|
+
@to_name = to_name
|
34
43
|
end
|
35
44
|
|
36
45
|
# Gets the attribute which this transition's machine is defined for
|
@@ -38,14 +47,14 @@ module StateMachine
|
|
38
47
|
machine.attribute
|
39
48
|
end
|
40
49
|
|
41
|
-
# Gets a hash of all the attributes defined for this transition with
|
42
|
-
# names as keys and values of the attributes as values.
|
50
|
+
# Gets a hash of all the core attributes defined for this transition with
|
51
|
+
# their names as keys and values of the attributes as values.
|
43
52
|
#
|
44
53
|
# == Example
|
45
54
|
#
|
46
|
-
# machine = StateMachine.new(
|
47
|
-
# transition = StateMachine::Transition.new(
|
48
|
-
# transition.attributes # => {:object => #<
|
55
|
+
# machine = StateMachine.new(Vehicle)
|
56
|
+
# transition = StateMachine::Transition.new(Vehicle.new, machine, :ignite, :parked, :idling)
|
57
|
+
# transition.attributes # => {:object => #<Vehicle:0xb7d60ea4>, :attribute => :state, :event => :ignite, :from => 'parked', :to => 'idling'}
|
49
58
|
def attributes
|
50
59
|
@attributes ||= {:object => object, :attribute => attribute, :event => event, :from => from, :to => to}
|
51
60
|
end
|
@@ -63,9 +72,9 @@ module StateMachine
|
|
63
72
|
# end
|
64
73
|
#
|
65
74
|
# vehicle = Vehicle.new
|
66
|
-
# transition = StateMachine::Transition.new(vehicle, machine, :
|
67
|
-
# transition.perform # => Runs the +save+ action after
|
68
|
-
# transition.perform(false) # => Only
|
75
|
+
# transition = StateMachine::Transition.new(vehicle, machine, :ignite, :parked, :idling)
|
76
|
+
# transition.perform # => Runs the +save+ action after setting the state attribute
|
77
|
+
# transition.perform(false) # => Only sets the state attribute
|
69
78
|
def perform(run_action = true)
|
70
79
|
result = false
|
71
80
|
|
@@ -91,17 +100,27 @@ module StateMachine
|
|
91
100
|
result
|
92
101
|
end
|
93
102
|
|
103
|
+
# Generates a nicely formatted description of this transitions's contents.
|
104
|
+
#
|
105
|
+
# For example,
|
106
|
+
#
|
107
|
+
# transition = StateMachine::Transition.new(object, machine, :ignite, :parked, :idling)
|
108
|
+
# transition # => #<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>
|
109
|
+
def inspect
|
110
|
+
"#<#{self.class} #{%w(attribute event from from_name to to_name).map {|attr| "#{attr}=#{send(attr).inspect}"} * ' '}>"
|
111
|
+
end
|
112
|
+
|
94
113
|
protected
|
95
114
|
# Gets a hash of the context defining this unique transition (including
|
96
115
|
# event, from state, and to state).
|
97
116
|
#
|
98
117
|
# == Example
|
99
118
|
#
|
100
|
-
# machine = StateMachine.new(
|
101
|
-
# transition = StateMachine::Transition.new(
|
102
|
-
# transition.context # => {:on =>
|
119
|
+
# machine = StateMachine.new(Vehicle)
|
120
|
+
# transition = StateMachine::Transition.new(Vehicle.new, machine, :ignite, :parked, :idling)
|
121
|
+
# transition.context # => {:on => :ignite, :from => :parked, :to => :idling}
|
103
122
|
def context
|
104
|
-
@context ||= {:on => event, :from =>
|
123
|
+
@context ||= {:on => event, :from => from_name, :to => to_name}
|
105
124
|
end
|
106
125
|
|
107
126
|
# Runs the callbacks of the given type for this transition. This will
|