state_machines 0.20.0 → 0.31.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +154 -18
  3. data/lib/state_machines/branch.rb +30 -17
  4. data/lib/state_machines/callback.rb +12 -13
  5. data/lib/state_machines/core.rb +0 -1
  6. data/lib/state_machines/error.rb +5 -4
  7. data/lib/state_machines/eval_helpers.rb +178 -49
  8. data/lib/state_machines/event.rb +31 -32
  9. data/lib/state_machines/event_collection.rb +4 -5
  10. data/lib/state_machines/extensions.rb +5 -5
  11. data/lib/state_machines/helper_module.rb +1 -1
  12. data/lib/state_machines/integrations/base.rb +1 -1
  13. data/lib/state_machines/integrations.rb +11 -14
  14. data/lib/state_machines/machine/action_hooks.rb +53 -0
  15. data/lib/state_machines/machine/callbacks.rb +59 -0
  16. data/lib/state_machines/machine/class_methods.rb +25 -11
  17. data/lib/state_machines/machine/configuration.rb +124 -0
  18. data/lib/state_machines/machine/event_methods.rb +59 -0
  19. data/lib/state_machines/machine/helper_generators.rb +125 -0
  20. data/lib/state_machines/machine/integration.rb +70 -0
  21. data/lib/state_machines/machine/parsing.rb +77 -0
  22. data/lib/state_machines/machine/rendering.rb +17 -0
  23. data/lib/state_machines/machine/scoping.rb +44 -0
  24. data/lib/state_machines/machine/state_methods.rb +101 -0
  25. data/lib/state_machines/machine/utilities.rb +85 -0
  26. data/lib/state_machines/machine/validation.rb +39 -0
  27. data/lib/state_machines/machine.rb +75 -619
  28. data/lib/state_machines/machine_collection.rb +18 -14
  29. data/lib/state_machines/macro_methods.rb +2 -2
  30. data/lib/state_machines/matcher.rb +6 -6
  31. data/lib/state_machines/matcher_helpers.rb +1 -1
  32. data/lib/state_machines/node_collection.rb +18 -17
  33. data/lib/state_machines/path.rb +2 -4
  34. data/lib/state_machines/path_collection.rb +2 -3
  35. data/lib/state_machines/state.rb +14 -7
  36. data/lib/state_machines/state_collection.rb +3 -3
  37. data/lib/state_machines/state_context.rb +6 -7
  38. data/lib/state_machines/stdio_renderer.rb +16 -16
  39. data/lib/state_machines/syntax_validator.rb +57 -0
  40. data/lib/state_machines/test_helper.rb +290 -27
  41. data/lib/state_machines/transition.rb +57 -46
  42. data/lib/state_machines/transition_collection.rb +22 -25
  43. data/lib/state_machines/version.rb +1 -1
  44. 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
- # assert_state(user, :status, :active)
41
- def assert_state(object, machine_name, expected_state, message = nil)
42
- actual = object.send("#{machine_name}_name")
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
- # assert_can_transition(user, :activate)
65
- def assert_can_transition(object, event, message = nil)
66
- can_method = "can_#{event}?"
67
- default_message = "Expected to be able to trigger event :#{event}, but #{can_method} returned false"
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
- # assert_cannot_transition(user, :delete)
89
- def assert_cannot_transition(object, event, message = nil)
90
- can_method = "can_#{event}?"
91
- default_message = "Expected not to be able to trigger event :#{event}, but #{can_method} returned true"
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
- # assert_transition(user, :activate, :status, :active)
115
- def assert_transition(object, event, machine_name, expected_state, message = nil)
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
- assert_state(object, machine_name, expected_state, message)
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.state
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.state
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.state
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.state
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
- def assert_sm_state_persisted(record, expected:, message: nil)
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.state
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(ruby maglev).include?(RUBY_ENGINE)
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) #:nodoc:
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
@@ -153,25 +153,32 @@ module StateMachines
153
153
  #
154
154
  # vehicle = Vehicle.new
155
155
  # transition = StateMachines::Transition.new(vehicle, machine, :ignite, :parked, :idling)
156
- # transition.perform # => Runs the +save+ action after setting the state attribute
157
- # transition.perform(false) # => Only sets the state attribute
158
- # transition.perform(Time.now) # => Passes in additional arguments and runs the +save+ action
159
- # transition.perform(Time.now, false) # => Passes in additional arguments and only sets the state attribute
156
+ # transition.perform # => Runs the +save+ action after setting the state attribute
157
+ # transition.perform(false) # => Only sets the state attribute
158
+ # transition.perform(run_action: false) # => Only sets the state attribute
159
+ # transition.perform(Time.now) # => Passes in additional arguments and runs the +save+ action
160
+ # transition.perform(Time.now, false) # => Passes in additional arguments and only sets the state attribute
161
+ # transition.perform(Time.now, run_action: false) # => Passes in additional arguments and only sets the state attribute
160
162
  def perform(*args)
161
- run_action = [true, false].include?(args.last) ? args.pop : true
163
+ run_action = true
164
+
165
+ if [true, false].include?(args.last)
166
+ run_action = args.pop
167
+ elsif args.last.is_a?(Hash) && args.last.key?(:run_action)
168
+ run_action = args.last.delete(:run_action)
169
+ end
170
+
162
171
  self.args = args
163
172
 
164
173
  # Run the transition
165
- !!TransitionCollection.new([self], {use_transactions: machine.use_transactions, actions: run_action}).perform
174
+ !!TransitionCollection.new([self], { use_transactions: machine.use_transactions, actions: run_action }).perform
166
175
  end
167
176
 
168
177
  # Runs a block within a transaction for the object being transitioned.
169
178
  # By default, transactions are a no-op unless otherwise defined by the
170
179
  # machine's integration.
171
- def within_transaction
172
- machine.within_transaction(object) do
173
- yield
174
- end
180
+ def within_transaction(&)
181
+ machine.within_transaction(object, &)
175
182
  end
176
183
 
177
184
  # Runs the before / after callbacks for this transition. If a block is
@@ -186,7 +193,7 @@ module StateMachines
186
193
  # This will return true if all before callbacks gets executed. After
187
194
  # callbacks will not have an effect on the result.
188
195
  def run_callbacks(options = {}, &block)
189
- options = {before: true, after: true}.merge(options)
196
+ options = { before: true, after: true }.merge(options)
190
197
  @success = false
191
198
 
192
199
  halted = pausable { before(options[:after], &block) } if options[:before]
@@ -219,10 +226,10 @@ module StateMachines
219
226
  #
220
227
  # vehicle.state # => 'idling'
221
228
  def persist
222
- unless @persisted
223
- machine.write(object, :state, to)
224
- @persisted = true
225
- end
229
+ return if @persisted
230
+
231
+ machine.write(object, :state, to)
232
+ @persisted = true
226
233
  end
227
234
 
228
235
  # Rolls back changes made to the object's state via this transition. This
@@ -265,11 +272,11 @@ module StateMachines
265
272
  # and event involved in the transition are equal
266
273
  def ==(other)
267
274
  other.instance_of?(self.class) &&
268
- other.object == object &&
269
- other.machine == machine &&
270
- other.from_name == from_name &&
271
- other.to_name == to_name &&
272
- other.event == event
275
+ other.object == object &&
276
+ other.machine == machine &&
277
+ other.from_name == from_name &&
278
+ other.to_name == to_name &&
279
+ other.event == event
273
280
  end
274
281
 
275
282
  # Generates a nicely formatted description of this transitions's contents.
@@ -279,10 +286,10 @@ module StateMachines
279
286
  # transition = StateMachines::Transition.new(object, machine, :ignite, :parked, :idling)
280
287
  # transition # => #<StateMachines::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>
281
288
  def inspect
282
- "#<#{self.class} #{%w(attribute event from from_name to to_name).map { |attr| "#{attr}=#{send(attr).inspect}" } * ' '}>"
289
+ "#<#{self.class} #{%w[attribute event from from_name to to_name].map { |attr| "#{attr}=#{send(attr).inspect}" } * ' '}>"
283
290
  end
284
291
 
285
- private
292
+ private
286
293
 
287
294
  # Runs a block that may get paused. If the block doesn't pause, then
288
295
  # execution will continue as normal. If the block gets paused, then it
@@ -292,13 +299,16 @@ module StateMachines
292
299
  # getting paused.
293
300
  def pausable
294
301
  begin
295
- halted = !catch(:halt) { yield; true }
296
- rescue => error
302
+ halted = !catch(:halt) do
303
+ yield
304
+ true
305
+ end
306
+ rescue StandardError => e
297
307
  raise unless @resume_block
298
308
  end
299
309
 
300
310
  if @resume_block
301
- @resume_block.call(halted, error)
311
+ @resume_block.call(halted, e)
302
312
  else
303
313
  halted
304
314
  end
@@ -310,12 +320,12 @@ module StateMachines
310
320
  def pause
311
321
  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
322
 
313
- unless @resume_block
314
- require 'continuation' unless defined?(callcc)
315
- callcc do |block|
316
- @paused_block = block
317
- throw :halt, true
318
- end
323
+ return if @resume_block
324
+
325
+ require 'continuation' unless defined?(callcc)
326
+ callcc do |block|
327
+ @paused_block = block
328
+ throw :halt, true
319
329
  end
320
330
  end
321
331
 
@@ -372,8 +382,9 @@ module StateMachines
372
382
  @before_run = true
373
383
  end
374
384
 
375
- action = {success: true}.merge(block_given? ? yield : {})
376
- @result, @success = action[:result], action[:success]
385
+ action = { success: true }.merge(block_given? ? yield : {})
386
+ @result = action[:result]
387
+ @success = action[:success]
377
388
  end
378
389
 
379
390
  # Runs the machine's +after+ callbacks for this transition. Only
@@ -390,17 +401,17 @@ module StateMachines
390
401
  # exception will not bubble up to the caller since +after+ callbacks
391
402
  # should never halt the execution of a +perform+.
392
403
  def after
393
- unless @after_run
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
404
+ return if @after_run
401
405
 
402
- @after_run = true
406
+ # First resume previously paused callbacks
407
+ if resume
408
+ catch(:halt) do
409
+ type = @success ? :after : :failure
410
+ machine.callbacks[type].each { |callback| callback.call(object, context, self) }
411
+ end
403
412
  end
413
+
414
+ @after_run = true
404
415
  end
405
416
 
406
417
  # Gets a hash of the context defining this unique transition (including
@@ -412,7 +423,7 @@ module StateMachines
412
423
  # transition = StateMachines::Transition.new(Vehicle.new, machine, :ignite, :parked, :idling)
413
424
  # transition.context # => {:on => :ignite, :from => :parked, :to => :idling}
414
425
  def context
415
- @context ||= {on: event, from: from_name, to: to_name}
426
+ @context ||= { on: event, from: from_name, to: to_name }
416
427
  end
417
428
  end
418
429
  end