hsume2-state_machine 1.0.1
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 +413 -0
- data/LICENSE +20 -0
- data/README.rdoc +717 -0
- data/Rakefile +77 -0
- data/examples/AutoShop_state.png +0 -0
- data/examples/Car_state.png +0 -0
- data/examples/TrafficLight_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/merb-rest/controller.rb +51 -0
- data/examples/merb-rest/model.rb +28 -0
- data/examples/merb-rest/view_edit.html.erb +24 -0
- data/examples/merb-rest/view_index.html.erb +23 -0
- data/examples/merb-rest/view_new.html.erb +13 -0
- data/examples/merb-rest/view_show.html.erb +17 -0
- data/examples/rails-rest/controller.rb +43 -0
- data/examples/rails-rest/migration.rb +11 -0
- data/examples/rails-rest/model.rb +23 -0
- data/examples/rails-rest/view_edit.html.erb +25 -0
- data/examples/rails-rest/view_index.html.erb +23 -0
- data/examples/rails-rest/view_new.html.erb +14 -0
- data/examples/rails-rest/view_show.html.erb +17 -0
- data/examples/traffic_light.rb +7 -0
- data/examples/vehicle.rb +31 -0
- data/init.rb +1 -0
- data/lib/state_machine.rb +448 -0
- data/lib/state_machine/alternate_machine.rb +79 -0
- data/lib/state_machine/assertions.rb +36 -0
- data/lib/state_machine/branch.rb +224 -0
- data/lib/state_machine/callback.rb +236 -0
- data/lib/state_machine/condition_proxy.rb +94 -0
- data/lib/state_machine/error.rb +13 -0
- data/lib/state_machine/eval_helpers.rb +86 -0
- data/lib/state_machine/event.rb +304 -0
- data/lib/state_machine/event_collection.rb +139 -0
- data/lib/state_machine/extensions.rb +149 -0
- data/lib/state_machine/initializers.rb +4 -0
- data/lib/state_machine/initializers/merb.rb +1 -0
- data/lib/state_machine/initializers/rails.rb +25 -0
- data/lib/state_machine/integrations.rb +110 -0
- data/lib/state_machine/integrations/active_model.rb +502 -0
- data/lib/state_machine/integrations/active_model/locale.rb +11 -0
- data/lib/state_machine/integrations/active_model/observer.rb +45 -0
- data/lib/state_machine/integrations/active_model/versions.rb +31 -0
- data/lib/state_machine/integrations/active_record.rb +424 -0
- data/lib/state_machine/integrations/active_record/locale.rb +20 -0
- data/lib/state_machine/integrations/active_record/versions.rb +143 -0
- data/lib/state_machine/integrations/base.rb +91 -0
- data/lib/state_machine/integrations/data_mapper.rb +392 -0
- data/lib/state_machine/integrations/data_mapper/observer.rb +210 -0
- data/lib/state_machine/integrations/data_mapper/versions.rb +62 -0
- data/lib/state_machine/integrations/mongo_mapper.rb +272 -0
- data/lib/state_machine/integrations/mongo_mapper/locale.rb +4 -0
- data/lib/state_machine/integrations/mongo_mapper/versions.rb +110 -0
- data/lib/state_machine/integrations/mongoid.rb +357 -0
- data/lib/state_machine/integrations/mongoid/locale.rb +4 -0
- data/lib/state_machine/integrations/mongoid/versions.rb +18 -0
- data/lib/state_machine/integrations/sequel.rb +428 -0
- data/lib/state_machine/integrations/sequel/versions.rb +36 -0
- data/lib/state_machine/machine.rb +1873 -0
- data/lib/state_machine/machine_collection.rb +87 -0
- data/lib/state_machine/matcher.rb +123 -0
- data/lib/state_machine/matcher_helpers.rb +54 -0
- data/lib/state_machine/node_collection.rb +157 -0
- data/lib/state_machine/path.rb +120 -0
- data/lib/state_machine/path_collection.rb +90 -0
- data/lib/state_machine/state.rb +271 -0
- data/lib/state_machine/state_collection.rb +112 -0
- data/lib/state_machine/transition.rb +458 -0
- data/lib/state_machine/transition_collection.rb +244 -0
- data/lib/tasks/state_machine.rake +1 -0
- data/lib/tasks/state_machine.rb +27 -0
- data/test/files/en.yml +17 -0
- data/test/files/switch.rb +11 -0
- data/test/functional/alternate_state_machine_test.rb +122 -0
- data/test/functional/state_machine_test.rb +993 -0
- data/test/test_helper.rb +4 -0
- data/test/unit/assertions_test.rb +40 -0
- data/test/unit/branch_test.rb +890 -0
- data/test/unit/callback_test.rb +701 -0
- data/test/unit/condition_proxy_test.rb +328 -0
- data/test/unit/error_test.rb +43 -0
- data/test/unit/eval_helpers_test.rb +222 -0
- data/test/unit/event_collection_test.rb +358 -0
- data/test/unit/event_test.rb +985 -0
- data/test/unit/integrations/active_model_test.rb +1097 -0
- data/test/unit/integrations/active_record_test.rb +2021 -0
- data/test/unit/integrations/base_test.rb +99 -0
- data/test/unit/integrations/data_mapper_test.rb +1909 -0
- data/test/unit/integrations/mongo_mapper_test.rb +1611 -0
- data/test/unit/integrations/mongoid_test.rb +1591 -0
- data/test/unit/integrations/sequel_test.rb +1523 -0
- data/test/unit/integrations_test.rb +61 -0
- data/test/unit/invalid_event_test.rb +20 -0
- data/test/unit/invalid_parallel_transition_test.rb +18 -0
- data/test/unit/invalid_transition_test.rb +77 -0
- data/test/unit/machine_collection_test.rb +599 -0
- data/test/unit/machine_test.rb +3043 -0
- data/test/unit/matcher_helpers_test.rb +37 -0
- data/test/unit/matcher_test.rb +155 -0
- data/test/unit/node_collection_test.rb +217 -0
- data/test/unit/path_collection_test.rb +266 -0
- data/test/unit/path_test.rb +485 -0
- data/test/unit/state_collection_test.rb +310 -0
- data/test/unit/state_machine_test.rb +31 -0
- data/test/unit/state_test.rb +924 -0
- data/test/unit/transition_collection_test.rb +2102 -0
- data/test/unit/transition_test.rb +1541 -0
- metadata +207 -0
@@ -0,0 +1,458 @@
|
|
1
|
+
require 'state_machine/transition_collection'
|
2
|
+
require 'state_machine/error'
|
3
|
+
|
4
|
+
module StateMachine
|
5
|
+
# An invalid transition was attempted
|
6
|
+
class InvalidTransition < Error
|
7
|
+
# The machine attempting to be transitioned
|
8
|
+
attr_reader :machine
|
9
|
+
|
10
|
+
# The current state value for the machine
|
11
|
+
attr_reader :from
|
12
|
+
|
13
|
+
def initialize(object, machine, event) #:nodoc:
|
14
|
+
@machine = machine
|
15
|
+
@from_state = machine.states.match!(object)
|
16
|
+
@from = machine.read(object, :state)
|
17
|
+
@event = machine.events.fetch(event)
|
18
|
+
|
19
|
+
super(object, "Cannot transition #{machine.name} via :#{self.event} from #{from_name.inspect}")
|
20
|
+
end
|
21
|
+
|
22
|
+
# The event that triggered the failed transition
|
23
|
+
def event
|
24
|
+
@event.name
|
25
|
+
end
|
26
|
+
|
27
|
+
# The fully-qualified name of the event that triggered the failed transition
|
28
|
+
def qualified_event
|
29
|
+
@event.qualified_name
|
30
|
+
end
|
31
|
+
|
32
|
+
# The name for the current state
|
33
|
+
def from_name
|
34
|
+
@from_state.name
|
35
|
+
end
|
36
|
+
|
37
|
+
# The fully-qualified name for the current state
|
38
|
+
def qualified_from_name
|
39
|
+
@from_state.qualified_name
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# A set of transition failed to run in parallel
|
44
|
+
class InvalidParallelTransition < Error
|
45
|
+
# The set of events that failed the transition(s)
|
46
|
+
attr_reader :events
|
47
|
+
|
48
|
+
def initialize(object, events) #:nodoc:
|
49
|
+
@events = events
|
50
|
+
|
51
|
+
super(object, "Cannot run events in parallel: #{events * ', '}")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# A transition represents a state change for a specific attribute.
|
56
|
+
#
|
57
|
+
# Transitions consist of:
|
58
|
+
# * An event
|
59
|
+
# * A starting state
|
60
|
+
# * An ending state
|
61
|
+
class Transition
|
62
|
+
# The object being transitioned
|
63
|
+
attr_reader :object
|
64
|
+
|
65
|
+
# The state machine for which this transition is defined
|
66
|
+
attr_reader :machine
|
67
|
+
|
68
|
+
# The original state value *before* the transition
|
69
|
+
attr_reader :from
|
70
|
+
|
71
|
+
# The new state value *after* the transition
|
72
|
+
attr_reader :to
|
73
|
+
|
74
|
+
# The arguments passed in to the event that triggered the transition
|
75
|
+
# (does not include the +run_action+ boolean argument if specified)
|
76
|
+
attr_accessor :args
|
77
|
+
|
78
|
+
# The result of invoking the action associated with the machine
|
79
|
+
attr_reader :result
|
80
|
+
|
81
|
+
# Whether the transition is only existing temporarily for the object
|
82
|
+
attr_writer :transient
|
83
|
+
|
84
|
+
# Creates a new, specific transition
|
85
|
+
def initialize(object, machine, event, from_name, to_name, read_state = true) #:nodoc:
|
86
|
+
@object = object
|
87
|
+
@machine = machine
|
88
|
+
@args = []
|
89
|
+
@transient = false
|
90
|
+
|
91
|
+
@event = machine.events.fetch(event)
|
92
|
+
@from_state = machine.states.fetch(from_name)
|
93
|
+
@from = read_state ? machine.read(object, :state) : @from_state.value
|
94
|
+
@to_state = machine.states.fetch(to_name)
|
95
|
+
@to = @to_state.value
|
96
|
+
|
97
|
+
reset
|
98
|
+
end
|
99
|
+
|
100
|
+
# The attribute which this transition's machine is defined for
|
101
|
+
def attribute
|
102
|
+
machine.attribute
|
103
|
+
end
|
104
|
+
|
105
|
+
# The action that will be run when this transition is performed
|
106
|
+
def action
|
107
|
+
machine.action
|
108
|
+
end
|
109
|
+
|
110
|
+
# The event that triggered the transition
|
111
|
+
def event
|
112
|
+
@event.name
|
113
|
+
end
|
114
|
+
|
115
|
+
# The fully-qualified name of the event that triggered the transition
|
116
|
+
def qualified_event
|
117
|
+
@event.qualified_name
|
118
|
+
end
|
119
|
+
|
120
|
+
# The human-readable name of the event that triggered the transition
|
121
|
+
def human_event
|
122
|
+
@event.human_name(@object.class)
|
123
|
+
end
|
124
|
+
|
125
|
+
# The state name *before* the transition
|
126
|
+
def from_name
|
127
|
+
@from_state.name
|
128
|
+
end
|
129
|
+
|
130
|
+
# The fully-qualified state name *before* the transition
|
131
|
+
def qualified_from_name
|
132
|
+
@from_state.qualified_name
|
133
|
+
end
|
134
|
+
|
135
|
+
# The human-readable state name *before* the transition
|
136
|
+
def human_from_name
|
137
|
+
@from_state.human_name(@object.class)
|
138
|
+
end
|
139
|
+
|
140
|
+
# The new state name *after* the transition
|
141
|
+
def to_name
|
142
|
+
@to_state.name
|
143
|
+
end
|
144
|
+
|
145
|
+
# The new fully-qualified state name *after* the transition
|
146
|
+
def qualified_to_name
|
147
|
+
@to_state.qualified_name
|
148
|
+
end
|
149
|
+
|
150
|
+
# The new human-readable state name *after* the transition
|
151
|
+
def human_to_name
|
152
|
+
@to_state.human_name(@object.class)
|
153
|
+
end
|
154
|
+
|
155
|
+
# Does this transition represent a loopback (i.e. the from and to state
|
156
|
+
# are the same)
|
157
|
+
#
|
158
|
+
# == Example
|
159
|
+
#
|
160
|
+
# machine = StateMachine.new(Vehicle)
|
161
|
+
# StateMachine::Transition.new(Vehicle.new, machine, :park, :parked, :parked).loopback? # => true
|
162
|
+
# StateMachine::Transition.new(Vehicle.new, machine, :park, :idling, :parked).loopback? # => false
|
163
|
+
def loopback?
|
164
|
+
from_name == to_name
|
165
|
+
end
|
166
|
+
|
167
|
+
# Is this transition existing for a short period only? If this is set, it
|
168
|
+
# indicates that the transition (or the event backing it) should not be
|
169
|
+
# written to the object if it fails.
|
170
|
+
def transient?
|
171
|
+
@transient
|
172
|
+
end
|
173
|
+
|
174
|
+
# A hash of all the core attributes defined for this transition with their
|
175
|
+
# names as keys and values of the attributes as values.
|
176
|
+
#
|
177
|
+
# == Example
|
178
|
+
#
|
179
|
+
# machine = StateMachine.new(Vehicle)
|
180
|
+
# transition = StateMachine::Transition.new(Vehicle.new, machine, :ignite, :parked, :idling)
|
181
|
+
# transition.attributes # => {:object => #<Vehicle:0xb7d60ea4>, :attribute => :state, :event => :ignite, :from => 'parked', :to => 'idling'}
|
182
|
+
def attributes
|
183
|
+
@attributes ||= {:object => object, :attribute => attribute, :event => event, :from => from, :to => to}
|
184
|
+
end
|
185
|
+
|
186
|
+
# Runs the actual transition and any before/after callbacks associated
|
187
|
+
# with the transition. The action associated with the transition/machine
|
188
|
+
# can be skipped by passing in +false+.
|
189
|
+
#
|
190
|
+
# == Examples
|
191
|
+
#
|
192
|
+
# class Vehicle
|
193
|
+
# state_machine :action => :save do
|
194
|
+
# ...
|
195
|
+
# end
|
196
|
+
# end
|
197
|
+
#
|
198
|
+
# vehicle = Vehicle.new
|
199
|
+
# transition = StateMachine::Transition.new(vehicle, machine, :ignite, :parked, :idling)
|
200
|
+
# transition.perform # => Runs the +save+ action after setting the state attribute
|
201
|
+
# transition.perform(false) # => Only sets the state attribute
|
202
|
+
# transition.perform(Time.now) # => Passes in additional arguments and runs the +save+ action
|
203
|
+
# transition.perform(Time.now, false) # => Passes in additional arguments and only sets the state attribute
|
204
|
+
def perform(*args)
|
205
|
+
run_action = [true, false].include?(args.last) ? args.pop : true
|
206
|
+
self.args = args
|
207
|
+
|
208
|
+
# Run the transition
|
209
|
+
!!TransitionCollection.new([self], :actions => run_action).perform
|
210
|
+
end
|
211
|
+
|
212
|
+
# Runs a block within a transaction for the object being transitioned.
|
213
|
+
# By default, transactions are a no-op unless otherwise defined by the
|
214
|
+
# machine's integration.
|
215
|
+
def within_transaction
|
216
|
+
machine.within_transaction(object) do
|
217
|
+
yield
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# Runs the before / after callbacks for this transition. If a block is
|
222
|
+
# provided, then it will be executed between the before and after callbacks.
|
223
|
+
#
|
224
|
+
# Configuration options:
|
225
|
+
# * +before+ - Whether to run before callbacks.
|
226
|
+
# * +after+ - Whether to run after callbacks. If false, then any around
|
227
|
+
# callbacks will be paused until called again with +after+ enabled.
|
228
|
+
# Default is true.
|
229
|
+
#
|
230
|
+
# This will return true if all before callbacks gets executed. After
|
231
|
+
# callbacks will not have an effect on the result.
|
232
|
+
def run_callbacks(options = {}, &block)
|
233
|
+
options = {:before => true, :after => true}.merge(options)
|
234
|
+
@success = false
|
235
|
+
|
236
|
+
halted = pausable { before(options[:after], &block) } if options[:before]
|
237
|
+
|
238
|
+
# After callbacks are only run if:
|
239
|
+
# * An around callback didn't halt after yielding
|
240
|
+
# * They're enabled or the run didn't succeed
|
241
|
+
after if !(@before_run && halted) && (options[:after] || !@success)
|
242
|
+
|
243
|
+
@before_run
|
244
|
+
end
|
245
|
+
|
246
|
+
# Transitions the current value of the state to that specified by the
|
247
|
+
# transition. Once the state is persisted, it cannot be persisted again
|
248
|
+
# until this transition is reset.
|
249
|
+
#
|
250
|
+
# == Example
|
251
|
+
#
|
252
|
+
# class Vehicle
|
253
|
+
# state_machine do
|
254
|
+
# event :ignite do
|
255
|
+
# transition :parked => :idling
|
256
|
+
# end
|
257
|
+
# end
|
258
|
+
# end
|
259
|
+
#
|
260
|
+
# vehicle = Vehicle.new
|
261
|
+
# transition = StateMachine::Transition.new(vehicle, Vehicle.state_machine, :ignite, :parked, :idling)
|
262
|
+
# transition.persist
|
263
|
+
#
|
264
|
+
# vehicle.state # => 'idling'
|
265
|
+
def persist
|
266
|
+
unless @persisted
|
267
|
+
machine.write(object, :state, to)
|
268
|
+
@persisted = true
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
# Rolls back changes made to the object's state via this transition. This
|
273
|
+
# will revert the state back to the +from+ value.
|
274
|
+
#
|
275
|
+
# == Example
|
276
|
+
#
|
277
|
+
# class Vehicle
|
278
|
+
# state_machine :initial => :parked do
|
279
|
+
# event :ignite do
|
280
|
+
# transition :parked => :idling
|
281
|
+
# end
|
282
|
+
# end
|
283
|
+
# end
|
284
|
+
#
|
285
|
+
# vehicle = Vehicle.new # => #<Vehicle:0xb7b7f568 @state="parked">
|
286
|
+
# transition = StateMachine::Transition.new(vehicle, Vehicle.state_machine, :ignite, :parked, :idling)
|
287
|
+
#
|
288
|
+
# # Persist the new state
|
289
|
+
# vehicle.state # => "parked"
|
290
|
+
# transition.persist
|
291
|
+
# vehicle.state # => "idling"
|
292
|
+
#
|
293
|
+
# # Roll back to the original state
|
294
|
+
# transition.rollback
|
295
|
+
# vehicle.state # => "parked"
|
296
|
+
def rollback
|
297
|
+
reset
|
298
|
+
machine.write(object, :state, from)
|
299
|
+
end
|
300
|
+
|
301
|
+
# Resets any tracking of which callbacks have already been run and whether
|
302
|
+
# the state has already been persisted
|
303
|
+
def reset
|
304
|
+
@before_run = @persisted = @after_run = false
|
305
|
+
@paused_block = nil
|
306
|
+
end
|
307
|
+
|
308
|
+
# Determines equality of transitions by testing whether the object, states,
|
309
|
+
# and event involved in the transition are equal
|
310
|
+
def ==(other)
|
311
|
+
other.instance_of?(self.class) &&
|
312
|
+
other.object == object &&
|
313
|
+
other.machine == machine &&
|
314
|
+
other.from_name == from_name &&
|
315
|
+
other.to_name == to_name &&
|
316
|
+
other.event == event
|
317
|
+
end
|
318
|
+
|
319
|
+
# Generates a nicely formatted description of this transitions's contents.
|
320
|
+
#
|
321
|
+
# For example,
|
322
|
+
#
|
323
|
+
# transition = StateMachine::Transition.new(object, machine, :ignite, :parked, :idling)
|
324
|
+
# transition # => #<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>
|
325
|
+
def inspect
|
326
|
+
"#<#{self.class} #{%w(attribute event from from_name to to_name).map {|attr| "#{attr}=#{send(attr).inspect}"} * ' '}>"
|
327
|
+
end
|
328
|
+
|
329
|
+
private
|
330
|
+
# Runs a block that may get paused. If the block doesn't pause, then
|
331
|
+
# execution will continue as normal. If the block gets paused, then it
|
332
|
+
# will take care of switching the execution context when it's resumed.
|
333
|
+
#
|
334
|
+
# This will return true if the given block halts for a reason other than
|
335
|
+
# getting paused.
|
336
|
+
def pausable
|
337
|
+
begin
|
338
|
+
halted = !catch(:halt) { yield; true }
|
339
|
+
rescue Exception => error
|
340
|
+
raise unless @resume_block
|
341
|
+
end
|
342
|
+
|
343
|
+
if @resume_block
|
344
|
+
@resume_block.call(halted, error)
|
345
|
+
else
|
346
|
+
halted
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
# Pauses the current callback execution. This should only occur within
|
351
|
+
# around callbacks when the remainder of the callback will be executed at
|
352
|
+
# a later point in time.
|
353
|
+
def pause
|
354
|
+
unless @resume_block
|
355
|
+
require 'continuation' unless defined?(callcc)
|
356
|
+
callcc do |block|
|
357
|
+
@paused_block = block
|
358
|
+
throw :halt, true
|
359
|
+
end
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
# Resumes the execution of a previously paused callback execution. Once
|
364
|
+
# the paused callbacks complete, the current execution will continue.
|
365
|
+
def resume
|
366
|
+
if @paused_block
|
367
|
+
halted, error = callcc do |block|
|
368
|
+
@resume_block = block
|
369
|
+
@paused_block.call
|
370
|
+
end
|
371
|
+
|
372
|
+
@resume_block = @paused_block = nil
|
373
|
+
|
374
|
+
raise error if error
|
375
|
+
!halted
|
376
|
+
else
|
377
|
+
true
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
# Runs the machine's +before+ callbacks for this transition. Only
|
382
|
+
# callbacks that are configured to match the event, from state, and to
|
383
|
+
# state will be invoked.
|
384
|
+
#
|
385
|
+
# Once the callbacks are run, they cannot be run again until this transition
|
386
|
+
# is reset.
|
387
|
+
def before(complete = true, index = 0, &block)
|
388
|
+
unless @before_run
|
389
|
+
while callback = machine.callbacks[:before][index]
|
390
|
+
index += 1
|
391
|
+
|
392
|
+
if callback.type == :around
|
393
|
+
# Around callback: need to handle recursively. Execution only gets
|
394
|
+
# paused if:
|
395
|
+
# * The block fails and the callback doesn't run on failures OR
|
396
|
+
# * The block succeeds, but after callbacks are disabled (in which
|
397
|
+
# case a continuation is stored for later execution)
|
398
|
+
return if catch(:cancel) do
|
399
|
+
callback.call(object, context, self) do
|
400
|
+
before(complete, index, &block)
|
401
|
+
|
402
|
+
pause if @success && !complete
|
403
|
+
throw :cancel, true unless @success
|
404
|
+
end
|
405
|
+
end
|
406
|
+
else
|
407
|
+
# Normal before callback
|
408
|
+
callback.call(object, context, self)
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
@before_run = true
|
413
|
+
end
|
414
|
+
|
415
|
+
action = {:success => true}.merge(block_given? ? yield : {})
|
416
|
+
@result, @success = action[:result], action[:success]
|
417
|
+
end
|
418
|
+
|
419
|
+
# Runs the machine's +after+ callbacks for this transition. Only
|
420
|
+
# callbacks that are configured to match the event, from state, and to
|
421
|
+
# state will be invoked.
|
422
|
+
#
|
423
|
+
# Once the callbacks are run, they cannot be run again until this transition
|
424
|
+
# is reset.
|
425
|
+
#
|
426
|
+
# == Halting
|
427
|
+
#
|
428
|
+
# If any callback throws a <tt>:halt</tt> exception, it will be caught
|
429
|
+
# and the callback chain will be automatically stopped. However, this
|
430
|
+
# exception will not bubble up to the caller since +after+ callbacks
|
431
|
+
# should never halt the execution of a +perform+.
|
432
|
+
def after
|
433
|
+
unless @after_run
|
434
|
+
# First resume previously paused callbacks
|
435
|
+
if resume
|
436
|
+
catch(:halt) do
|
437
|
+
type = @success ? :after : :failure
|
438
|
+
machine.callbacks[type].each {|callback| callback.call(object, context, self)}
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
@after_run = true
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
# Gets a hash of the context defining this unique transition (including
|
447
|
+
# event, from state, and to state).
|
448
|
+
#
|
449
|
+
# == Example
|
450
|
+
#
|
451
|
+
# machine = StateMachine.new(Vehicle)
|
452
|
+
# transition = StateMachine::Transition.new(Vehicle.new, machine, :ignite, :parked, :idling)
|
453
|
+
# transition.context # => {:on => :ignite, :from => :parked, :to => :idling}
|
454
|
+
def context
|
455
|
+
@context ||= {:on => event, :from => from_name, :to => to_name}
|
456
|
+
end
|
457
|
+
end
|
458
|
+
end
|