state_machine 0.8.1 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +17 -0
- data/LICENSE +1 -1
- data/README.rdoc +162 -23
- data/Rakefile +3 -18
- data/lib/state_machine.rb +3 -4
- data/lib/state_machine/callback.rb +65 -13
- data/lib/state_machine/eval_helpers.rb +20 -4
- data/lib/state_machine/initializers.rb +4 -0
- data/lib/state_machine/initializers/merb.rb +1 -0
- data/lib/state_machine/initializers/rails.rb +7 -0
- data/lib/state_machine/integrations.rb +21 -6
- data/lib/state_machine/integrations/active_model.rb +414 -0
- data/lib/state_machine/integrations/active_model/locale.rb +11 -0
- data/lib/state_machine/integrations/{active_record → active_model}/observer.rb +7 -7
- data/lib/state_machine/integrations/active_record.rb +65 -129
- data/lib/state_machine/integrations/active_record/locale.rb +4 -11
- data/lib/state_machine/integrations/data_mapper.rb +24 -6
- data/lib/state_machine/integrations/data_mapper/observer.rb +36 -0
- data/lib/state_machine/integrations/mongo_mapper.rb +295 -0
- data/lib/state_machine/integrations/sequel.rb +33 -7
- data/lib/state_machine/machine.rb +121 -23
- data/lib/state_machine/machine_collection.rb +12 -103
- data/lib/state_machine/transition.rb +125 -164
- data/lib/state_machine/transition_collection.rb +244 -0
- data/lib/tasks/state_machine.rb +12 -15
- data/test/functional/state_machine_test.rb +11 -1
- data/test/unit/callback_test.rb +305 -32
- data/test/unit/eval_helpers_test.rb +103 -1
- data/test/unit/event_test.rb +2 -1
- data/test/unit/guard_test.rb +2 -1
- data/test/unit/integrations/active_model_test.rb +909 -0
- data/test/unit/integrations/active_record_test.rb +1542 -1292
- data/test/unit/integrations/data_mapper_test.rb +1369 -1041
- data/test/unit/integrations/mongo_mapper_test.rb +1349 -0
- data/test/unit/integrations/sequel_test.rb +1214 -985
- data/test/unit/integrations_test.rb +8 -0
- data/test/unit/machine_collection_test.rb +140 -513
- data/test/unit/machine_test.rb +212 -10
- data/test/unit/state_test.rb +2 -1
- data/test/unit/transition_collection_test.rb +2098 -0
- data/test/unit/transition_test.rb +704 -552
- metadata +16 -3
@@ -64,6 +64,9 @@ module StateMachine
|
|
64
64
|
# vehicle = Vehicle.create(:state_event => 'ignite') # => #<Vehicle @values={:state=>"idling", :name=>nil, :id=>1}>
|
65
65
|
# vehicle.state # => "idling"
|
66
66
|
#
|
67
|
+
# This technique is always used for transitioning states when the +save+
|
68
|
+
# action (which is the default) is configured for the machine.
|
69
|
+
#
|
67
70
|
# === Security implications
|
68
71
|
#
|
69
72
|
# Beware that public event attributes mean that events can be fired
|
@@ -131,6 +134,9 @@ module StateMachine
|
|
131
134
|
# end
|
132
135
|
# end
|
133
136
|
#
|
137
|
+
# If using the +save+ action for the machine, this option will be ignored as
|
138
|
+
# the transaction will be created by Sequel within +save+.
|
139
|
+
#
|
134
140
|
# == Validation errors
|
135
141
|
#
|
136
142
|
# If an event fails to successfully fire because there are no matching
|
@@ -278,14 +284,34 @@ module StateMachine
|
|
278
284
|
end
|
279
285
|
end
|
280
286
|
|
281
|
-
# Adds hooks into validation for automatically firing events
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
+
# Adds hooks into validation for automatically firing events. This is
|
288
|
+
# a bit more complicated than other integrations since Sequel doesn't
|
289
|
+
# provide an easy way to hook around validation / save calls
|
290
|
+
def define_action_helpers
|
291
|
+
if action == :save
|
292
|
+
@instance_helper_module.class_eval do
|
293
|
+
define_method(:valid?) do |*args|
|
294
|
+
yielded = false
|
295
|
+
result = self.class.state_machines.transitions(self, :save, :after => false).perform do
|
296
|
+
yielded = true
|
297
|
+
super(*args)
|
298
|
+
end
|
299
|
+
|
300
|
+
raise_on_save_failure && !yielded && !result ? save_failure(:validation) : result
|
287
301
|
end
|
288
|
-
|
302
|
+
|
303
|
+
define_method(defined?(::Sequel::MAJOR) && (::Sequel::MAJOR >= 3 || ::Sequel::MAJOR == 2 && ::Sequel::MINOR == 12) ? :_save : :save) do |*args|
|
304
|
+
yielded = false
|
305
|
+
result = self.class.state_machines.transitions(self, :save).perform do
|
306
|
+
yielded = true
|
307
|
+
super(*args)
|
308
|
+
end
|
309
|
+
|
310
|
+
yielded || result ? result : save_failure(:save)
|
311
|
+
end
|
312
|
+
end unless owner_class.state_machines.any? {|name, machine| machine.action == :save && machine != self}
|
313
|
+
else
|
314
|
+
super
|
289
315
|
end
|
290
316
|
end
|
291
317
|
|
@@ -90,17 +90,17 @@ module StateMachine
|
|
90
90
|
# action being invoked (and not a superclass), then it must manually run the
|
91
91
|
# StateMachine hook that checks for event attributes.
|
92
92
|
#
|
93
|
-
# For example, in ActiveRecord, DataMapper, and Sequel, the
|
94
|
-
# (+save+) is already defined in a base class. As a result,
|
95
|
-
# machine is defined in a model / resource, StateMachine can
|
96
|
-
# hook into the +save+ action.
|
93
|
+
# For example, in ActiveRecord, DataMapper, MongoMapper, and Sequel, the
|
94
|
+
# default action (+save+) is already defined in a base class. As a result,
|
95
|
+
# when a state machine is defined in a model / resource, StateMachine can
|
96
|
+
# automatically hook into the +save+ action.
|
97
97
|
#
|
98
98
|
# On the other hand, the Vehicle class from above defined its own +save+
|
99
99
|
# method (and there is no +save+ method in its superclass). As a result, it
|
100
100
|
# must be modified like so:
|
101
101
|
#
|
102
102
|
# def save
|
103
|
-
# self.class.state_machines.
|
103
|
+
# self.class.state_machines.transitions(self, :save).perform do
|
104
104
|
# @saving_state = state
|
105
105
|
# fail != true
|
106
106
|
# end
|
@@ -156,6 +156,11 @@ module StateMachine
|
|
156
156
|
# later callbacks are canceled. If an +after+ callback halts the chain,
|
157
157
|
# the later callbacks are canceled, but the transition is still successful.
|
158
158
|
#
|
159
|
+
# These same rules apply to +around+ callbacks with the exception that any
|
160
|
+
# +around+ callback that doesn't yield will essentially result in :halt being
|
161
|
+
# thrown. Any code executed after the yield will behave in the same way as
|
162
|
+
# +after+ callbacks.
|
163
|
+
#
|
159
164
|
# *Note* that if a +before+ callback fails and the bang version of an event
|
160
165
|
# was invoked, an exception will be raised instead of returning false. For
|
161
166
|
# example,
|
@@ -207,6 +212,10 @@ module StateMachine
|
|
207
212
|
# def self.after_transition(vehicle, transition)
|
208
213
|
# logger.info "#{vehicle} instructed to #{transition.event}... #{transition.attribute} was: #{transition.from}, #{transition.attribute} is: #{transition.to}"
|
209
214
|
# end
|
215
|
+
#
|
216
|
+
# def self.around_transition(vehicle, transition)
|
217
|
+
# logger.info Benchmark.measure { yield }
|
218
|
+
# end
|
210
219
|
# end
|
211
220
|
#
|
212
221
|
# Vehicle.state_machine do
|
@@ -215,6 +224,8 @@ module StateMachine
|
|
215
224
|
#
|
216
225
|
# after_transition :on => :park, :do => VehicleObserver.method(:after_park)
|
217
226
|
# after_transition VehicleObserver.method(:after_transition)
|
227
|
+
#
|
228
|
+
# around_transition VehicleObserver.method(:around_transition)
|
218
229
|
# end
|
219
230
|
#
|
220
231
|
# One common callback is to record transitions for all models in the system
|
@@ -279,11 +290,13 @@ module StateMachine
|
|
279
290
|
#
|
280
291
|
# When a state machine is defined for classes using any of the above libraries,
|
281
292
|
# it will try to automatically determine the integration to use (Agnostic,
|
282
|
-
# ActiveRecord, DataMapper, or Sequel) based on the
|
283
|
-
# see how each integration affects the machine's
|
284
|
-
# constants defined under the StateMachine::Integrations
|
293
|
+
# ActiveModel, ActiveRecord, DataMapper, MongoMapper, or Sequel) based on the
|
294
|
+
# class definition. To see how each integration affects the machine's
|
295
|
+
# behavior, refer to all constants defined under the StateMachine::Integrations
|
296
|
+
# namespace.
|
285
297
|
class Machine
|
286
298
|
include Assertions
|
299
|
+
include EvalHelpers
|
287
300
|
include MatcherHelpers
|
288
301
|
|
289
302
|
class << self
|
@@ -489,6 +502,12 @@ module StateMachine
|
|
489
502
|
states.each {|state| state.initial = (state.name == @initial_state)}
|
490
503
|
end
|
491
504
|
|
505
|
+
# Initializes the state on the given object. This will always write to the
|
506
|
+
# attribute regardless of whether a value is already present.
|
507
|
+
def initialize_state(object)
|
508
|
+
write(object, :state, initial_state(object).value)
|
509
|
+
end
|
510
|
+
|
492
511
|
# Gets the actual name of the attribute on the machine's owner class that
|
493
512
|
# stores data with the given name.
|
494
513
|
def attribute(name = :state)
|
@@ -569,7 +588,7 @@ module StateMachine
|
|
569
588
|
# vehicle.force_idle = false
|
570
589
|
# Vehicle.state_machine.initial_state(vehicle) # => #<StateMachine::State name=:parked value="parked" initial=false>
|
571
590
|
def initial_state(object)
|
572
|
-
states.fetch(dynamic_initial_state? ? @initial_state
|
591
|
+
states.fetch(dynamic_initial_state? ? evaluate_method(object, @initial_state) : @initial_state)
|
573
592
|
end
|
574
593
|
|
575
594
|
# Whether a dynamic initial state is being used in the machine
|
@@ -917,6 +936,7 @@ module StateMachine
|
|
917
936
|
#
|
918
937
|
# event :first_gear do
|
919
938
|
# transition :parked => :first_gear, :if => :seatbelt_on?
|
939
|
+
# transition :parked => same # Allow to loopback if seatbelt is off
|
920
940
|
# end
|
921
941
|
#
|
922
942
|
# See StateMachine::Event#transition for more information on
|
@@ -983,6 +1003,7 @@ module StateMachine
|
|
983
1003
|
#
|
984
1004
|
# event :ignite do
|
985
1005
|
# transition :parked => :idling
|
1006
|
+
# transition :idling => same # Allow ignite while still idling
|
986
1007
|
# end
|
987
1008
|
# end
|
988
1009
|
# end
|
@@ -1084,15 +1105,20 @@ module StateMachine
|
|
1084
1105
|
#
|
1085
1106
|
# == Result requirements
|
1086
1107
|
#
|
1087
|
-
# By default, after_transition callbacks
|
1108
|
+
# By default, +after_transition+ callbacks and code executed after an
|
1109
|
+
# +around_transition+ callback yields will only be run if the transition
|
1088
1110
|
# was performed successfully. A transition is successful if the machine's
|
1089
1111
|
# action is not configured or does not return false when it is invoked.
|
1090
|
-
# In order to include failed attempts when running an after_transition
|
1091
|
-
# callback, the
|
1112
|
+
# In order to include failed attempts when running an +after_transition+ or
|
1113
|
+
# +around_transition+ callback, the <tt>:include_failures</tt> option can be
|
1114
|
+
# specified like so:
|
1092
1115
|
#
|
1093
1116
|
# after_transition :include_failures => true, :do => ... # Runs on all attempts to transition, including failures
|
1094
1117
|
# after_transition :do => ... # Runs only on successful attempts to transition
|
1095
1118
|
#
|
1119
|
+
# around_transition :include_failures => true, :do => ... # Runs on all attempts to transition, including failures
|
1120
|
+
# around_transition :do => ... # Runs only on successful attempts to transition
|
1121
|
+
#
|
1096
1122
|
# == Verbose Requirements
|
1097
1123
|
#
|
1098
1124
|
# Requirements can also be defined using verbose options rather than the
|
@@ -1203,6 +1229,67 @@ module StateMachine
|
|
1203
1229
|
add_callback(:after, options, &block)
|
1204
1230
|
end
|
1205
1231
|
|
1232
|
+
# Creates a callback that will be invoked *around* a transition so long as
|
1233
|
+
# the given requirements match the transition.
|
1234
|
+
#
|
1235
|
+
# == The callback
|
1236
|
+
#
|
1237
|
+
# Around callbacks wrap transitions, executing code both before and after.
|
1238
|
+
# These callbacks are defined in the exact same manner as before / after
|
1239
|
+
# callbacks with the exception that the transition must be yielded to in
|
1240
|
+
# order to finish running it.
|
1241
|
+
#
|
1242
|
+
# If defining +around+ callbacks using blocks, you must yield within the
|
1243
|
+
# transition by directly calling the block (since yielding is not allowed
|
1244
|
+
# within blocks).
|
1245
|
+
#
|
1246
|
+
# For example,
|
1247
|
+
#
|
1248
|
+
# class Vehicle
|
1249
|
+
# state_machine do
|
1250
|
+
# around_transition do |block|
|
1251
|
+
# Benchmark.measure { block.call }
|
1252
|
+
# end
|
1253
|
+
#
|
1254
|
+
# around_transition do |vehicle, block|
|
1255
|
+
# logger.info "vehicle was #{state}..."
|
1256
|
+
# block.call
|
1257
|
+
# logger.info "...and is now #{state}"
|
1258
|
+
# end
|
1259
|
+
#
|
1260
|
+
# around_transition do |vehicle, transition, block|
|
1261
|
+
# logger.info "before #{transition.event}: #{vehicle.state}"
|
1262
|
+
# block.call
|
1263
|
+
# logger.info "after #{transition.event}: #{vehicle.state}"
|
1264
|
+
# end
|
1265
|
+
# end
|
1266
|
+
# end
|
1267
|
+
#
|
1268
|
+
# Notice that referencing the block is similar to doing so within an
|
1269
|
+
# actual method definition in that it is always the last argument.
|
1270
|
+
#
|
1271
|
+
# On the other hand, if you're defining +around+ callbacks using method
|
1272
|
+
# references, you can yield like normal:
|
1273
|
+
#
|
1274
|
+
# class Vehicle
|
1275
|
+
# state_machine do
|
1276
|
+
# around_transition :benchmark
|
1277
|
+
# ...
|
1278
|
+
# end
|
1279
|
+
#
|
1280
|
+
# def benchmark
|
1281
|
+
# Benchmark.measure { yield }
|
1282
|
+
# end
|
1283
|
+
# end
|
1284
|
+
#
|
1285
|
+
# See +before_transition+ for a description of the possible configurations
|
1286
|
+
# for defining callbacks.
|
1287
|
+
def around_transition(*args, &block)
|
1288
|
+
options = (args.last.is_a?(Hash) ? args.pop : {})
|
1289
|
+
options[:do] = args if args.any?
|
1290
|
+
add_callback(:around, options, &block)
|
1291
|
+
end
|
1292
|
+
|
1206
1293
|
# Marks the given object as invalid with the given message.
|
1207
1294
|
#
|
1208
1295
|
# By default, this is a no-op.
|
@@ -1265,6 +1352,7 @@ module StateMachine
|
|
1265
1352
|
begin
|
1266
1353
|
# Load the graphviz library
|
1267
1354
|
require 'rubygems'
|
1355
|
+
gem 'ruby-graphviz', '>=0.9.0'
|
1268
1356
|
require 'graphviz'
|
1269
1357
|
|
1270
1358
|
graph = GraphViz.new('G', :rankdir => options[:orientation] == 'landscape' ? 'LR' : 'TB')
|
@@ -1284,7 +1372,7 @@ module StateMachine
|
|
1284
1372
|
# Generate the graph
|
1285
1373
|
graphvizVersion = Constants::RGV_VERSION.split('.')
|
1286
1374
|
|
1287
|
-
if graphvizVersion[
|
1375
|
+
if graphvizVersion[1] == '9' && graphvizVersion[2] == '0'
|
1288
1376
|
outputOptions = {
|
1289
1377
|
:output => options[:format],
|
1290
1378
|
:file => File.join(options[:path], "#{options[:name]}.#{options[:format]}")
|
@@ -1298,11 +1386,17 @@ module StateMachine
|
|
1298
1386
|
graph.output(outputOptions)
|
1299
1387
|
graph
|
1300
1388
|
rescue LoadError
|
1301
|
-
$stderr.puts 'Cannot draw the machine. `gem install ruby-graphviz` and try again.'
|
1389
|
+
$stderr.puts 'Cannot draw the machine. `gem install ruby-graphviz` >= v0.9.0 and try again.'
|
1302
1390
|
false
|
1303
1391
|
end
|
1304
1392
|
end
|
1305
1393
|
|
1394
|
+
# Determines whether a helper method was defined for firing attribute-based
|
1395
|
+
# event transitions when the configuration action gets called.
|
1396
|
+
def action_helper_defined?
|
1397
|
+
@action_helper_defined
|
1398
|
+
end
|
1399
|
+
|
1306
1400
|
protected
|
1307
1401
|
# Runs additional initialization hooks. By default, this is a no-op.
|
1308
1402
|
def after_initialize
|
@@ -1389,23 +1483,27 @@ module StateMachine
|
|
1389
1483
|
# Adds helper methods for automatically firing events when an action
|
1390
1484
|
# is invoked
|
1391
1485
|
def define_action_helpers(action_hook = self.action)
|
1392
|
-
|
1393
|
-
|
1486
|
+
private_action = owner_class.private_method_defined?(action_hook)
|
1487
|
+
action_defined = @action_helper_defined = owner_class.ancestors.any? do |ancestor|
|
1488
|
+
ancestor != owner_class && (ancestor.method_defined?(action_hook) || ancestor.private_method_defined?(action_hook))
|
1489
|
+
end
|
1490
|
+
action_overridden = owner_class.state_machines.any? {|name, machine| machine.action == action && machine != self}
|
1394
1491
|
|
1395
|
-
|
1396
|
-
|
1492
|
+
# Only define helper if:
|
1493
|
+
# 1. Action was originally defined somewhere other than the owner class
|
1494
|
+
# 2. It hasn't already been overridden by another machine
|
1495
|
+
if action_defined && !action_overridden
|
1496
|
+
action = self.action
|
1397
1497
|
@instance_helper_module.class_eval do
|
1398
|
-
# Override the default action to invoke the before / after hooks
|
1399
1498
|
define_method(action_hook) do |*args|
|
1400
|
-
self.class.state_machines.
|
1499
|
+
self.class.state_machines.transitions(self, action).perform { super(*args) }
|
1401
1500
|
end
|
1402
1501
|
|
1403
|
-
private action_hook if
|
1502
|
+
private action_hook if private_action
|
1404
1503
|
end
|
1405
1504
|
|
1406
1505
|
true
|
1407
1506
|
else
|
1408
|
-
# Action already defined: don't add integration-specific hooks
|
1409
1507
|
false
|
1410
1508
|
end
|
1411
1509
|
end
|
@@ -1466,7 +1564,7 @@ module StateMachine
|
|
1466
1564
|
|
1467
1565
|
# Adds a new transition callback of the given type.
|
1468
1566
|
def add_callback(type, options, &block)
|
1469
|
-
callbacks[type] << callback = Callback.new(options, &block)
|
1567
|
+
callbacks[type == :around ? :before : type] << callback = Callback.new(type, options, &block)
|
1470
1568
|
add_states(callback.known_states)
|
1471
1569
|
callback
|
1472
1570
|
end
|
@@ -6,13 +6,13 @@ module StateMachine
|
|
6
6
|
# (which must mean the defaults are being skipped)
|
7
7
|
def initialize_states(object, options = {})
|
8
8
|
if ignore = options[:ignore]
|
9
|
-
ignore.map
|
9
|
+
ignore = ignore.map {|attribute| attribute.to_sym}
|
10
10
|
end
|
11
11
|
|
12
12
|
each_value do |machine|
|
13
13
|
if (!ignore || !ignore.include?(machine.attribute)) && (!options.include?(:dynamic) || machine.dynamic_initial_state? == options[:dynamic])
|
14
14
|
value = machine.read(object, :state)
|
15
|
-
machine.
|
15
|
+
machine.initialize_state(object) if ignore || value.nil? || value.respond_to?(:empty?) && value.empty?
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
@@ -26,9 +26,7 @@ module StateMachine
|
|
26
26
|
transitions = events.collect do |event_name|
|
27
27
|
# Find the actual event being run
|
28
28
|
event = nil
|
29
|
-
detect
|
30
|
-
event = machine.events[event_name, :qualified_name]
|
31
|
-
end
|
29
|
+
detect {|name, machine| event = machine.events[event_name, :qualified_name]}
|
32
30
|
|
33
31
|
raise InvalidEvent, "#{event_name.inspect} is an unknown state machine event" unless event
|
34
32
|
|
@@ -44,112 +42,23 @@ module StateMachine
|
|
44
42
|
# Run the events in parallel only if valid transitions were found for
|
45
43
|
# all of them
|
46
44
|
if events.length == transitions.length
|
47
|
-
|
45
|
+
TransitionCollection.new(transitions, :actions => run_action).perform
|
48
46
|
else
|
49
47
|
false
|
50
48
|
end
|
51
49
|
end
|
52
50
|
|
53
|
-
#
|
54
|
-
#
|
55
|
-
#
|
56
|
-
# example, when validating records in ORM integrations).
|
57
|
-
#
|
58
|
-
# The event attributes that will be fired are based on which machines
|
59
|
-
# match the action that is being invoked.
|
60
|
-
#
|
61
|
-
# == Examples
|
62
|
-
#
|
63
|
-
# class Vehicle
|
64
|
-
# include DataMapper::Resource
|
65
|
-
# property :id, Serial
|
66
|
-
#
|
67
|
-
# state_machine :initial => :parked do
|
68
|
-
# event :ignite do
|
69
|
-
# transition :parked => :idling
|
70
|
-
# end
|
71
|
-
# end
|
72
|
-
#
|
73
|
-
# state_machine :alarm_state, :namespace => 'alarm', :initial => :active do
|
74
|
-
# event :disable do
|
75
|
-
# transition all => :off
|
76
|
-
# end
|
77
|
-
# end
|
78
|
-
# end
|
79
|
-
#
|
80
|
-
# With valid events:
|
81
|
-
#
|
82
|
-
# vehicle = Vehicle.create # => #<Vehicle id=1 state="parked" alarm_state="active">
|
83
|
-
# vehicle.state_event = 'ignite'
|
84
|
-
# vehicle.alarm_state_event = 'disable'
|
85
|
-
#
|
86
|
-
# Vehicle.state_machines.fire_event_attributes(vehicle, :save) { true }
|
87
|
-
# vehicle.state # => "idling"
|
88
|
-
# vehicle.state_event # => nil
|
89
|
-
# vehicle.alarm_state # => "off"
|
90
|
-
# vehicle.alarm_state_event # => nil
|
91
|
-
#
|
92
|
-
# With invalid events:
|
93
|
-
#
|
94
|
-
# vehicle = Vehicle.create # => #<Vehicle id=1 state="parked" alarm_state="active">
|
95
|
-
# vehicle.state_event = 'park'
|
96
|
-
# vehicle.alarm_state_event = 'disable'
|
97
|
-
#
|
98
|
-
# Vehicle.state_machines.fire_event_attributes(vehicle, :save) { true }
|
99
|
-
# vehicle.state # => "parked"
|
100
|
-
# vehicle.state_event # => nil
|
101
|
-
# vehicle.alarm_state # => "active"
|
102
|
-
# vehicle.alarm_state_event # => nil
|
103
|
-
# vehicle.errors # => #<DataMapper::Validate::ValidationErrors:0xb7af9abc @errors={"state_event"=>["is invalid"]}>
|
104
|
-
#
|
105
|
-
# With partial firing:
|
51
|
+
# Builds the collection of transitions for all event attributes defined on
|
52
|
+
# the given object. This will only include events whose machine actions
|
53
|
+
# match the one specified.
|
106
54
|
#
|
107
|
-
#
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
# vehicle.state # => "idling"
|
112
|
-
# vehicle.state_event # => "ignite"
|
113
|
-
# vehicle.state_event_transition # => #<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>
|
114
|
-
def fire_event_attributes(object, action, complete = true)
|
115
|
-
# Get the transitions to fire for each applicable machine
|
116
|
-
transitions = map {|name, machine| machine.action == action ? machine.events.attribute_transition_for(object, true) : nil}.compact
|
117
|
-
return yield if transitions.empty?
|
118
|
-
|
119
|
-
# The value generated by the yielded block (the actual action)
|
120
|
-
action_value = nil
|
121
|
-
|
122
|
-
# Make sure all events were valid
|
123
|
-
if result = transitions.all? {|transition| transition != false}
|
124
|
-
# Clear any traces of the event since transitions are available and to
|
125
|
-
# prevent from being evaluated multiple times if actions are nested
|
126
|
-
transitions.each do |transition|
|
127
|
-
transition.machine.write(object, :event, nil)
|
128
|
-
transition.machine.write(object, :event_transition, nil)
|
129
|
-
end
|
130
|
-
|
131
|
-
# Perform the transitions
|
132
|
-
begin
|
133
|
-
result = Transition.perform(transitions, :after => complete) { action_value = yield }
|
134
|
-
rescue Exception
|
135
|
-
# Reset the event attribute so it can be re-evaluated if attempted again
|
136
|
-
transitions.each do |transition|
|
137
|
-
transition.machine.write(object, :event, transition.event)
|
138
|
-
end
|
139
|
-
|
140
|
-
raise
|
141
|
-
end
|
142
|
-
|
143
|
-
transitions.each do |transition|
|
144
|
-
# Revert event if failed (to allow for more attempts)
|
145
|
-
transition.machine.write(object, :event, transition.event) unless result
|
146
|
-
|
147
|
-
# Track transition if partial transition was successful
|
148
|
-
transition.machine.write(object, :event_transition, transition) if !complete && result
|
149
|
-
end
|
55
|
+
# These should only be fired as a result of the action being run.
|
56
|
+
def transitions(object, action, options = {})
|
57
|
+
transitions = map do |name, machine|
|
58
|
+
machine.events.attribute_transition_for(object, true) if machine.action == action
|
150
59
|
end
|
151
60
|
|
152
|
-
|
61
|
+
AttributeTransitionCollection.new(transitions.compact, options)
|
153
62
|
end
|
154
63
|
end
|
155
64
|
end
|