Module: StateMachines::AsyncMode::AsyncEvents

Defined in:
lib/state_machines/async_mode/async_events.rb

Overview

Async-aware event firing capabilities using the async gem

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name, *args, **kwargs, &block) ⇒ Object

Dynamically handle individual event async methods This provides launch_async, launch_async!, arm_weapons_async, etc.



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/state_machines/async_mode/async_events.rb', line 154

def method_missing(method_name, *args, **kwargs, &block)
  method_str = method_name.to_s

  # Check if this is an async event method
  if method_str.end_with?('_async!')
    # Remove the _async! suffix to get the base event method
    base_method = method_str.chomp('_async!').to_sym

    # Check if the base method exists and this machine is async-enabled
    if respond_to?(base_method) && async_method_for_event?(base_method)
      return handle_individual_event_async_bang(base_method, *args, **kwargs)
    end
  elsif method_str.end_with?('_async')
    # Remove the _async suffix to get the base event method
    base_method = method_str.chomp('_async').to_sym

    # Check if the base method exists and this machine is async-enabled
    if respond_to?(base_method) && async_method_for_event?(base_method)
      return handle_individual_event_async(base_method, *args, **kwargs)
    end
  end

  # If not an async method, call the original method_missing
  super
end

Instance Method Details

#async_fire_event(event_name, *args) ⇒ Object

Fires an event asynchronously using Async Returns an Async::Task that can be awaited for the result

Example:

Async do
  task = vehicle.async_fire_event(:ignite)
  result = task.wait # => true/false
end


15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/state_machines/async_mode/async_events.rb', line 15

def async_fire_event(event_name, *args)
  # Find the machine that has this event
  machine = self.class.state_machines.values.find { |m| m.events[event_name] }

  unless machine
    raise ArgumentError, "Event #{event_name} not found in any state machine"
  end

  # Must be called within an Async context
  unless defined?(::Async::Task) && ::Async::Task.current?
    raise RuntimeError, "async_fire_event must be called within an Async context. Use: Async { vehicle.async_fire_event(:event) }"
  end

  Async do
    machine.events[event_name].fire(self, *args)
  end
end

#async_fire_event!(event_name, *args) ⇒ Object

Fires an event asynchronously using Async and raises exception on failure Returns an Async::Task that raises StateMachines::InvalidTransition when awaited

Example:

Async do
  begin
    task = vehicle.async_fire_event!(:ignite)
    result = task.wait
    puts "Event fired successfully!"
  rescue StateMachines::InvalidTransition => e
    puts "Transition failed: #{e.message}"
  end
end


103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/state_machines/async_mode/async_events.rb', line 103

def async_fire_event!(event_name, *args)
  # Find the machine that has this event
  machine = self.class.state_machines.values.find { |m| m.events[event_name] }

  unless machine
    raise ArgumentError, "Event #{event_name} not found in any state machine"
  end

  # Must be called within an Async context
  unless defined?(::Async::Task) && ::Async::Task.current?
    raise RuntimeError, "async_fire_event! must be called within an Async context. Use: Async { vehicle.async_fire_event!(:event) }"
  end

  Async do
    # Use the bang version which raises exceptions on failure
    machine.events[event_name].fire(self, *args) || raise(StateMachines::InvalidTransition.new(self, machine, event_name))
  end
end

#async_fire_events(*event_names) ⇒ Object

Fires multiple events asynchronously across different state machines Returns an array of Async::Tasks for concurrent execution

Example:

Async do
  tasks = vehicle.async_fire_events(:ignite, :buy_insurance)
  results = tasks.map(&:wait) # => [true, true]
end


41
42
43
# File 'lib/state_machines/async_mode/async_events.rb', line 41

def async_fire_events(*event_names)
  event_names.map { |event_name| async_fire_event(event_name) }
end

#fire_event_async(event_name, *args) ⇒ Object

Fires an event asynchronously and waits for completion This is a convenience method that creates and waits for the task

Example:

result = vehicle.fire_event_async(:ignite) # => true/false

Raises:

  • (NoMethodError)


50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/state_machines/async_mode/async_events.rb', line 50

def fire_event_async(event_name, *args)
  raise NoMethodError, "undefined method `fire_event_async' for #{self}" unless has_async_machines?
  # Find the machine that has this event
  machine = self.class.state_machines.values.find { |m| m.events[event_name] }

  unless machine
    raise ArgumentError, "Event #{event_name} not found in any state machine"
  end

  if defined?(::Async::Task) && ::Async::Task.current?
    # Already in async context, just fire directly
    machine.events[event_name].fire(self, *args)
  else
    # Create async context and wait for result
    Async do
      machine.events[event_name].fire(self, *args)
    end.wait
  end
end

#fire_event_async!(event_name, *args) ⇒ Object

Fires an event asynchronously and waits for result, raising exceptions on failure This is a convenience method that creates and waits for the task

Example:

begin
  result = vehicle.fire_event_async!(:ignite)
  puts "Event fired successfully!"
rescue StateMachines::InvalidTransition => e
  puts "Transition failed: #{e.message}"
end

Raises:

  • (NoMethodError)


132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/state_machines/async_mode/async_events.rb', line 132

def fire_event_async!(event_name, *args)
  raise NoMethodError, "undefined method `fire_event_async!' for #{self}" unless has_async_machines?
  # Find the machine that has this event
  machine = self.class.state_machines.values.find { |m| m.events[event_name] }

  unless machine
    raise ArgumentError, "Event #{event_name} not found in any state machine"
  end

  if defined?(::Async::Task) && ::Async::Task.current?
    # Already in async context, just fire directly with bang behavior
    machine.events[event_name].fire(self, *args) || raise(StateMachines::InvalidTransition.new(self, machine, event_name))
  else
    # Create async context and wait for result (may raise exception)
    Async do
      machine.events[event_name].fire(self, *args) || raise(StateMachines::InvalidTransition.new(self, machine, event_name))
    end.wait
  end
end

#fire_events_async(*event_names) ⇒ Object

Fires multiple events asynchronously and waits for all completions Returns results in the same order as the input events

Example:

results = vehicle.fire_events_async(:ignite, :buy_insurance) # => [true, true]

Raises:

  • (NoMethodError)


75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/state_machines/async_mode/async_events.rb', line 75

def fire_events_async(*event_names)
  raise NoMethodError, "undefined method `fire_events_async' for #{self}" unless has_async_machines?
  if defined?(::Async::Task) && ::Async::Task.current?
    # Already in async context, run concurrently
    tasks = event_names.map { |event_name| async_fire_event(event_name) }
    tasks.map(&:wait)
  else
    # Create async context and run concurrently
    Async do
      tasks = event_names.map { |event_name| async_fire_event(event_name) }
      tasks.map(&:wait)
    end.wait
  end
end

#fire_events_concurrent(*event_names) ⇒ Object

Fires multiple events concurrently within an async context This method should be called from within an Async block

Example:

Async do
  results = vehicle.fire_events_concurrent(:ignite, :buy_insurance)
end


272
273
274
275
276
277
278
279
# File 'lib/state_machines/async_mode/async_events.rb', line 272

def fire_events_concurrent(*event_names)
  unless defined?(::Async::Task) && ::Async::Task.current?
    raise RuntimeError, "fire_events_concurrent must be called within an Async context"
  end

  tasks = async_fire_events(*event_names)
  tasks.map(&:wait)
end

#has_async_machines?Boolean

Check if this object has any async-enabled state machines

Returns:

  • (Boolean)


196
197
198
# File 'lib/state_machines/async_mode/async_events.rb', line 196

def has_async_machines?
  self.class.state_machines.any? { |name, machine| machine.async_mode_enabled? }
end

#respond_to_missing?(method_name, include_private = false) ⇒ Boolean

Check if we should respond to async methods for this event

Returns:

  • (Boolean)


181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/state_machines/async_mode/async_events.rb', line 181

def respond_to_missing?(method_name, include_private = false)
  # Only provide async methods if this object has async-enabled machines
  return super unless has_async_machines?

  method_str = method_name.to_s

  if method_str.end_with?('_async!') || method_str.end_with?('_async')
    base_method = method_str.chomp('_async!').chomp('_async').to_sym
    return respond_to?(base_method) && async_method_for_event?(base_method)
  end

  super
end