state_machines 0.50.0 → 0.100.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.
- checksums.yaml +4 -4
- data/lib/state_machines/async_mode.rb +1 -1
- data/lib/state_machines/branch.rb +3 -0
- data/lib/state_machines/eval_helpers.rb +6 -8
- data/lib/state_machines/event_collection.rb +2 -2
- data/lib/state_machines/machine/async_extensions.rb +1 -1
- data/lib/state_machines/machine/class_methods.rb +1 -3
- data/lib/state_machines/machine/configuration.rb +1 -3
- data/lib/state_machines/machine.rb +0 -2
- data/lib/state_machines/test_helper.rb +0 -1
- data/lib/state_machines/transition.rb +194 -69
- data/lib/state_machines/transition_collection.rb +32 -12
- data/lib/state_machines/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c2e1765c263d1be7747cf812e195b95ab82d183dbf62d83a4014bc47e34132ee
|
4
|
+
data.tar.gz: 35fc3eba5b2a7321436de83091467b0f9e65309356e7d948479acd46be85ee68
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7b76d0c6596909901ccbeedf46b581b0fda2289466ae9c0bd402dc8b40db7d39fe74cbe7792a3b49c439e920ac6dd92c6bedfb51236155a3d0e61b0d7a84db51
|
7
|
+
data.tar.gz: 887b286630bd17fb51c9b00550ee3c6822fd3d1b0c2084630d4cf3e960c66abda12962e050bd09c1829e7091694d120378ad15607ba02f1f2594518b15173976
|
@@ -22,7 +22,7 @@ end
|
|
22
22
|
|
23
23
|
# Load required gems with version constraints
|
24
24
|
gem 'async', '>= 2.25.0'
|
25
|
-
gem 'concurrent-ruby', '>= 1.3.5'
|
25
|
+
gem 'concurrent-ruby', '>= 1.3.5' # Security is not negotiable - enterprise-grade thread safety required
|
26
26
|
|
27
27
|
require 'async'
|
28
28
|
require 'concurrent-ruby'
|
@@ -94,6 +94,9 @@ module StateMachines
|
|
94
94
|
def matches?(object, query = {})
|
95
95
|
!match(object, query).nil?
|
96
96
|
end
|
97
|
+
|
98
|
+
# Alias for Minitest's assert_match
|
99
|
+
alias =~ matches?
|
97
100
|
|
98
101
|
# Attempts to match the given object / query against the set of requirements
|
99
102
|
# configured for this branch. In addition to matching the event, from state,
|
@@ -114,12 +114,10 @@ module StateMachines
|
|
114
114
|
# Input validation for string evaluation
|
115
115
|
validate_eval_string(str)
|
116
116
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
# Support for JRuby and Truffle Ruby, which don't support binding blocks
|
122
|
-
# Need to check with @headius, if jruby 10 does now.
|
117
|
+
# Evaluate the string in the object's context
|
118
|
+
if block_given?
|
119
|
+
# TruffleRuby and some other implementations need special handling for blocks
|
120
|
+
# Create a temporary method to evaluate the string with block support
|
123
121
|
eigen = class << object; self; end
|
124
122
|
eigen.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
125
123
|
def __temp_eval_method__(*args, &b)
|
@@ -129,8 +127,8 @@ module StateMachines
|
|
129
127
|
result = object.__temp_eval_method__(*args, &block)
|
130
128
|
eigen.send(:remove_method, :__temp_eval_method__)
|
131
129
|
result
|
132
|
-
|
133
|
-
|
130
|
+
else
|
131
|
+
object.instance_eval(str, __FILE__, __LINE__)
|
134
132
|
end
|
135
133
|
else
|
136
134
|
raise ArgumentError, 'Methods must be a symbol denoting the method to call, a block to be invoked, or a string to be evaluated'
|
@@ -119,12 +119,12 @@ module StateMachines
|
|
119
119
|
# TODO, simplify
|
120
120
|
# First try the regular event_transition
|
121
121
|
transition = machine.read(object, :event_transition)
|
122
|
-
|
122
|
+
|
123
123
|
# If not found and we have stored transitions by machine (issue #91)
|
124
124
|
if !transition && (transitions_by_machine = object.instance_variable_get(:@_state_machine_event_transitions))
|
125
125
|
transition = transitions_by_machine[machine.name]
|
126
126
|
end
|
127
|
-
|
127
|
+
|
128
128
|
transition || if event_name = machine.read(object, :event)
|
129
129
|
if event = self[event_name.to_sym, :name]
|
130
130
|
event.transition_for(object) || begin
|
@@ -30,7 +30,7 @@ module StateMachines
|
|
30
30
|
|
31
31
|
owner_class.include(StateMachines::AsyncMode::ThreadSafeState)
|
32
32
|
owner_class.include(StateMachines::AsyncMode::AsyncEvents)
|
33
|
-
|
33
|
+
extend(StateMachines::AsyncMode::AsyncMachine)
|
34
34
|
|
35
35
|
# Extend events to generate async versions
|
36
36
|
events.each do |event|
|
@@ -31,9 +31,7 @@ module StateMachines
|
|
31
31
|
machine.initial_state = options[:initial] if options.include?(:initial)
|
32
32
|
machine.owner_class = owner_class
|
33
33
|
# Configure async mode if requested in options
|
34
|
-
if options.include?(:async)
|
35
|
-
machine.configure_async_mode!(options[:async])
|
36
|
-
end
|
34
|
+
machine.configure_async_mode!(options[:async]) if options.include?(:async)
|
37
35
|
end
|
38
36
|
|
39
37
|
# Evaluate DSL
|
@@ -51,9 +51,7 @@ module StateMachines
|
|
51
51
|
instance_eval(&) if block_given?
|
52
52
|
|
53
53
|
# Configure async mode if requested, after owner_class is set and DSL is evaluated
|
54
|
-
if @async_requested
|
55
|
-
configure_async_mode!(true)
|
56
|
-
end
|
54
|
+
configure_async_mode!(true) if @async_requested
|
57
55
|
|
58
56
|
self.initial_state = options[:initial] unless sibling_machines.any?
|
59
57
|
end
|
@@ -749,7 +749,6 @@ module StateMachines
|
|
749
749
|
has_async = object.respond_to?(async_method)
|
750
750
|
has_async_bang = object.respond_to?(async_bang_method)
|
751
751
|
|
752
|
-
default_message = "Expected async event methods #{async_method} and #{async_bang_method} to be available for event :#{event}"
|
753
752
|
|
754
753
|
if defined?(::Minitest)
|
755
754
|
assert has_async, "Missing #{async_method} method"
|
@@ -30,19 +30,15 @@ module StateMachines
|
|
30
30
|
# Whether the transition is only existing temporarily for the object
|
31
31
|
attr_writer :transient
|
32
32
|
|
33
|
-
# Determines whether the current ruby implementation supports pausing and
|
34
|
-
# resuming transitions
|
35
|
-
def self.pause_supported?
|
36
|
-
%w[ruby maglev].include?(RUBY_ENGINE)
|
37
|
-
end
|
38
|
-
|
39
33
|
# Creates a new, specific transition
|
40
34
|
def initialize(object, machine, event, from_name, to_name, read_state = true) # :nodoc:
|
41
35
|
@object = object
|
42
36
|
@machine = machine
|
43
37
|
@args = []
|
44
38
|
@transient = false
|
45
|
-
@
|
39
|
+
@paused_fiber = nil
|
40
|
+
@resuming = false
|
41
|
+
@continuation_block = nil
|
46
42
|
|
47
43
|
@event = machine.events.fetch(event)
|
48
44
|
@from_state = machine.states.fetch(from_name)
|
@@ -161,13 +157,13 @@ module StateMachines
|
|
161
157
|
# transition.perform(Time.now, run_action: false) # => Passes in additional arguments and only sets the state attribute
|
162
158
|
def perform(*args)
|
163
159
|
run_action = case args.last
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
160
|
+
in true | false
|
161
|
+
args.pop
|
162
|
+
in { run_action: }
|
163
|
+
args.last.delete(:run_action)
|
164
|
+
else
|
165
|
+
true
|
166
|
+
end
|
171
167
|
|
172
168
|
self.args = args
|
173
169
|
|
@@ -195,14 +191,32 @@ module StateMachines
|
|
195
191
|
# callbacks will not have an effect on the result.
|
196
192
|
def run_callbacks(options = {}, &block)
|
197
193
|
options = { before: true, after: true }.merge(options)
|
198
|
-
@success = false
|
199
194
|
|
200
|
-
|
195
|
+
# If we have a paused fiber and we're not trying to resume (after: false),
|
196
|
+
# this is an idempotent call on an already-paused transition. Just return true.
|
197
|
+
return true if @paused_fiber&.alive? && !options[:after]
|
198
|
+
|
199
|
+
# Extract pausable options
|
200
|
+
pausable_options = options.key?(:fiber) ? { fiber: options[:fiber] } : {}
|
201
|
+
|
202
|
+
# Check if we're resuming from a pause
|
203
|
+
if @paused_fiber&.alive? && options[:after]
|
204
|
+
# Resume the paused fiber
|
205
|
+
# Don't reset @success when resuming - preserve the state from the pause
|
206
|
+
# Store the block for later execution
|
207
|
+
@continuation_block = block if block_given?
|
208
|
+
halted = pausable(pausable_options) { true }
|
209
|
+
else
|
210
|
+
@success = false
|
211
|
+
# For normal execution (not pause/resume), default to success
|
212
|
+
# The action block will override this if needed
|
213
|
+
halted = pausable(pausable_options) { before(options[:after], &block) } if options[:before]
|
214
|
+
end
|
201
215
|
|
202
216
|
# After callbacks are only run if:
|
203
|
-
# * An around callback didn't halt after yielding
|
217
|
+
# * An around callback didn't halt after yielding OR the run failed
|
204
218
|
# * They're enabled or the run didn't succeed
|
205
|
-
after if !(@before_run && halted) && (options[:after] || !@success)
|
219
|
+
after if (!(@before_run && halted) || !@success) && (options[:after] || !@success)
|
206
220
|
|
207
221
|
@before_run
|
208
222
|
end
|
@@ -266,7 +280,9 @@ module StateMachines
|
|
266
280
|
# the state has already been persisted
|
267
281
|
def reset
|
268
282
|
@before_run = @persisted = @after_run = false
|
269
|
-
@
|
283
|
+
@paused_fiber = nil
|
284
|
+
@resuming = false
|
285
|
+
@continuation_block = nil
|
270
286
|
end
|
271
287
|
|
272
288
|
# Determines equality of transitions by testing whether the object, states,
|
@@ -290,6 +306,38 @@ module StateMachines
|
|
290
306
|
"#<#{self.class} #{%w[attribute event from from_name to to_name].map { |attr| "#{attr}=#{send(attr).inspect}" } * ' '}>"
|
291
307
|
end
|
292
308
|
|
309
|
+
# Checks whether this transition is currently paused.
|
310
|
+
# Returns true if there is a paused fiber, false otherwise.
|
311
|
+
def paused?
|
312
|
+
@paused_fiber&.alive? || false
|
313
|
+
end
|
314
|
+
|
315
|
+
# Checks whether this transition has a paused fiber that can be resumed.
|
316
|
+
# Returns true if there is a paused fiber, false otherwise.
|
317
|
+
#
|
318
|
+
# Note: The actual resuming happens automatically when run_callbacks is called
|
319
|
+
# again on a transition with a paused fiber.
|
320
|
+
def resumable?
|
321
|
+
paused?
|
322
|
+
end
|
323
|
+
|
324
|
+
# Manually resumes the execution of a previously paused callback.
|
325
|
+
# Returns true if the transition was successfully resumed and completed,
|
326
|
+
# false if there was no paused fiber, and raises an exception if the
|
327
|
+
# transition was halted.
|
328
|
+
def resume!(&block)
|
329
|
+
return false unless paused?
|
330
|
+
|
331
|
+
# Store continuation block if provided
|
332
|
+
@continuation_block = block if block_given?
|
333
|
+
|
334
|
+
# Run the pausable block which will resume the fiber
|
335
|
+
halted = pausable { true }
|
336
|
+
|
337
|
+
# Return whether the transition completed successfully
|
338
|
+
!halted
|
339
|
+
end
|
340
|
+
|
293
341
|
private
|
294
342
|
|
295
343
|
# Runs a block that may get paused. If the block doesn't pause, then
|
@@ -298,20 +346,97 @@ module StateMachines
|
|
298
346
|
#
|
299
347
|
# This will return true if the given block halts for a reason other than
|
300
348
|
# getting paused.
|
301
|
-
|
302
|
-
|
349
|
+
#
|
350
|
+
# Options:
|
351
|
+
# * :fiber - Whether to use fiber-based execution (default: true)
|
352
|
+
def pausable(options = {})
|
353
|
+
# If fiber is disabled, use simple synchronous execution
|
354
|
+
if options[:fiber] == false
|
303
355
|
halted = !catch(:halt) do
|
304
356
|
yield
|
305
357
|
true
|
306
358
|
end
|
307
|
-
|
308
|
-
raise unless @resume_block
|
359
|
+
return halted
|
309
360
|
end
|
310
361
|
|
311
|
-
if @
|
312
|
-
|
362
|
+
if @paused_fiber
|
363
|
+
# Resume the paused fiber
|
364
|
+
@resuming = true
|
365
|
+
begin
|
366
|
+
result = @paused_fiber.resume
|
367
|
+
rescue StandardError => e
|
368
|
+
# Clean up on exception
|
369
|
+
@resuming = false
|
370
|
+
@paused_fiber = nil
|
371
|
+
raise e
|
372
|
+
end
|
373
|
+
@resuming = false
|
374
|
+
|
375
|
+
# Handle different result types
|
376
|
+
case result
|
377
|
+
when Array
|
378
|
+
# Exception occurred inside the fiber
|
379
|
+
if result[0] == :error
|
380
|
+
# Clean up state before re-raising
|
381
|
+
@paused_fiber = nil
|
382
|
+
raise result[1]
|
383
|
+
end
|
384
|
+
else
|
385
|
+
# Normal flow
|
386
|
+
# Check if fiber is still alive after resume
|
387
|
+
if @paused_fiber.alive?
|
388
|
+
# Still paused, keep the fiber
|
389
|
+
true
|
390
|
+
else
|
391
|
+
# Fiber completed
|
392
|
+
@paused_fiber = nil
|
393
|
+
result == :halted
|
394
|
+
end
|
395
|
+
end
|
313
396
|
else
|
314
|
-
|
397
|
+
# Create a new fiber to run the block
|
398
|
+
fiber = Fiber.new do
|
399
|
+
# Mark that we're inside a pausable fiber
|
400
|
+
Thread.current[:state_machine_fiber_pausable] = true
|
401
|
+
begin
|
402
|
+
halted = !catch(:halt) do
|
403
|
+
yield
|
404
|
+
true
|
405
|
+
end
|
406
|
+
halted ? :halted : :completed
|
407
|
+
rescue StandardError => e
|
408
|
+
# Store the exception for re-raising
|
409
|
+
[:error, e]
|
410
|
+
ensure
|
411
|
+
# Clean up the flag
|
412
|
+
Thread.current[:state_machine_fiber_pausable] = false
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
# Run the fiber
|
417
|
+
result = fiber.resume
|
418
|
+
|
419
|
+
# Handle different result types
|
420
|
+
case result
|
421
|
+
when Array
|
422
|
+
# Exception occurred
|
423
|
+
if result[0] == :error
|
424
|
+
# Clean up state before re-raising
|
425
|
+
@paused_fiber = nil
|
426
|
+
raise result[1]
|
427
|
+
end
|
428
|
+
else
|
429
|
+
# Normal flow
|
430
|
+
# Save if paused
|
431
|
+
if fiber.alive?
|
432
|
+
@paused_fiber = fiber
|
433
|
+
# Return true to indicate paused (treated as halted for flow control)
|
434
|
+
true
|
435
|
+
else
|
436
|
+
# Fiber completed, return whether it was halted
|
437
|
+
result == :halted
|
438
|
+
end
|
439
|
+
end
|
315
440
|
end
|
316
441
|
end
|
317
442
|
|
@@ -319,34 +444,22 @@ module StateMachines
|
|
319
444
|
# around callbacks when the remainder of the callback will be executed at
|
320
445
|
# a later point in time.
|
321
446
|
def pause
|
322
|
-
|
323
|
-
|
324
|
-
return if @resume_block
|
325
|
-
|
326
|
-
require 'continuation' unless defined?(callcc)
|
327
|
-
callcc do |block|
|
328
|
-
@paused_block = block
|
329
|
-
throw :halt, true
|
330
|
-
end
|
331
|
-
end
|
447
|
+
# Don't pause if we're in the middle of resuming
|
448
|
+
return if @resuming
|
332
449
|
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
if @paused_block
|
337
|
-
halted, error = callcc do |block|
|
338
|
-
@resume_block = block
|
339
|
-
@paused_block.call
|
340
|
-
end
|
450
|
+
# Only yield if we're actually inside a fiber created by pausable
|
451
|
+
# We use a thread-local variable to track this
|
452
|
+
return unless Thread.current[:state_machine_fiber_pausable]
|
341
453
|
|
342
|
-
|
454
|
+
Fiber.yield
|
343
455
|
|
344
|
-
|
456
|
+
# When we resume from the pause, execute the continuation block if present
|
457
|
+
return unless @continuation_block && !@result
|
345
458
|
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
459
|
+
action = { success: true }.merge(@continuation_block.call)
|
460
|
+
@result = action[:result]
|
461
|
+
@success = action[:success]
|
462
|
+
@continuation_block = nil
|
350
463
|
end
|
351
464
|
|
352
465
|
# Runs the machine's +before+ callbacks for this transition. Only
|
@@ -356,36 +469,51 @@ module StateMachines
|
|
356
469
|
# Once the callbacks are run, they cannot be run again until this transition
|
357
470
|
# is reset.
|
358
471
|
def before(complete = true, index = 0, &block)
|
359
|
-
|
360
|
-
|
361
|
-
|
472
|
+
return if @before_run
|
473
|
+
|
474
|
+
callback = machine.callbacks[:before][index]
|
362
475
|
|
476
|
+
if callback
|
477
|
+
# Check if callback matches this transition using branch
|
478
|
+
if callback.branch.matches?(object, context)
|
363
479
|
if callback.type == :around
|
364
480
|
# Around callback: need to handle recursively. Execution only gets
|
365
481
|
# paused if:
|
366
482
|
# * The block fails and the callback doesn't run on failures OR
|
367
483
|
# * The block succeeds, but after callbacks are disabled (in which
|
368
484
|
# case a continuation is stored for later execution)
|
369
|
-
|
370
|
-
|
371
|
-
|
485
|
+
callback.call(object, context, self) do
|
486
|
+
before(complete, index + 1, &block)
|
487
|
+
|
488
|
+
pause if @success && !complete
|
372
489
|
|
373
|
-
|
374
|
-
|
375
|
-
|
490
|
+
# If the block failed (success is false), we should halt
|
491
|
+
# the around callback from continuing
|
492
|
+
throw :halt unless @success
|
376
493
|
end
|
377
494
|
else
|
378
495
|
# Normal before callback
|
379
496
|
callback.call(object, context, self)
|
497
|
+
# Continue with next callback
|
498
|
+
before(complete, index + 1, &block)
|
380
499
|
end
|
500
|
+
else
|
501
|
+
# Skip to next callback if it doesn't match
|
502
|
+
before(complete, index + 1, &block)
|
503
|
+
end
|
504
|
+
else
|
505
|
+
# No more callbacks, execute the action block if at the end
|
506
|
+
if block_given?
|
507
|
+
action = { success: true }.merge(yield)
|
508
|
+
@result = action[:result]
|
509
|
+
@success = action[:success]
|
510
|
+
else
|
511
|
+
# No action block provided, default to success
|
512
|
+
@success = true
|
381
513
|
end
|
382
514
|
|
383
515
|
@before_run = true
|
384
516
|
end
|
385
|
-
|
386
|
-
action = { success: true }.merge(block_given? ? yield : {})
|
387
|
-
@result = action[:result]
|
388
|
-
@success = action[:success]
|
389
517
|
end
|
390
518
|
|
391
519
|
# Runs the machine's +after+ callbacks for this transition. Only
|
@@ -404,12 +532,9 @@ module StateMachines
|
|
404
532
|
def after
|
405
533
|
return if @after_run
|
406
534
|
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
type = @success ? :after : :failure
|
411
|
-
machine.callbacks[type].each { |callback| callback.call(object, context, self) }
|
412
|
-
end
|
535
|
+
catch(:halt) do
|
536
|
+
type = @success ? :after : :failure
|
537
|
+
machine.callbacks[type].each { |callback| callback.call(object, context, self) }
|
413
538
|
end
|
414
539
|
|
415
540
|
@after_run = true
|
@@ -14,6 +14,9 @@ module StateMachines
|
|
14
14
|
# Whether transitions should wrapped around a transaction block
|
15
15
|
attr_reader :use_transactions
|
16
16
|
|
17
|
+
# Options passed to the collection
|
18
|
+
attr_reader :options
|
19
|
+
|
17
20
|
# Creates a new collection of transitions that can be run in parallel. Each
|
18
21
|
# transition *must* be for a different attribute.
|
19
22
|
#
|
@@ -26,16 +29,23 @@ module StateMachines
|
|
26
29
|
|
27
30
|
# Determine the validity of the transitions as a whole
|
28
31
|
@valid = all?
|
29
|
-
reject!
|
32
|
+
reject!(&:!)
|
30
33
|
|
31
|
-
attributes = map
|
34
|
+
attributes = map(&:attribute).uniq
|
32
35
|
raise ArgumentError, 'Cannot perform multiple transitions in parallel for the same state machine attribute' if attributes.length != length
|
33
36
|
|
34
|
-
StateMachines::OptionsValidator.assert_valid_keys!(options, :actions, :after, :use_transactions)
|
37
|
+
StateMachines::OptionsValidator.assert_valid_keys!(options, :actions, :after, :use_transactions, :fiber)
|
35
38
|
options = { actions: true, after: true, use_transactions: true }.merge(options)
|
36
39
|
@skip_actions = !options[:actions]
|
37
40
|
@skip_after = !options[:after]
|
38
41
|
@use_transactions = options[:use_transactions]
|
42
|
+
@options = options
|
43
|
+
|
44
|
+
# Reset transitions when creating a new collection
|
45
|
+
# But preserve paused transitions to allow resuming
|
46
|
+
each do |transition|
|
47
|
+
transition.reset unless transition.paused?
|
48
|
+
end
|
39
49
|
end
|
40
50
|
|
41
51
|
# Runs each of the collection's transitions in parallel.
|
@@ -104,7 +114,7 @@ module StateMachines
|
|
104
114
|
# Gets the list of actions to run. If configured to skip actions, then
|
105
115
|
# this will return an empty collection.
|
106
116
|
def actions
|
107
|
-
empty? ? [nil] : map
|
117
|
+
empty? ? [nil] : map(&:action).uniq
|
108
118
|
end
|
109
119
|
|
110
120
|
# Determines whether an event attribute be used to trigger the transitions
|
@@ -127,11 +137,21 @@ module StateMachines
|
|
127
137
|
#
|
128
138
|
# If any transition fails to run its callbacks, :halt will be thrown.
|
129
139
|
def run_callbacks(index = 0, &block)
|
130
|
-
if transition = self[index]
|
131
|
-
|
140
|
+
if (transition = self[index])
|
141
|
+
# Pass through any options that affect callback execution (e.g., fiber: false)
|
142
|
+
callback_options = { after: !skip_after }
|
143
|
+
callback_options[:fiber] = options[:fiber] if options.key?(:fiber)
|
144
|
+
|
145
|
+
callback_result = transition.run_callbacks(callback_options) do
|
132
146
|
run_callbacks(index + 1, &block)
|
133
147
|
{ result: results[transition.action], success: success? }
|
134
148
|
end
|
149
|
+
|
150
|
+
# If we're skipping after callbacks and the transition is paused,
|
151
|
+
# consider it successful (the pause was intentional)
|
152
|
+
@success = true if skip_after && transition.paused?
|
153
|
+
|
154
|
+
throw :halt unless callback_result
|
135
155
|
else
|
136
156
|
persist
|
137
157
|
run_actions(&block)
|
@@ -141,7 +161,7 @@ module StateMachines
|
|
141
161
|
# Transitions the current value of the object's states to those specified by
|
142
162
|
# each transition
|
143
163
|
def persist
|
144
|
-
each
|
164
|
+
each(&:persist)
|
145
165
|
end
|
146
166
|
|
147
167
|
# Runs the actions for each transition. If a block is given method, then it
|
@@ -163,7 +183,7 @@ module StateMachines
|
|
163
183
|
|
164
184
|
# Rolls back changes made to the object's states via each transition
|
165
185
|
def rollback
|
166
|
-
each
|
186
|
+
each(&:rollback)
|
167
187
|
end
|
168
188
|
|
169
189
|
# Wraps the given block with a rescue handler so that any exceptions that
|
@@ -202,14 +222,14 @@ module StateMachines
|
|
202
222
|
# Hooks into running transition callbacks so that event / event transition
|
203
223
|
# attributes can be properly updated
|
204
224
|
def run_callbacks(index = 0)
|
205
|
-
if index
|
225
|
+
if index.zero?
|
206
226
|
# Clears any traces of the event attribute to prevent it from being
|
207
227
|
# evaluated multiple times if actions are nested
|
208
228
|
each do |transition|
|
209
229
|
transition.machine.write(object, :event, nil)
|
210
230
|
transition.machine.write(object, :event_transition, nil)
|
211
231
|
end
|
212
|
-
|
232
|
+
|
213
233
|
# Clear stored transitions hash for new cycle (issue #91)
|
214
234
|
if !empty? && (obj = first.object)
|
215
235
|
obj.instance_variable_set(:@_state_machine_event_transitions, nil)
|
@@ -229,9 +249,9 @@ module StateMachines
|
|
229
249
|
# after callbacks.
|
230
250
|
if skip_after && success?
|
231
251
|
each { |transition| transition.machine.write(object, :event_transition, transition) }
|
232
|
-
|
252
|
+
|
233
253
|
# Store transitions in a hash by machine name to avoid overwriting (issue #91)
|
234
|
-
|
254
|
+
unless empty?
|
235
255
|
transitions_by_machine = object.instance_variable_get(:@_state_machine_event_transitions) || {}
|
236
256
|
each { |transition| transitions_by_machine[transition.machine.name] = transition }
|
237
257
|
object.instance_variable_set(:@_state_machine_event_transitions, transitions_by_machine)
|