state_machines 0.20.0 → 0.100.4

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +356 -18
  3. data/lib/state_machines/async_mode/async_event_extensions.rb +49 -0
  4. data/lib/state_machines/async_mode/async_events.rb +282 -0
  5. data/lib/state_machines/async_mode/async_machine.rb +60 -0
  6. data/lib/state_machines/async_mode/async_transition_collection.rb +141 -0
  7. data/lib/state_machines/async_mode/thread_safe_state.rb +47 -0
  8. data/lib/state_machines/async_mode.rb +64 -0
  9. data/lib/state_machines/branch.rb +74 -17
  10. data/lib/state_machines/callback.rb +14 -14
  11. data/lib/state_machines/core.rb +0 -1
  12. data/lib/state_machines/error.rb +5 -4
  13. data/lib/state_machines/eval_helpers.rb +176 -49
  14. data/lib/state_machines/event.rb +36 -35
  15. data/lib/state_machines/event_collection.rb +24 -17
  16. data/lib/state_machines/extensions.rb +5 -5
  17. data/lib/state_machines/helper_module.rb +1 -1
  18. data/lib/state_machines/integrations/base.rb +7 -1
  19. data/lib/state_machines/integrations.rb +11 -14
  20. data/lib/state_machines/machine/action_hooks.rb +53 -0
  21. data/lib/state_machines/machine/async_extensions.rb +88 -0
  22. data/lib/state_machines/machine/callbacks.rb +59 -0
  23. data/lib/state_machines/machine/class_methods.rb +27 -11
  24. data/lib/state_machines/machine/configuration.rb +136 -0
  25. data/lib/state_machines/machine/event_methods.rb +59 -0
  26. data/lib/state_machines/machine/helper_generators.rb +125 -0
  27. data/lib/state_machines/machine/integration.rb +70 -0
  28. data/lib/state_machines/machine/parsing.rb +77 -0
  29. data/lib/state_machines/machine/rendering.rb +17 -0
  30. data/lib/state_machines/machine/scoping.rb +44 -0
  31. data/lib/state_machines/machine/state_methods.rb +101 -0
  32. data/lib/state_machines/machine/utilities.rb +85 -0
  33. data/lib/state_machines/machine/validation.rb +39 -0
  34. data/lib/state_machines/machine.rb +74 -619
  35. data/lib/state_machines/machine_collection.rb +18 -14
  36. data/lib/state_machines/macro_methods.rb +2 -2
  37. data/lib/state_machines/matcher.rb +6 -6
  38. data/lib/state_machines/matcher_helpers.rb +1 -1
  39. data/lib/state_machines/node_collection.rb +18 -17
  40. data/lib/state_machines/path.rb +2 -4
  41. data/lib/state_machines/path_collection.rb +2 -3
  42. data/lib/state_machines/state.rb +19 -11
  43. data/lib/state_machines/state_collection.rb +3 -3
  44. data/lib/state_machines/state_context.rb +6 -7
  45. data/lib/state_machines/stdio_renderer.rb +16 -16
  46. data/lib/state_machines/syntax_validator.rb +57 -0
  47. data/lib/state_machines/test_helper.rb +617 -27
  48. data/lib/state_machines/transition.rb +269 -91
  49. data/lib/state_machines/transition_collection.rb +66 -35
  50. data/lib/state_machines/version.rb +1 -1
  51. metadata +30 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5aa5d105c78cb53f15f42e8662dd7f8e0d0d5f8807b88dcbd8b4201f13718384
4
- data.tar.gz: d761cedbe052c5c8829626e0875f54dce064f7156162119b4a040594b439068c
3
+ metadata.gz: 7b5d883e6f3259de8981a9855e50d6f2d859bbd7e9e10a4cc5b4d91ae3fec65f
4
+ data.tar.gz: 9185125133d36eeed7ae95875a825e0e9a2e4d73b7de286c65be589965d2613d
5
5
  SHA512:
6
- metadata.gz: 37398c9ca5f2de7413dfb7a8a467984d0fc4d47f8367ea0747fec6d9c1eaca5a7c3439f37988c02dd8ce27622a9a9e54cf5cc0b2d84404f00f92a862c168bb23
7
- data.tar.gz: 159022534eb3c308bc2f2c8e960f111053631d3e10adafc9ae8156c95a26165f48690c682229a577bdfecf918b335ebe5b6d57e82e095c924585ad8d11b9d1ee
6
+ metadata.gz: 4786a92acf6011cc49c6e9f8daa0505d7bfcd26da0d244e351830aed0972ac12656ab39df4f9cf7b35fa4892c7afa02830b7ec0b33876a8624076e60fb273f21
7
+ data.tar.gz: e87573373f971cea02ebba3dcca45124f49d8ca6354c34756e55fd101deab5649ee349e62b05a3abb7d0fdde9ead78075984a089f7f3070fb4b4768c7af8fcc8
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  ![Build Status](https://github.com/state-machines/state_machines/actions/workflows/ruby.yml/badge.svg)
2
+
2
3
  # State Machines
3
4
 
4
5
  State Machines adds support for creating state machines for attributes on any Ruby class.
@@ -9,15 +10,21 @@ State Machines adds support for creating state machines for attributes on any Ru
9
10
 
10
11
  Add this line to your application's Gemfile:
11
12
 
12
- gem 'state_machines'
13
+ ```ruby
14
+ gem 'state_machines'
15
+ ```
13
16
 
14
17
  And then execute:
15
18
 
16
- $ bundle
19
+ ```sh
20
+ bundle
21
+ ```
17
22
 
18
23
  Or install it yourself as:
19
24
 
20
- $ gem install state_machines
25
+ ```sh
26
+ gem install state_machines
27
+ ```
21
28
 
22
29
  ## Usage
23
30
 
@@ -29,6 +36,8 @@ Below is an example of many of the features offered by this plugin, including:
29
36
  * Namespaced states
30
37
  * Transition callbacks
31
38
  * Conditional transitions
39
+ * Coordinated state management guards
40
+ * Asynchronous state machines (async: true)
32
41
  * State-driven instance behavior
33
42
  * Customized state values
34
43
  * Parallel events
@@ -38,11 +47,11 @@ Class definition:
38
47
 
39
48
  ```ruby
40
49
  class Vehicle
41
- attr_accessor :seatbelt_on, :time_used, :auto_shop_busy
50
+ attr_accessor :seatbelt_on, :time_used, :auto_shop_busy, :parking_meter_number
42
51
 
43
52
  state_machine :state, initial: :parked do
44
53
  before_transition parked: any - :parked, do: :put_on_seatbelt
45
-
54
+
46
55
  after_transition on: :crash, do: :tow
47
56
  after_transition on: :repair, do: :fix
48
57
  after_transition any => :parked do |vehicle, transition|
@@ -61,6 +70,18 @@ class Vehicle
61
70
  transition [:idling, :first_gear] => :parked
62
71
  end
63
72
 
73
+ before_transition on: :park do |vehicle, transition|
74
+ # If using Rails:
75
+ # options = transition.args.extract_options!
76
+
77
+ options = transition.args.last.is_a?(Hash) ? transition.args.pop : {}
78
+ meter_number = options[:meter_number]
79
+
80
+ unless meter_number.nil?
81
+ vehicle.parking_meter_number = meter_number
82
+ end
83
+ end
84
+
64
85
  event :ignite do
65
86
  transition stalled: same, parked: :idling
66
87
  end
@@ -130,6 +151,7 @@ class Vehicle
130
151
  @seatbelt_on = false
131
152
  @time_used = 0
132
153
  @auto_shop_busy = true
154
+ @parking_meter_number = nil
133
155
  super() # NOTE: This *must* be called, otherwise states won't get initialized
134
156
  end
135
157
 
@@ -200,6 +222,11 @@ vehicle.park! # => StateMachines:InvalidTransition: Cannot tra
200
222
  vehicle.state?(:parked) # => false
201
223
  vehicle.state?(:invalid) # => IndexError: :invalid is an invalid name
202
224
 
225
+ # Transition callbacks can receive arguments
226
+ vehicle.park(meter_number: '12345') # => true
227
+ vehicle.parked? # => true
228
+ vehicle.parking_meter_number # => "12345"
229
+
203
230
  # Namespaced machines have uniquely-generated methods
204
231
  vehicle.alarm_state # => 1
205
232
  vehicle.alarm_state_name # => :active
@@ -220,6 +247,206 @@ vehicle.alarm_state_name # => :active
220
247
 
221
248
  vehicle.fire_events!(:ignite, :enable_alarm) # => StateMachines:InvalidParallelTransition: Cannot run events in parallel: ignite, enable_alarm
222
249
 
250
+ # Coordinated State Management
251
+
252
+ State machines can coordinate with each other using state guards, allowing transitions to depend on the state of other state machines within the same object. This enables complex system modeling where components are interdependent.
253
+
254
+ ## State Guard Options
255
+
256
+ ### Single State Guards
257
+
258
+ * `:if_state` - Transition only if another state machine is in a specific state.
259
+ * `:unless_state` - Transition only if another state machine is NOT in a specific state.
260
+
261
+ ```ruby
262
+ class TorpedoSystem
263
+ state_machine :bay_doors, initial: :closed do
264
+ event :open do
265
+ transition closed: :open
266
+ end
267
+
268
+ event :close do
269
+ transition open: :closed
270
+ end
271
+ end
272
+
273
+ state_machine :torpedo_status, initial: :loaded do
274
+ event :fire_torpedo do
275
+ # Can only fire torpedo if bay doors are open
276
+ transition loaded: :fired, if_state: { bay_doors: :open }
277
+ end
278
+
279
+ event :reload do
280
+ # Can only reload if bay doors are closed (for safety)
281
+ transition fired: :loaded, unless_state: { bay_doors: :open }
282
+ end
283
+ end
284
+ end
285
+
286
+ system = TorpedoSystem.new
287
+ system.fire_torpedo # => false (bay doors are closed)
288
+
289
+ system.open_bay_doors!
290
+ system.fire_torpedo # => true (bay doors are now open)
291
+ ```
292
+
293
+ ### Multiple State Guards
294
+
295
+ * `:if_all_states` - Transition only if ALL specified state machines are in their respective states.
296
+ * `:unless_all_states` - Transition only if NOT ALL specified state machines are in their respective states.
297
+ * `:if_any_state` - Transition only if ANY of the specified state machines are in their respective states.
298
+ * `:unless_any_state` - Transition only if NONE of the specified state machines are in their respective states.
299
+
300
+ ```ruby
301
+ class StarshipBridge
302
+ state_machine :shields, initial: :down
303
+ state_machine :weapons, initial: :offline
304
+ state_machine :warp_core, initial: :stable
305
+
306
+ state_machine :alert_status, initial: :green do
307
+ event :red_alert do
308
+ # Red alert if ANY critical system needs attention
309
+ transition green: :red, if_any_state: { warp_core: :critical, shields: :down }
310
+ end
311
+
312
+ event :battle_stations do
313
+ # Battle stations only if ALL combat systems are ready
314
+ transition green: :battle, if_all_states: { shields: :up, weapons: :armed }
315
+ end
316
+ end
317
+ end
318
+ ```
319
+
320
+ ## Error Handling
321
+
322
+ State guards provide comprehensive error checking:
323
+
324
+ ```ruby
325
+ # Referencing a non-existent state machine
326
+ event :invalid, if_state: { nonexistent_machine: :some_state }
327
+ # => ArgumentError: State machine 'nonexistent_machine' is not defined for StarshipBridge
328
+
329
+ # Referencing a non-existent state
330
+ event :another_invalid, if_state: { shields: :nonexistent_state }
331
+ # => ArgumentError: State 'nonexistent_state' is not defined in state machine 'shields'
332
+ ```
333
+
334
+ # Asynchronous State Machines
335
+
336
+ State machines can operate asynchronously for high-performance applications. This is ideal for I/O-bound tasks, such as in web servers or other concurrent environments, where you don't want a long-running state transition (like one involving a network call) to block the entire thread.
337
+
338
+ This feature is powered by the [async](https://github.com/socketry/async) gem and uses `concurrent-ruby` for enterprise-grade thread safety.
339
+
340
+ ## Platform Compatibility
341
+
342
+ **Supported Platforms:**
343
+ * MRI Ruby (CRuby) 3.2+
344
+ * Other Ruby engines with full Fiber scheduler support
345
+
346
+ **Unsupported Platforms:**
347
+ * JRuby - Falls back to synchronous mode with warnings
348
+ * TruffleRuby - Falls back to synchronous mode with warnings
349
+
350
+ ## Basic Async Usage
351
+
352
+ Enable async mode by adding `async: true` to your state machine declaration:
353
+
354
+ ```ruby
355
+ class AutonomousDrone < StarfleetShip
356
+ # Async-enabled state machine for autonomous operation
357
+ state_machine :status, async: true, initial: :docked do
358
+ event :launch do
359
+ transition docked: :flying
360
+ end
361
+
362
+ event :land do
363
+ transition flying: :docked
364
+ end
365
+ end
366
+
367
+ # Mixed configuration: some machines async, others sync
368
+ state_machine :teleporter_status, async: true, initial: :offline do
369
+ event :power_up do
370
+ transition offline: :charging
371
+ end
372
+
373
+ event :teleport do
374
+ transition ready: :teleporting
375
+ end
376
+ end
377
+
378
+ # Weapons remain synchronous for safety
379
+ state_machine :weapons, initial: :disarmed do
380
+ event :arm do
381
+ transition disarmed: :armed
382
+ end
383
+ end
384
+ end
385
+ ```
386
+
387
+ ## Async Event Methods
388
+
389
+ Async-enabled machines automatically generate async versions of event methods:
390
+
391
+ ```ruby
392
+ drone = AutonomousDrone.new
393
+
394
+ # Within an Async context
395
+ Async do
396
+ # Async event firing - returns Async::Task
397
+ task = drone.launch_async
398
+ result = task.wait # => true
399
+
400
+ # Bang methods for strict error handling
401
+ drone.power_up_async! # => Async::Task (raises on failure)
402
+
403
+ # Generic async event firing
404
+ drone.fire_event_async(:teleport) # => Async::Task
405
+ end
406
+
407
+ # Outside Async context - raises error
408
+ drone.launch_async # => RuntimeError: launch_async must be called within an Async context
409
+ ```
410
+
411
+ ## Thread Safety
412
+
413
+ Async state machines use enterprise-grade thread safety with `concurrent-ruby`:
414
+
415
+ ```ruby
416
+ # Concurrent operations are automatically thread-safe
417
+ threads = []
418
+ 10.times do
419
+ threads << Thread.new do
420
+ Async do
421
+ drone.launch_async.wait
422
+ drone.land_async.wait
423
+ end
424
+ end
425
+ end
426
+ threads.each(&:join)
427
+ ```
428
+
429
+ ## Performance Considerations
430
+
431
+ * **Thread Safety**: Uses `Concurrent::ReentrantReadWriteLock` for optimal read/write performance.
432
+ * **Memory**: Each async-enabled object gets its own mutex (lazy-loaded).
433
+ * **Marshalling**: Objects with async state machines can be serialized (mutex excluded/recreated).
434
+ * **Mixed Mode**: You can mix async and sync state machines in the same class.
435
+
436
+ ## Dependencies
437
+
438
+ Async functionality requires:
439
+
440
+ ```ruby
441
+ # Gemfile (automatically scoped to MRI Ruby)
442
+ platform :ruby do
443
+ gem 'async', '>= 2.25.0'
444
+ gem 'concurrent-ruby', '>= 1.3.5'
445
+ end
446
+ ```
447
+
448
+ *Note: These gems are only installed on supported platforms. JRuby/TruffleRuby won't attempt installation.*
449
+
223
450
  # Human-friendly names can be accessed for states/events
224
451
  Vehicle.human_state_name(:first_gear) # => "first gear"
225
452
  Vehicle.human_alarm_state_name(:active) # => "active"
@@ -256,30 +483,36 @@ vehicle.state_name # => :parked
256
483
 
257
484
  ## Testing
258
485
 
259
- State Machines provides a `TestHelper` module with assertion methods to make testing state machines easier and more expressive.
486
+ State Machines provides an optional `TestHelper` module with assertion methods to make testing state machines easier and more expressive.
487
+
488
+ **Note: TestHelper is not required by default** - you must explicitly require it in your test files.
260
489
 
261
490
  ### Setup
262
491
 
263
- Include the test helper in your test class:
492
+ First, require the test helper module, then include it in your test class:
264
493
 
265
494
  ```ruby
266
495
  # For Minitest
496
+ require 'state_machines/test_helper'
497
+
267
498
  class VehicleTest < Minitest::Test
268
499
  include StateMachines::TestHelper
269
-
500
+
270
501
  def test_initial_state
271
502
  vehicle = Vehicle.new
272
- assert_state vehicle, :state, :parked
503
+ assert_sm_state vehicle, :parked
273
504
  end
274
505
  end
275
506
 
276
- # For RSpec
507
+ # For RSpec
508
+ require 'state_machines/test_helper'
509
+
277
510
  RSpec.describe Vehicle do
278
511
  include StateMachines::TestHelper
279
-
512
+
280
513
  it "starts in parked state" do
281
514
  vehicle = Vehicle.new
282
- assert_state vehicle, :state, :parked
515
+ assert_sm_state vehicle, :parked
283
516
  end
284
517
  end
285
518
  ```
@@ -292,10 +525,17 @@ The TestHelper provides both basic assertions and comprehensive state machine-sp
292
525
 
293
526
  ```ruby
294
527
  vehicle = Vehicle.new
295
- assert_state vehicle, :state, :parked
296
- assert_can_transition vehicle, :ignite
297
- assert_cannot_transition vehicle, :shift_up
298
- assert_transition vehicle, :ignite, :state, :idling
528
+
529
+ # New standardized API (all methods prefixed with assert_sm_)
530
+ assert_sm_state(vehicle, :parked) # Uses default :state machine
531
+ assert_sm_state(vehicle, :parked, machine_name: :status) # Specify machine explicitly
532
+ assert_sm_can_transition(vehicle, :ignite) # Test transition capability
533
+ assert_sm_cannot_transition(vehicle, :shift_up) # Test transition restriction
534
+ assert_sm_transition(vehicle, :ignite, :idling) # Test actual transition
535
+
536
+ # Multi-FSM examples
537
+ assert_sm_state(vehicle, :inactive, machine_name: :insurance_state) # Test insurance state
538
+ assert_sm_can_transition(vehicle, :buy_insurance, machine_name: :insurance_state)
299
539
  ```
300
540
 
301
541
  #### Extended State Machine Assertions
@@ -308,7 +548,7 @@ vehicle = Vehicle.new
308
548
  assert_sm_states_list machine, [:parked, :idling, :stalled]
309
549
  assert_sm_initial_state machine, :parked
310
550
 
311
- # Event behavior
551
+ # Event behavior
312
552
  assert_sm_event_triggers vehicle, :ignite
313
553
  refute_sm_event_triggers vehicle, :shift_up
314
554
  assert_sm_event_raises_error vehicle, :invalid_event, StateMachines::InvalidTransition
@@ -317,8 +557,106 @@ assert_sm_event_raises_error vehicle, :invalid_event, StateMachines::InvalidTran
317
557
  assert_sm_state_persisted record, expected: :active
318
558
  ```
319
559
 
560
+ #### Indirect Event Testing
561
+
562
+ Test that methods trigger state machine events indirectly:
563
+
564
+ ```ruby
565
+ # Minitest style
566
+ vehicle = Vehicle.new
567
+ vehicle.ignite # Put in idling state
568
+
569
+ # Test that a custom method triggers a specific event
570
+ assert_sm_triggers_event(vehicle, :crash) do
571
+ vehicle.redline # Custom method that calls crash! internally
572
+ end
573
+
574
+ # Test multiple events
575
+ assert_sm_triggers_event(vehicle, [:crash, :emergency]) do
576
+ vehicle.emergency_stop
577
+ end
578
+
579
+ # Test on specific state machine (multi-FSM support)
580
+ assert_sm_triggers_event(vehicle, :disable, machine_name: :alarm) do
581
+ vehicle.turn_off_alarm
582
+ end
583
+ ```
584
+
585
+ ```ruby
586
+ # RSpec style (coming soon with proper matcher support)
587
+ RSpec.describe Vehicle do
588
+ include StateMachines::TestHelper
589
+
590
+ it "triggers crash when redlining" do
591
+ vehicle = Vehicle.new
592
+ vehicle.ignite
593
+
594
+ expect_to_trigger_event(vehicle, :crash) do
595
+ vehicle.redline
596
+ end
597
+ end
598
+ end
599
+ ```
600
+
601
+ #### Callback Definition Testing (TDD Support)
602
+
603
+ Verify that callbacks are properly defined in your state machine:
604
+
605
+ ```ruby
606
+ # Test after_transition callbacks
607
+ assert_after_transition(Vehicle, on: :crash, do: :tow)
608
+ assert_after_transition(Vehicle, from: :stalled, to: :parked, do: :log_repair)
609
+
610
+ # Test before_transition callbacks
611
+ assert_before_transition(Vehicle, from: :parked, do: :put_on_seatbelt)
612
+ assert_before_transition(Vehicle, on: :ignite, if: :seatbelt_on?)
613
+
614
+ # Works with machine instances too
615
+ machine = Vehicle.state_machine(:state)
616
+ assert_after_transition(machine, on: :crash, do: :tow)
617
+ ```
618
+
619
+ #### Multiple State Machine Support
620
+
621
+ The TestHelper fully supports objects with multiple state machines:
622
+
623
+ ```ruby
624
+ # Example: StarfleetShip with 3 state machines
625
+ ship = StarfleetShip.new
626
+
627
+ # Test states on different machines
628
+ assert_sm_state(ship, :docked, machine_name: :status) # Main ship status
629
+ assert_sm_state(ship, :down, machine_name: :shields) # Shield system
630
+ assert_sm_state(ship, :standby, machine_name: :weapons) # Weapons system
631
+
632
+ # Test transitions on specific machines
633
+ assert_sm_transition(ship, :undock, :impulse, machine_name: :status)
634
+ assert_sm_transition(ship, :raise_shields, :up, machine_name: :shields)
635
+ assert_sm_transition(ship, :arm_weapons, :armed, machine_name: :weapons)
636
+
637
+ # Test event triggering across multiple machines
638
+ assert_sm_triggers_event(ship, :red_alert, machine_name: :status) do
639
+ ship.engage_combat_mode # Custom method affecting multiple systems
640
+ end
641
+
642
+ assert_sm_triggers_event(ship, :raise_shields, machine_name: :shields) do
643
+ ship.engage_combat_mode
644
+ end
645
+
646
+ # Test callback definitions on specific machines
647
+ shields_machine = StarfleetShip.state_machine(:shields)
648
+ assert_before_transition(shields_machine, from: :down, to: :up, do: :power_up_shields)
649
+
650
+ # Test persistence across multiple machines
651
+ assert_sm_state_persisted(ship, "impulse", :status)
652
+ assert_sm_state_persisted(ship, "up", :shields)
653
+ assert_sm_state_persisted(ship, "armed", :weapons)
654
+ ```
655
+
320
656
  The test helper works with both Minitest and RSpec, automatically detecting your testing framework.
321
657
 
658
+ **Note:** All methods use consistent keyword arguments with `machine_name:` as the last parameter, making the API intuitive and Grep-friendly.
659
+
322
660
  ## Additional Topics
323
661
 
324
662
  ### Explicit vs. Implicit Event Transitions
@@ -679,7 +1017,7 @@ For RSpec testing, use the custom RSpec matchers:
679
1017
 
680
1018
  ## Contributing
681
1019
 
682
- 1. Fork it ( https://github.com/state-machines/state_machines/fork )
1020
+ 1. Fork it ( <https://github.com/state-machines/state_machines/fork> )
683
1021
  2. Create your feature branch (`git checkout -b my-new-feature`)
684
1022
  3. Commit your changes (`git commit -am 'Add some feature'`)
685
1023
  4. Push to the branch (`git push origin my-new-feature`)
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StateMachines
4
+ module AsyncMode
5
+ # Extensions to Event class for async bang methods
6
+ module AsyncEventExtensions
7
+ # Generate async bang methods for events when async mode is enabled
8
+ def define_helper(scope, method, *args, &block)
9
+ result = super
10
+
11
+ # If this is an async-enabled machine and we're defining an event method
12
+ if scope == :instance && method !~ /_async[!]?$/ && machine.async_mode_enabled?
13
+ qualified_name = method.to_s
14
+
15
+ # Create async version that returns a task
16
+ machine.define_helper(scope, "#{qualified_name}_async") do |machine, object, *method_args, **kwargs|
17
+ # Find the machine that has this event
18
+ target_machine = object.class.state_machines.values.find { |m| m.events[name] }
19
+
20
+ unless defined?(::Async::Task) && ::Async::Task.current?
21
+ raise RuntimeError, "#{qualified_name}_async must be called within an Async context"
22
+ end
23
+
24
+ Async do
25
+ target_machine.events[name].fire(object, *method_args, **kwargs)
26
+ end
27
+ end
28
+
29
+ # Create async bang version that raises exceptions when awaited
30
+ machine.define_helper(scope, "#{qualified_name}_async!") do |machine, object, *method_args, **kwargs|
31
+ # Find the machine that has this event
32
+ target_machine = object.class.state_machines.values.find { |m| m.events[name] }
33
+
34
+ unless defined?(::Async::Task) && ::Async::Task.current?
35
+ raise RuntimeError, "#{qualified_name}_async! must be called within an Async context"
36
+ end
37
+
38
+ Async do
39
+ # Use fire method which will raise exceptions on invalid transitions
40
+ target_machine.events[name].fire(object, *method_args, **kwargs) || raise(StateMachines::InvalidTransition.new(object, target_machine, name))
41
+ end
42
+ end
43
+ end
44
+
45
+ result
46
+ end
47
+ end
48
+ end
49
+ end