state_machines 0.20.0 → 0.30.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/README.md +124 -13
- data/lib/state_machines/branch.rb +12 -13
- data/lib/state_machines/callback.rb +11 -12
- data/lib/state_machines/core.rb +0 -1
- data/lib/state_machines/error.rb +5 -4
- data/lib/state_machines/eval_helpers.rb +83 -45
- data/lib/state_machines/event.rb +23 -26
- data/lib/state_machines/event_collection.rb +4 -5
- data/lib/state_machines/extensions.rb +5 -5
- data/lib/state_machines/helper_module.rb +1 -1
- data/lib/state_machines/integrations/base.rb +1 -1
- data/lib/state_machines/integrations.rb +11 -14
- data/lib/state_machines/machine/action_hooks.rb +53 -0
- data/lib/state_machines/machine/callbacks.rb +59 -0
- data/lib/state_machines/machine/class_methods.rb +25 -11
- data/lib/state_machines/machine/configuration.rb +124 -0
- data/lib/state_machines/machine/event_methods.rb +59 -0
- data/lib/state_machines/machine/helper_generators.rb +125 -0
- data/lib/state_machines/machine/integration.rb +70 -0
- data/lib/state_machines/machine/parsing.rb +77 -0
- data/lib/state_machines/machine/rendering.rb +17 -0
- data/lib/state_machines/machine/scoping.rb +44 -0
- data/lib/state_machines/machine/state_methods.rb +101 -0
- data/lib/state_machines/machine/utilities.rb +85 -0
- data/lib/state_machines/machine/validation.rb +39 -0
- data/lib/state_machines/machine.rb +73 -617
- data/lib/state_machines/machine_collection.rb +18 -14
- data/lib/state_machines/macro_methods.rb +2 -2
- data/lib/state_machines/matcher.rb +6 -6
- data/lib/state_machines/matcher_helpers.rb +1 -1
- data/lib/state_machines/node_collection.rb +18 -17
- data/lib/state_machines/path.rb +2 -4
- data/lib/state_machines/path_collection.rb +2 -3
- data/lib/state_machines/state.rb +6 -5
- data/lib/state_machines/state_collection.rb +3 -3
- data/lib/state_machines/state_context.rb +6 -7
- data/lib/state_machines/stdio_renderer.rb +16 -16
- data/lib/state_machines/syntax_validator.rb +57 -0
- data/lib/state_machines/test_helper.rb +290 -27
- data/lib/state_machines/transition.rb +43 -41
- data/lib/state_machines/transition_collection.rb +22 -25
- data/lib/state_machines/version.rb +1 -1
- metadata +23 -9
@@ -29,17 +29,30 @@ module StateMachines
|
|
29
29
|
# Assert that an object is in a specific state for a given state machine
|
30
30
|
#
|
31
31
|
# @param object [Object] The object with state machines
|
32
|
-
# @param machine_name [Symbol] The name of the state machine
|
33
32
|
# @param expected_state [Symbol] The expected state
|
33
|
+
# @param machine_name [Symbol] The name of the state machine (defaults to :state)
|
34
34
|
# @param message [String, nil] Custom failure message
|
35
35
|
# @return [void]
|
36
36
|
# @raise [AssertionError] If the state doesn't match
|
37
37
|
#
|
38
38
|
# @example
|
39
39
|
# user = User.new
|
40
|
-
#
|
41
|
-
|
42
|
-
|
40
|
+
# assert_sm_state(user, :active) # Uses default :state machine
|
41
|
+
# assert_sm_state(user, :active, machine_name: :status) # Uses :status machine
|
42
|
+
def assert_sm_state(object, expected_state, machine_name: :state, message: nil)
|
43
|
+
name_method = "#{machine_name}_name"
|
44
|
+
|
45
|
+
# Handle the case where machine_name doesn't have a corresponding _name method
|
46
|
+
unless object.respond_to?(name_method)
|
47
|
+
available_machines = begin
|
48
|
+
object.class.state_machines.keys
|
49
|
+
rescue StandardError
|
50
|
+
[]
|
51
|
+
end
|
52
|
+
raise ArgumentError, "No state machine '#{machine_name}' found. Available machines: #{available_machines.inspect}"
|
53
|
+
end
|
54
|
+
|
55
|
+
actual = object.send(name_method)
|
43
56
|
default_message = "Expected #{object.class}##{machine_name} to be #{expected_state}, but was #{actual}"
|
44
57
|
|
45
58
|
if defined?(::Minitest)
|
@@ -55,16 +68,30 @@ module StateMachines
|
|
55
68
|
#
|
56
69
|
# @param object [Object] The object with state machines
|
57
70
|
# @param event [Symbol] The event name
|
71
|
+
# @param machine_name [Symbol] The name of the state machine (defaults to :state)
|
58
72
|
# @param message [String, nil] Custom failure message
|
59
73
|
# @return [void]
|
60
74
|
# @raise [AssertionError] If the transition is not available
|
61
75
|
#
|
62
76
|
# @example
|
63
77
|
# user = User.new
|
64
|
-
#
|
65
|
-
|
66
|
-
|
67
|
-
|
78
|
+
# assert_sm_can_transition(user, :activate) # Uses default :state machine
|
79
|
+
# assert_sm_can_transition(user, :activate, machine_name: :status) # Uses :status machine
|
80
|
+
def assert_sm_can_transition(object, event, machine_name: :state, message: nil)
|
81
|
+
# Try different method naming patterns
|
82
|
+
possible_methods = [
|
83
|
+
"can_#{event}?", # Default state machine or non-namespaced
|
84
|
+
"can_#{event}_#{machine_name}?" # Namespaced events
|
85
|
+
]
|
86
|
+
|
87
|
+
can_method = possible_methods.find { |method| object.respond_to?(method) }
|
88
|
+
|
89
|
+
unless can_method
|
90
|
+
available_methods = object.methods.grep(/^can_.*\?$/).sort
|
91
|
+
raise ArgumentError, "No transition method found for event :#{event} on machine :#{machine_name}. Available methods: #{available_methods.first(10).inspect}"
|
92
|
+
end
|
93
|
+
|
94
|
+
default_message = "Expected to be able to trigger event :#{event} on #{machine_name}, but #{can_method} returned false"
|
68
95
|
|
69
96
|
if defined?(::Minitest)
|
70
97
|
assert object.send(can_method), message || default_message
|
@@ -79,16 +106,30 @@ module StateMachines
|
|
79
106
|
#
|
80
107
|
# @param object [Object] The object with state machines
|
81
108
|
# @param event [Symbol] The event name
|
109
|
+
# @param machine_name [Symbol] The name of the state machine (defaults to :state)
|
82
110
|
# @param message [String, nil] Custom failure message
|
83
111
|
# @return [void]
|
84
112
|
# @raise [AssertionError] If the transition is available
|
85
113
|
#
|
86
114
|
# @example
|
87
115
|
# user = User.new
|
88
|
-
#
|
89
|
-
|
90
|
-
|
91
|
-
|
116
|
+
# assert_sm_cannot_transition(user, :delete) # Uses default :state machine
|
117
|
+
# assert_sm_cannot_transition(user, :delete, machine_name: :status) # Uses :status machine
|
118
|
+
def assert_sm_cannot_transition(object, event, machine_name: :state, message: nil)
|
119
|
+
# Try different method naming patterns
|
120
|
+
possible_methods = [
|
121
|
+
"can_#{event}?", # Default state machine or non-namespaced
|
122
|
+
"can_#{event}_#{machine_name}?" # Namespaced events
|
123
|
+
]
|
124
|
+
|
125
|
+
can_method = possible_methods.find { |method| object.respond_to?(method) }
|
126
|
+
|
127
|
+
unless can_method
|
128
|
+
available_methods = object.methods.grep(/^can_.*\?$/).sort
|
129
|
+
raise ArgumentError, "No transition method found for event :#{event} on machine :#{machine_name}. Available methods: #{available_methods.first(10).inspect}"
|
130
|
+
end
|
131
|
+
|
132
|
+
default_message = "Expected not to be able to trigger event :#{event} on #{machine_name}, but #{can_method} returned true"
|
92
133
|
|
93
134
|
if defined?(::Minitest)
|
94
135
|
refute object.send(can_method), message || default_message
|
@@ -103,18 +144,19 @@ module StateMachines
|
|
103
144
|
#
|
104
145
|
# @param object [Object] The object with state machines
|
105
146
|
# @param event [Symbol] The event to trigger
|
106
|
-
# @param machine_name [Symbol] The name of the state machine
|
107
147
|
# @param expected_state [Symbol] The expected state after transition
|
148
|
+
# @param machine_name [Symbol] The name of the state machine (defaults to :state)
|
108
149
|
# @param message [String, nil] Custom failure message
|
109
150
|
# @return [void]
|
110
151
|
# @raise [AssertionError] If the transition fails or results in wrong state
|
111
152
|
#
|
112
153
|
# @example
|
113
154
|
# user = User.new
|
114
|
-
#
|
115
|
-
|
155
|
+
# assert_sm_transition(user, :activate, :active) # Uses default :state machine
|
156
|
+
# assert_sm_transition(user, :activate, :active, machine_name: :status) # Uses :status machine
|
157
|
+
def assert_sm_transition(object, event, expected_state, machine_name: :state, message: nil)
|
116
158
|
object.send("#{event}!")
|
117
|
-
|
159
|
+
assert_sm_state(object, expected_state, machine_name: machine_name, message: message)
|
118
160
|
end
|
119
161
|
|
120
162
|
# === Extended State Machine Assertions ===
|
@@ -205,11 +247,11 @@ module StateMachines
|
|
205
247
|
end
|
206
248
|
alias assert_sm_transition_not_allowed refute_sm_transition_allowed
|
207
249
|
|
208
|
-
def assert_sm_event_triggers(object, event, message = nil)
|
209
|
-
initial_state = object.
|
250
|
+
def assert_sm_event_triggers(object, event, machine_name = :state, message = nil)
|
251
|
+
initial_state = object.send(machine_name)
|
210
252
|
object.send("#{event}!")
|
211
|
-
state_changed = initial_state != object.
|
212
|
-
default_message = "Expected event #{event} to trigger state change"
|
253
|
+
state_changed = initial_state != object.send(machine_name)
|
254
|
+
default_message = "Expected event #{event} to trigger state change on #{machine_name}"
|
213
255
|
|
214
256
|
if defined?(::Minitest)
|
215
257
|
assert state_changed, message || default_message
|
@@ -220,12 +262,12 @@ module StateMachines
|
|
220
262
|
end
|
221
263
|
end
|
222
264
|
|
223
|
-
def refute_sm_event_triggers(object, event, message = nil)
|
224
|
-
initial_state = object.
|
265
|
+
def refute_sm_event_triggers(object, event, machine_name = :state, message = nil)
|
266
|
+
initial_state = object.send(machine_name)
|
225
267
|
begin
|
226
268
|
object.send("#{event}!")
|
227
|
-
state_unchanged = initial_state == object.
|
228
|
-
default_message = "Expected event #{event} to not trigger state change"
|
269
|
+
state_unchanged = initial_state == object.send(machine_name)
|
270
|
+
default_message = "Expected event #{event} to not trigger state change on #{machine_name}"
|
229
271
|
|
230
272
|
if defined?(::Minitest)
|
231
273
|
assert state_unchanged, message || default_message
|
@@ -288,10 +330,26 @@ module StateMachines
|
|
288
330
|
end
|
289
331
|
alias assert_sm_callback_not_executed refute_sm_callback_executed
|
290
332
|
|
291
|
-
|
333
|
+
# Assert that a record's state is persisted correctly for a specific state machine
|
334
|
+
#
|
335
|
+
# @param record [Object] The record to check (should respond to reload)
|
336
|
+
# @param expected [String, Symbol] The expected persisted state
|
337
|
+
# @param machine_name [Symbol] The name of the state machine (defaults to :state)
|
338
|
+
# @param message [String, nil] Custom failure message
|
339
|
+
# @return [void]
|
340
|
+
# @raise [AssertionError] If the persisted state doesn't match
|
341
|
+
#
|
342
|
+
# @example
|
343
|
+
# # Default state machine
|
344
|
+
# assert_sm_state_persisted(user, "active")
|
345
|
+
#
|
346
|
+
# # Specific state machine
|
347
|
+
# assert_sm_state_persisted(ship, "up", :shields)
|
348
|
+
# assert_sm_state_persisted(ship, "armed", :weapons)
|
349
|
+
def assert_sm_state_persisted(record, expected, machine_name = :state, message = nil)
|
292
350
|
record.reload if record.respond_to?(:reload)
|
293
|
-
actual_state = record.
|
294
|
-
default_message = "Expected persisted state #{expected} but got #{actual_state}"
|
351
|
+
actual_state = record.send(machine_name)
|
352
|
+
default_message = "Expected persisted state #{expected} for #{machine_name} but got #{actual_state}"
|
295
353
|
|
296
354
|
if defined?(::Minitest)
|
297
355
|
assert_equal expected, actual_state, message || default_message
|
@@ -301,5 +359,210 @@ module StateMachines
|
|
301
359
|
raise default_message unless expected == actual_state
|
302
360
|
end
|
303
361
|
end
|
362
|
+
|
363
|
+
# Assert that executing a block triggers one or more expected events
|
364
|
+
#
|
365
|
+
# @param object [Object] The object with state machines
|
366
|
+
# @param expected_events [Symbol, Array<Symbol>] The event(s) expected to be triggered
|
367
|
+
# @param machine_name [Symbol] The name of the state machine (defaults to :state)
|
368
|
+
# @param message [String, nil] Custom failure message
|
369
|
+
# @return [void]
|
370
|
+
# @raise [AssertionError] If the expected events were not triggered
|
371
|
+
#
|
372
|
+
# @example
|
373
|
+
# # Single event
|
374
|
+
# assert_sm_triggers_event(vehicle, :crash) { vehicle.redline }
|
375
|
+
#
|
376
|
+
# # Multiple events
|
377
|
+
# assert_sm_triggers_event(vehicle, [:crash, :emergency]) { vehicle.emergency_stop }
|
378
|
+
#
|
379
|
+
# # Specific machine
|
380
|
+
# assert_sm_triggers_event(vehicle, :disable, machine_name: :alarm) { vehicle.turn_off_alarm }
|
381
|
+
def assert_sm_triggers_event(object, expected_events, machine_name: :state, message: nil)
|
382
|
+
expected_events = Array(expected_events)
|
383
|
+
triggered_events = []
|
384
|
+
|
385
|
+
# Get the state machine
|
386
|
+
machine = object.class.state_machines[machine_name]
|
387
|
+
raise ArgumentError, "No state machine found for #{machine_name}" unless machine
|
388
|
+
|
389
|
+
# Save original callbacks to restore later
|
390
|
+
machine.callbacks[:before].dup
|
391
|
+
|
392
|
+
# Add a temporary callback to track triggered events
|
393
|
+
temp_callback = machine.before_transition do |_obj, transition|
|
394
|
+
triggered_events << transition.event if transition.event
|
395
|
+
end
|
396
|
+
|
397
|
+
begin
|
398
|
+
# Execute the block
|
399
|
+
yield
|
400
|
+
|
401
|
+
# Check if expected events were triggered
|
402
|
+
missing_events = expected_events - triggered_events
|
403
|
+
extra_events = triggered_events - expected_events
|
404
|
+
|
405
|
+
unless missing_events.empty? && extra_events.empty?
|
406
|
+
default_message = "Expected events #{expected_events.inspect} to be triggered, but got #{triggered_events.inspect}"
|
407
|
+
default_message += ". Missing: #{missing_events.inspect}" if missing_events.any?
|
408
|
+
default_message += ". Extra: #{extra_events.inspect}" if extra_events.any?
|
409
|
+
|
410
|
+
if defined?(::Minitest)
|
411
|
+
assert false, message || default_message
|
412
|
+
elsif defined?(::RSpec)
|
413
|
+
raise message || default_message
|
414
|
+
else
|
415
|
+
raise default_message
|
416
|
+
end
|
417
|
+
end
|
418
|
+
ensure
|
419
|
+
# Restore original callbacks by removing the temporary one
|
420
|
+
machine.callbacks[:before].delete(temp_callback)
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
# Assert that a before_transition callback is defined with expected arguments
|
425
|
+
#
|
426
|
+
# @param machine_or_class [StateMachines::Machine, Class] The machine or class to check
|
427
|
+
# @param options [Hash] Expected callback options (on:, from:, to:, do:, if:, unless:)
|
428
|
+
# @param message [String, nil] Custom failure message
|
429
|
+
# @return [void]
|
430
|
+
# @raise [AssertionError] If the callback is not defined
|
431
|
+
#
|
432
|
+
# @example
|
433
|
+
# # Check for specific transition callback
|
434
|
+
# assert_before_transition(Vehicle, on: :crash, do: :emergency_stop)
|
435
|
+
#
|
436
|
+
# # Check with from/to states
|
437
|
+
# assert_before_transition(Vehicle.state_machine, from: :parked, to: :idling, do: :start_engine)
|
438
|
+
#
|
439
|
+
# # Check with conditions
|
440
|
+
# assert_before_transition(Vehicle, on: :ignite, if: :seatbelt_on?)
|
441
|
+
def assert_before_transition(machine_or_class, options = {}, message = nil)
|
442
|
+
_assert_transition_callback(:before, machine_or_class, options, message)
|
443
|
+
end
|
444
|
+
|
445
|
+
# Assert that an after_transition callback is defined with expected arguments
|
446
|
+
#
|
447
|
+
# @param machine_or_class [StateMachines::Machine, Class] The machine or class to check
|
448
|
+
# @param options [Hash] Expected callback options (on:, from:, to:, do:, if:, unless:)
|
449
|
+
# @param message [String, nil] Custom failure message
|
450
|
+
# @return [void]
|
451
|
+
# @raise [AssertionError] If the callback is not defined
|
452
|
+
#
|
453
|
+
# @example
|
454
|
+
# # Check for specific transition callback
|
455
|
+
# assert_after_transition(Vehicle, on: :crash, do: :tow)
|
456
|
+
#
|
457
|
+
# # Check with from/to states
|
458
|
+
# assert_after_transition(Vehicle.state_machine, from: :stalled, to: :parked, do: :log_repair)
|
459
|
+
def assert_after_transition(machine_or_class, options = {}, message = nil)
|
460
|
+
_assert_transition_callback(:after, machine_or_class, options, message)
|
461
|
+
end
|
462
|
+
|
463
|
+
# RSpec-style aliases for event triggering (for consistency with RSpec expectations)
|
464
|
+
alias expect_to_trigger_event assert_sm_triggers_event
|
465
|
+
alias have_triggered_event assert_sm_triggers_event
|
466
|
+
|
467
|
+
private
|
468
|
+
|
469
|
+
# Internal helper for checking transition callbacks
|
470
|
+
def _assert_transition_callback(callback_type, machine_or_class, options, message)
|
471
|
+
# Get the machine
|
472
|
+
machine = machine_or_class.is_a?(StateMachines::Machine) ? machine_or_class : machine_or_class.state_machine
|
473
|
+
raise ArgumentError, 'No state machine found' unless machine
|
474
|
+
|
475
|
+
callbacks = machine.callbacks[callback_type] || []
|
476
|
+
|
477
|
+
# Extract expected conditions
|
478
|
+
expected_event = options[:on]
|
479
|
+
expected_from = options[:from]
|
480
|
+
expected_to = options[:to]
|
481
|
+
expected_method = options[:do]
|
482
|
+
expected_if = options[:if]
|
483
|
+
expected_unless = options[:unless]
|
484
|
+
|
485
|
+
# Find matching callback
|
486
|
+
matching_callback = callbacks.find do |callback|
|
487
|
+
branch = callback.branch
|
488
|
+
|
489
|
+
# Check event requirement
|
490
|
+
if expected_event
|
491
|
+
event_requirement = branch.event_requirement
|
492
|
+
event_matches = if event_requirement && event_requirement.respond_to?(:values)
|
493
|
+
event_requirement.values.include?(expected_event)
|
494
|
+
else
|
495
|
+
false
|
496
|
+
end
|
497
|
+
next false unless event_matches
|
498
|
+
end
|
499
|
+
|
500
|
+
# Check state requirements (from/to)
|
501
|
+
if expected_from || expected_to
|
502
|
+
state_matches = false
|
503
|
+
branch.state_requirements.each do |req|
|
504
|
+
from_matches = !expected_from || (req[:from] && req[:from].respond_to?(:values) && req[:from].values.include?(expected_from))
|
505
|
+
to_matches = !expected_to || (req[:to] && req[:to].respond_to?(:values) && req[:to].values.include?(expected_to))
|
506
|
+
|
507
|
+
if from_matches && to_matches
|
508
|
+
state_matches = true
|
509
|
+
break
|
510
|
+
end
|
511
|
+
end
|
512
|
+
next false unless state_matches
|
513
|
+
end
|
514
|
+
|
515
|
+
# Check method requirement
|
516
|
+
if expected_method
|
517
|
+
methods = callback.instance_variable_get(:@methods) || []
|
518
|
+
method_matches = methods.any? do |method|
|
519
|
+
(method.is_a?(Symbol) && method == expected_method) ||
|
520
|
+
(method.is_a?(String) && method.to_sym == expected_method) ||
|
521
|
+
(method.respond_to?(:call) && method.respond_to?(:source_location))
|
522
|
+
end
|
523
|
+
next false unless method_matches
|
524
|
+
end
|
525
|
+
|
526
|
+
# Check if condition
|
527
|
+
if expected_if
|
528
|
+
if_condition = branch.if_condition
|
529
|
+
if_matches = (if_condition.is_a?(Symbol) && if_condition == expected_if) ||
|
530
|
+
(if_condition.is_a?(String) && if_condition.to_sym == expected_if) ||
|
531
|
+
if_condition.respond_to?(:call)
|
532
|
+
next false unless if_matches
|
533
|
+
end
|
534
|
+
|
535
|
+
# Check unless condition
|
536
|
+
if expected_unless
|
537
|
+
unless_condition = branch.unless_condition
|
538
|
+
unless_matches = (unless_condition.is_a?(Symbol) && unless_condition == expected_unless) ||
|
539
|
+
(unless_condition.is_a?(String) && unless_condition.to_sym == expected_unless) ||
|
540
|
+
unless_condition.respond_to?(:call)
|
541
|
+
next false unless unless_matches
|
542
|
+
end
|
543
|
+
|
544
|
+
true
|
545
|
+
end
|
546
|
+
|
547
|
+
return if matching_callback
|
548
|
+
|
549
|
+
expected_parts = []
|
550
|
+
expected_parts << "on: #{expected_event.inspect}" if expected_event
|
551
|
+
expected_parts << "from: #{expected_from.inspect}" if expected_from
|
552
|
+
expected_parts << "to: #{expected_to.inspect}" if expected_to
|
553
|
+
expected_parts << "do: #{expected_method.inspect}" if expected_method
|
554
|
+
expected_parts << "if: #{expected_if.inspect}" if expected_if
|
555
|
+
expected_parts << "unless: #{expected_unless.inspect}" if expected_unless
|
556
|
+
|
557
|
+
default_message = "Expected #{callback_type}_transition callback with #{expected_parts.join(', ')} to be defined, but it was not found"
|
558
|
+
|
559
|
+
if defined?(::Minitest)
|
560
|
+
assert false, message || default_message
|
561
|
+
elsif defined?(::RSpec)
|
562
|
+
raise message || default_message
|
563
|
+
else
|
564
|
+
raise default_message
|
565
|
+
end
|
566
|
+
end
|
304
567
|
end
|
305
568
|
end
|
@@ -33,11 +33,11 @@ module StateMachines
|
|
33
33
|
# Determines whether the current ruby implementation supports pausing and
|
34
34
|
# resuming transitions
|
35
35
|
def self.pause_supported?
|
36
|
-
%w
|
36
|
+
%w[ruby maglev].include?(RUBY_ENGINE)
|
37
37
|
end
|
38
38
|
|
39
39
|
# Creates a new, specific transition
|
40
|
-
def initialize(object, machine, event, from_name, to_name, read_state = true)
|
40
|
+
def initialize(object, machine, event, from_name, to_name, read_state = true) # :nodoc:
|
41
41
|
@object = object
|
42
42
|
@machine = machine
|
43
43
|
@args = []
|
@@ -136,7 +136,7 @@ module StateMachines
|
|
136
136
|
# transition = StateMachines::Transition.new(Vehicle.new, machine, :ignite, :parked, :idling)
|
137
137
|
# transition.attributes # => {:object => #<Vehicle:0xb7d60ea4>, :attribute => :state, :event => :ignite, :from => 'parked', :to => 'idling'}
|
138
138
|
def attributes
|
139
|
-
@attributes ||= {object: object, attribute: attribute, event: event, from: from, to: to}
|
139
|
+
@attributes ||= { object: object, attribute: attribute, event: event, from: from, to: to }
|
140
140
|
end
|
141
141
|
|
142
142
|
# Runs the actual transition and any before/after callbacks associated
|
@@ -162,16 +162,14 @@ module StateMachines
|
|
162
162
|
self.args = args
|
163
163
|
|
164
164
|
# Run the transition
|
165
|
-
!!TransitionCollection.new([self], {use_transactions: machine.use_transactions, actions: run_action}).perform
|
165
|
+
!!TransitionCollection.new([self], { use_transactions: machine.use_transactions, actions: run_action }).perform
|
166
166
|
end
|
167
167
|
|
168
168
|
# Runs a block within a transaction for the object being transitioned.
|
169
169
|
# By default, transactions are a no-op unless otherwise defined by the
|
170
170
|
# machine's integration.
|
171
|
-
def within_transaction
|
172
|
-
machine.within_transaction(object)
|
173
|
-
yield
|
174
|
-
end
|
171
|
+
def within_transaction(&)
|
172
|
+
machine.within_transaction(object, &)
|
175
173
|
end
|
176
174
|
|
177
175
|
# Runs the before / after callbacks for this transition. If a block is
|
@@ -186,7 +184,7 @@ module StateMachines
|
|
186
184
|
# This will return true if all before callbacks gets executed. After
|
187
185
|
# callbacks will not have an effect on the result.
|
188
186
|
def run_callbacks(options = {}, &block)
|
189
|
-
options = {before: true, after: true}.merge(options)
|
187
|
+
options = { before: true, after: true }.merge(options)
|
190
188
|
@success = false
|
191
189
|
|
192
190
|
halted = pausable { before(options[:after], &block) } if options[:before]
|
@@ -219,10 +217,10 @@ module StateMachines
|
|
219
217
|
#
|
220
218
|
# vehicle.state # => 'idling'
|
221
219
|
def persist
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
220
|
+
return if @persisted
|
221
|
+
|
222
|
+
machine.write(object, :state, to)
|
223
|
+
@persisted = true
|
226
224
|
end
|
227
225
|
|
228
226
|
# Rolls back changes made to the object's state via this transition. This
|
@@ -265,11 +263,11 @@ module StateMachines
|
|
265
263
|
# and event involved in the transition are equal
|
266
264
|
def ==(other)
|
267
265
|
other.instance_of?(self.class) &&
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
266
|
+
other.object == object &&
|
267
|
+
other.machine == machine &&
|
268
|
+
other.from_name == from_name &&
|
269
|
+
other.to_name == to_name &&
|
270
|
+
other.event == event
|
273
271
|
end
|
274
272
|
|
275
273
|
# Generates a nicely formatted description of this transitions's contents.
|
@@ -279,10 +277,10 @@ module StateMachines
|
|
279
277
|
# transition = StateMachines::Transition.new(object, machine, :ignite, :parked, :idling)
|
280
278
|
# transition # => #<StateMachines::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>
|
281
279
|
def inspect
|
282
|
-
"#<#{self.class} #{%w
|
280
|
+
"#<#{self.class} #{%w[attribute event from from_name to to_name].map { |attr| "#{attr}=#{send(attr).inspect}" } * ' '}>"
|
283
281
|
end
|
284
282
|
|
285
|
-
|
283
|
+
private
|
286
284
|
|
287
285
|
# Runs a block that may get paused. If the block doesn't pause, then
|
288
286
|
# execution will continue as normal. If the block gets paused, then it
|
@@ -292,13 +290,16 @@ module StateMachines
|
|
292
290
|
# getting paused.
|
293
291
|
def pausable
|
294
292
|
begin
|
295
|
-
halted = !catch(:halt)
|
296
|
-
|
293
|
+
halted = !catch(:halt) do
|
294
|
+
yield
|
295
|
+
true
|
296
|
+
end
|
297
|
+
rescue StandardError => e
|
297
298
|
raise unless @resume_block
|
298
299
|
end
|
299
300
|
|
300
301
|
if @resume_block
|
301
|
-
@resume_block.call(halted,
|
302
|
+
@resume_block.call(halted, e)
|
302
303
|
else
|
303
304
|
halted
|
304
305
|
end
|
@@ -310,12 +311,12 @@ module StateMachines
|
|
310
311
|
def pause
|
311
312
|
raise ArgumentError, 'around_transition callbacks cannot be called in multiple execution contexts in java implementations of Ruby. Use before/after_transitions instead.' unless self.class.pause_supported?
|
312
313
|
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
314
|
+
return if @resume_block
|
315
|
+
|
316
|
+
require 'continuation' unless defined?(callcc)
|
317
|
+
callcc do |block|
|
318
|
+
@paused_block = block
|
319
|
+
throw :halt, true
|
319
320
|
end
|
320
321
|
end
|
321
322
|
|
@@ -372,8 +373,9 @@ module StateMachines
|
|
372
373
|
@before_run = true
|
373
374
|
end
|
374
375
|
|
375
|
-
action = {success: true}.merge(block_given? ? yield : {})
|
376
|
-
@result
|
376
|
+
action = { success: true }.merge(block_given? ? yield : {})
|
377
|
+
@result = action[:result]
|
378
|
+
@success = action[:success]
|
377
379
|
end
|
378
380
|
|
379
381
|
# Runs the machine's +after+ callbacks for this transition. Only
|
@@ -390,17 +392,17 @@ module StateMachines
|
|
390
392
|
# exception will not bubble up to the caller since +after+ callbacks
|
391
393
|
# should never halt the execution of a +perform+.
|
392
394
|
def after
|
393
|
-
|
394
|
-
# First resume previously paused callbacks
|
395
|
-
if resume
|
396
|
-
catch(:halt) do
|
397
|
-
type = @success ? :after : :failure
|
398
|
-
machine.callbacks[type].each { |callback| callback.call(object, context, self) }
|
399
|
-
end
|
400
|
-
end
|
395
|
+
return if @after_run
|
401
396
|
|
402
|
-
|
397
|
+
# First resume previously paused callbacks
|
398
|
+
if resume
|
399
|
+
catch(:halt) do
|
400
|
+
type = @success ? :after : :failure
|
401
|
+
machine.callbacks[type].each { |callback| callback.call(object, context, self) }
|
402
|
+
end
|
403
403
|
end
|
404
|
+
|
405
|
+
@after_run = true
|
404
406
|
end
|
405
407
|
|
406
408
|
# Gets a hash of the context defining this unique transition (including
|
@@ -412,7 +414,7 @@ module StateMachines
|
|
412
414
|
# transition = StateMachines::Transition.new(Vehicle.new, machine, :ignite, :parked, :idling)
|
413
415
|
# transition.context # => {:on => :ignite, :from => :parked, :to => :idling}
|
414
416
|
def context
|
415
|
-
@context ||= {on: event, from: from_name, to: to_name}
|
417
|
+
@context ||= { on: event, from: from_name, to: to_name }
|
416
418
|
end
|
417
419
|
end
|
418
420
|
end
|