ultra-smart-sys 0.0.1

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 (57) hide show
  1. checksums.yaml +7 -0
  2. data/state_machines-0.201.0/LICENSE.txt +23 -0
  3. data/state_machines-0.201.0/README.md +1024 -0
  4. data/state_machines-0.201.0/lib/state_machines/async_mode/async_event_extensions.rb +49 -0
  5. data/state_machines-0.201.0/lib/state_machines/async_mode/async_events.rb +282 -0
  6. data/state_machines-0.201.0/lib/state_machines/async_mode/async_machine.rb +60 -0
  7. data/state_machines-0.201.0/lib/state_machines/async_mode/async_transition_collection.rb +136 -0
  8. data/state_machines-0.201.0/lib/state_machines/async_mode/thread_safe_state.rb +47 -0
  9. data/state_machines-0.201.0/lib/state_machines/async_mode.rb +64 -0
  10. data/state_machines-0.201.0/lib/state_machines/branch.rb +246 -0
  11. data/state_machines-0.201.0/lib/state_machines/callback.rb +223 -0
  12. data/state_machines-0.201.0/lib/state_machines/core.rb +43 -0
  13. data/state_machines-0.201.0/lib/state_machines/core_ext/class/state_machine.rb +5 -0
  14. data/state_machines-0.201.0/lib/state_machines/core_ext.rb +4 -0
  15. data/state_machines-0.201.0/lib/state_machines/error.rb +115 -0
  16. data/state_machines-0.201.0/lib/state_machines/eval_helpers.rb +227 -0
  17. data/state_machines-0.201.0/lib/state_machines/event.rb +247 -0
  18. data/state_machines-0.201.0/lib/state_machines/event_collection.rb +149 -0
  19. data/state_machines-0.201.0/lib/state_machines/extensions.rb +150 -0
  20. data/state_machines-0.201.0/lib/state_machines/helper_module.rb +19 -0
  21. data/state_machines-0.201.0/lib/state_machines/integrations/base.rb +49 -0
  22. data/state_machines-0.201.0/lib/state_machines/integrations.rb +111 -0
  23. data/state_machines-0.201.0/lib/state_machines/machine/action_hooks.rb +53 -0
  24. data/state_machines-0.201.0/lib/state_machines/machine/async_extensions.rb +88 -0
  25. data/state_machines-0.201.0/lib/state_machines/machine/callbacks.rb +333 -0
  26. data/state_machines-0.201.0/lib/state_machines/machine/class_methods.rb +95 -0
  27. data/state_machines-0.201.0/lib/state_machines/machine/configuration.rb +128 -0
  28. data/state_machines-0.201.0/lib/state_machines/machine/event_methods.rb +436 -0
  29. data/state_machines-0.201.0/lib/state_machines/machine/helper_generators.rb +125 -0
  30. data/state_machines-0.201.0/lib/state_machines/machine/integration.rb +92 -0
  31. data/state_machines-0.201.0/lib/state_machines/machine/parsing.rb +77 -0
  32. data/state_machines-0.201.0/lib/state_machines/machine/rendering.rb +17 -0
  33. data/state_machines-0.201.0/lib/state_machines/machine/scoping.rb +44 -0
  34. data/state_machines-0.201.0/lib/state_machines/machine/state_methods.rb +398 -0
  35. data/state_machines-0.201.0/lib/state_machines/machine/utilities.rb +86 -0
  36. data/state_machines-0.201.0/lib/state_machines/machine/validation.rb +39 -0
  37. data/state_machines-0.201.0/lib/state_machines/machine.rb +615 -0
  38. data/state_machines-0.201.0/lib/state_machines/machine_collection.rb +105 -0
  39. data/state_machines-0.201.0/lib/state_machines/macro_methods.rb +522 -0
  40. data/state_machines-0.201.0/lib/state_machines/matcher.rb +124 -0
  41. data/state_machines-0.201.0/lib/state_machines/matcher_helpers.rb +56 -0
  42. data/state_machines-0.201.0/lib/state_machines/node_collection.rb +226 -0
  43. data/state_machines-0.201.0/lib/state_machines/options_validator.rb +72 -0
  44. data/state_machines-0.201.0/lib/state_machines/path.rb +123 -0
  45. data/state_machines-0.201.0/lib/state_machines/path_collection.rb +91 -0
  46. data/state_machines-0.201.0/lib/state_machines/state.rb +314 -0
  47. data/state_machines-0.201.0/lib/state_machines/state_collection.rb +113 -0
  48. data/state_machines-0.201.0/lib/state_machines/state_context.rb +134 -0
  49. data/state_machines-0.201.0/lib/state_machines/stdio_renderer.rb +74 -0
  50. data/state_machines-0.201.0/lib/state_machines/syntax_validator.rb +54 -0
  51. data/state_machines-0.201.0/lib/state_machines/test_helper.rb +775 -0
  52. data/state_machines-0.201.0/lib/state_machines/transition.rb +593 -0
  53. data/state_machines-0.201.0/lib/state_machines/transition_collection.rb +310 -0
  54. data/state_machines-0.201.0/lib/state_machines/version.rb +5 -0
  55. data/state_machines-0.201.0/lib/state_machines.rb +6 -0
  56. data/ultra-smart-sys.gemspec +12 -0
  57. metadata +96 -0
@@ -0,0 +1,593 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StateMachines
4
+ # Module to extend Fiber instances for pausable state tracking
5
+ module PausableFiber
6
+ attr_accessor :state_machine_fiber_pausable
7
+ end
8
+
9
+ # A transition represents a state change for a specific attribute.
10
+ #
11
+ # Transitions consist of:
12
+ # * An event
13
+ # * A starting state
14
+ # * An ending state
15
+ class Transition
16
+ # The object being transitioned
17
+ attr_reader :object
18
+
19
+ # The state machine for which this transition is defined
20
+ attr_reader :machine
21
+
22
+ # The original state value *before* the transition
23
+ attr_reader :from
24
+
25
+ # The new state value *after* the transition
26
+ attr_reader :to
27
+
28
+ # The arguments passed in to the event that triggered the transition
29
+ # (does not include the +run_action+ boolean argument if specified)
30
+ attr_accessor :args
31
+
32
+ # The result of invoking the action associated with the machine
33
+ attr_reader :result
34
+
35
+ # Whether the transition is only existing temporarily for the object
36
+ attr_writer :transient
37
+
38
+ # Creates a new, specific transition
39
+ def initialize(object, machine, event, from_name, to_name, read_state = true) # :nodoc:
40
+ @object = object
41
+ @machine = machine
42
+ @args = []
43
+ @transient = false
44
+ @paused_fiber = nil
45
+ @resuming = false
46
+ @continuation_block = nil
47
+ @fiber_thread_storage = nil
48
+
49
+ @event = machine.events.fetch(event)
50
+ @from_state = machine.states.fetch(from_name)
51
+ @from = read_state ? machine.read(object, :state) : @from_state.value
52
+ @to_state = machine.states.fetch(to_name)
53
+ @to = @to_state.value
54
+
55
+ reset
56
+ end
57
+
58
+ # The attribute which this transition's machine is defined for
59
+ def attribute
60
+ machine.attribute
61
+ end
62
+
63
+ # The action that will be run when this transition is performed
64
+ def action
65
+ machine.action
66
+ end
67
+
68
+ # The event that triggered the transition
69
+ def event
70
+ @event.name
71
+ end
72
+
73
+ # The fully-qualified name of the event that triggered the transition
74
+ def qualified_event
75
+ @event.qualified_name
76
+ end
77
+
78
+ # The human-readable name of the event that triggered the transition
79
+ def human_event
80
+ @event.human_name(@object.class)
81
+ end
82
+
83
+ # The state name *before* the transition
84
+ def from_name
85
+ @from_state.name
86
+ end
87
+
88
+ # The fully-qualified state name *before* the transition
89
+ def qualified_from_name
90
+ @from_state.qualified_name
91
+ end
92
+
93
+ # The human-readable state name *before* the transition
94
+ def human_from_name
95
+ @from_state.human_name(@object.class)
96
+ end
97
+
98
+ # The new state name *after* the transition
99
+ def to_name
100
+ @to_state.name
101
+ end
102
+
103
+ # The new fully-qualified state name *after* the transition
104
+ def qualified_to_name
105
+ @to_state.qualified_name
106
+ end
107
+
108
+ # The new human-readable state name *after* the transition
109
+ def human_to_name
110
+ @to_state.human_name(@object.class)
111
+ end
112
+
113
+ # Does this transition represent a loopback (i.e. the from and to state
114
+ # are the same)
115
+ #
116
+ # == Example
117
+ #
118
+ # machine = StateMachine.new(Vehicle)
119
+ # StateMachines::Transition.new(Vehicle.new, machine, :park, :parked, :parked).loopback? # => true
120
+ # StateMachines::Transition.new(Vehicle.new, machine, :park, :idling, :parked).loopback? # => false
121
+ def loopback?
122
+ from_name == to_name
123
+ end
124
+
125
+ # Is this transition existing for a short period only? If this is set, it
126
+ # indicates that the transition (or the event backing it) should not be
127
+ # written to the object if it fails.
128
+ def transient?
129
+ @transient
130
+ end
131
+
132
+ # A hash of all the core attributes defined for this transition with their
133
+ # names as keys and values of the attributes as values.
134
+ #
135
+ # == Example
136
+ #
137
+ # machine = StateMachine.new(Vehicle)
138
+ # transition = StateMachines::Transition.new(Vehicle.new, machine, :ignite, :parked, :idling)
139
+ # transition.attributes # => {:object => #<Vehicle:0xb7d60ea4>, :attribute => :state, :event => :ignite, :from => 'parked', :to => 'idling'}
140
+ def attributes
141
+ @attributes ||= { object: object, attribute: attribute, event: event, from: from, to: to }
142
+ end
143
+
144
+ # Runs the actual transition and any before/after callbacks associated
145
+ # with the transition. The action associated with the transition/machine
146
+ # can be skipped by passing in +false+.
147
+ #
148
+ # == Examples
149
+ #
150
+ # class Vehicle
151
+ # state_machine :action => :save do
152
+ # ...
153
+ # end
154
+ # end
155
+ #
156
+ # vehicle = Vehicle.new
157
+ # transition = StateMachines::Transition.new(vehicle, machine, :ignite, :parked, :idling)
158
+ # transition.perform # => Runs the +save+ action after setting the state attribute
159
+ # transition.perform(false) # => Only sets the state attribute
160
+ # transition.perform(run_action: false) # => Only sets the state attribute
161
+ # transition.perform(Time.now) # => Passes in additional arguments and runs the +save+ action
162
+ # transition.perform(Time.now, false) # => Passes in additional arguments and only sets the state attribute
163
+ # transition.perform(Time.now, run_action: false) # => Passes in additional arguments and only sets the state attribute
164
+ def perform(*args)
165
+ run_action = case args.last
166
+ in true | false
167
+ args.pop
168
+ in { run_action: }
169
+ args.last.delete(:run_action)
170
+ else
171
+ true
172
+ end
173
+
174
+ self.args = args
175
+
176
+ # Run the transition
177
+ !!TransitionCollection.new([self], { use_transactions: machine.use_transactions, actions: run_action }).perform
178
+ end
179
+
180
+ # Runs a block within a transaction for the object being transitioned.
181
+ # By default, transactions are a no-op unless otherwise defined by the
182
+ # machine's integration.
183
+ def within_transaction(&)
184
+ machine.within_transaction(object, &)
185
+ end
186
+
187
+ # Runs the before / after callbacks for this transition. If a block is
188
+ # provided, then it will be executed between the before and after callbacks.
189
+ #
190
+ # Configuration options:
191
+ # * +before+ - Whether to run before callbacks.
192
+ # * +after+ - Whether to run after callbacks. If false, then any around
193
+ # callbacks will be paused until called again with +after+ enabled.
194
+ # Default is true.
195
+ #
196
+ # This will return true if all before callbacks gets executed. After
197
+ # callbacks will not have an effect on the result.
198
+ def run_callbacks(options = {}, &block)
199
+ options = { before: true, after: true }.merge(options)
200
+
201
+ # If we have a paused fiber and we're not trying to resume (after: false),
202
+ # this is an idempotent call on an already-paused transition. Just return true.
203
+ return true if @paused_fiber&.alive? && !options[:after]
204
+
205
+ # Always use fibers for compatibility with existing pause/resume functionality
206
+ # The fiber argument can still be used to explicitly control fiber usage
207
+ pausable_options = options.key?(:fiber) ? { fiber: options[:fiber] } : {}
208
+
209
+ # Check if we're resuming from a pause
210
+ if @paused_fiber&.alive? && options[:after]
211
+ # Resume the paused fiber
212
+ # Don't reset @success when resuming - preserve the state from the pause
213
+ # Store the block for later execution
214
+ @continuation_block = block if block_given?
215
+ halted = pausable(pausable_options) { true }
216
+ else
217
+ @success = false
218
+ # For normal execution (not pause/resume), default to success
219
+ # The action block will override this if needed
220
+ halted = pausable(pausable_options) { before(options[:after], &block) } if options[:before]
221
+ end
222
+
223
+ # After callbacks are only run if:
224
+ # * An around callback didn't halt after yielding OR the run failed
225
+ # * They're enabled or the run didn't succeed
226
+ after if (!(@before_run && halted) || !@success) && (options[:after] || !@success)
227
+
228
+ @before_run
229
+ end
230
+
231
+ # Transitions the current value of the state to that specified by the
232
+ # transition. Once the state is persisted, it cannot be persisted again
233
+ # until this transition is reset.
234
+ #
235
+ # == Example
236
+ #
237
+ # class Vehicle
238
+ # state_machine do
239
+ # event :ignite do
240
+ # transition :parked => :idling
241
+ # end
242
+ # end
243
+ # end
244
+ #
245
+ # vehicle = Vehicle.new
246
+ # transition = StateMachines::Transition.new(vehicle, Vehicle.state_machine, :ignite, :parked, :idling)
247
+ # transition.persist
248
+ #
249
+ # vehicle.state # => 'idling'
250
+ def persist
251
+ return if @persisted
252
+
253
+ machine.write(object, :state, to)
254
+ @persisted = true
255
+ end
256
+
257
+ # Rolls back changes made to the object's state via this transition. This
258
+ # will revert the state back to the +from+ value.
259
+ #
260
+ # == Example
261
+ #
262
+ # class Vehicle
263
+ # state_machine :initial => :parked do
264
+ # event :ignite do
265
+ # transition :parked => :idling
266
+ # end
267
+ # end
268
+ # end
269
+ #
270
+ # vehicle = Vehicle.new # => #<Vehicle:0xb7b7f568 @state="parked">
271
+ # transition = StateMachines::Transition.new(vehicle, Vehicle.state_machine, :ignite, :parked, :idling)
272
+ #
273
+ # # Persist the new state
274
+ # vehicle.state # => "parked"
275
+ # transition.persist
276
+ # vehicle.state # => "idling"
277
+ #
278
+ # # Roll back to the original state
279
+ # transition.rollback
280
+ # vehicle.state # => "parked"
281
+ def rollback
282
+ reset
283
+ machine.write(object, :state, from)
284
+ end
285
+
286
+ # Resets any tracking of which callbacks have already been run and whether
287
+ # the state has already been persisted
288
+ def reset
289
+ @before_run = @persisted = @after_run = false
290
+ @paused_fiber = nil
291
+ @resuming = false
292
+ @continuation_block = nil
293
+ @fiber_thread_storage = nil
294
+ end
295
+
296
+ # Determines equality of transitions by testing whether the object, states,
297
+ # and event involved in the transition are equal
298
+ def ==(other)
299
+ other.instance_of?(self.class) &&
300
+ other.object == object &&
301
+ other.machine == machine &&
302
+ other.from_name == from_name &&
303
+ other.to_name == to_name &&
304
+ other.event == event
305
+ end
306
+
307
+ # Generates a nicely formatted description of this transitions's contents.
308
+ #
309
+ # For example,
310
+ #
311
+ # transition = StateMachines::Transition.new(object, machine, :ignite, :parked, :idling)
312
+ # transition # => #<StateMachines::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>
313
+ def inspect
314
+ "#<#{self.class} #{%w[attribute event from from_name to to_name].map { |attr| "#{attr}=#{send(attr).inspect}" } * ' '}>"
315
+ end
316
+
317
+ # Checks whether this transition is currently paused.
318
+ # Returns true if there is a paused fiber, false otherwise.
319
+ def paused?
320
+ @paused_fiber&.alive? || false
321
+ end
322
+
323
+ # Checks whether this transition has a paused fiber that can be resumed.
324
+ # Returns true if there is a paused fiber, false otherwise.
325
+ #
326
+ # Note: The actual resuming happens automatically when run_callbacks is called
327
+ # again on a transition with a paused fiber.
328
+ def resumable?
329
+ paused?
330
+ end
331
+
332
+ # Manually resumes the execution of a previously paused callback.
333
+ # Returns true if the transition was successfully resumed and completed,
334
+ # false if there was no paused fiber, and raises an exception if the
335
+ # transition was halted.
336
+ def resume!(&block)
337
+ return false unless paused?
338
+
339
+ # Store continuation block if provided
340
+ @continuation_block = block if block_given?
341
+
342
+ # Run the pausable block which will resume the fiber
343
+ halted = pausable { true }
344
+
345
+ # Return whether the transition completed successfully
346
+ !halted
347
+ end
348
+
349
+ # Completes a transition whose before phase has already run by executing
350
+ # its after callbacks, resuming any paused around callbacks first. Used
351
+ # to finish transitions that were stored for deferred completion (e.g.
352
+ # generated mid-action from an event attribute) once their action has
353
+ # succeeded.
354
+ def complete_deferred_after_callbacks
355
+ if paused?
356
+ run_callbacks(after: true)
357
+ else
358
+ after
359
+ end
360
+ end
361
+
362
+ private
363
+
364
+ # Runs a block that may get paused. If the block doesn't pause, then
365
+ # execution will continue as normal. If the block gets paused, then it
366
+ # will take care of switching the execution context when it's resumed.
367
+ #
368
+ # This will return true if the given block halts for a reason other than
369
+ # getting paused.
370
+ #
371
+ # Options:
372
+ # * :fiber - Whether to use fiber-based execution (default: true)
373
+ def pausable(options = {})
374
+ # If fiber is disabled, use simple synchronous execution
375
+ if options[:fiber] == false
376
+ halted = !catch(:halt) do
377
+ yield
378
+ true
379
+ end
380
+ return halted
381
+ end
382
+
383
+ if @paused_fiber
384
+ # Resume the paused fiber
385
+ @resuming = true
386
+ begin
387
+ result = @paused_fiber.resume
388
+ rescue StandardError => e
389
+ # Clean up on exception
390
+ @resuming = false
391
+ @paused_fiber = nil
392
+ raise e
393
+ end
394
+ @resuming = false
395
+
396
+ result_value = unwrap_fiber_result(result)
397
+
398
+ # Check if fiber is still alive after resume
399
+ if @paused_fiber.alive?
400
+ # Still paused, keep the fiber
401
+ true
402
+ else
403
+ # Fiber completed
404
+ @paused_fiber = nil
405
+ result_value == :halted
406
+ end
407
+ else
408
+ # Capture current fiber's Thread.current storage to preserve object identity
409
+ # This is needed for compatibility but has limitations with dynamic assignments
410
+ parent_fiber_locals = Thread.current.keys.each_with_object({}) do |key, storage|
411
+ storage[key] = Thread.current[key]
412
+ end
413
+
414
+ # Create a new fiber to run the block
415
+ fiber = Fiber.new do
416
+ # Restore parent's Thread.current storage with exact same object references
417
+ parent_fiber_locals.each do |key, value|
418
+ Thread.current[key] = value
419
+ end
420
+
421
+ # Mark that we're inside a pausable fiber
422
+ Fiber.current.extend(StateMachines::PausableFiber)
423
+ Fiber.current.state_machine_fiber_pausable = true
424
+ begin
425
+ halted = !catch(:halt) do
426
+ yield
427
+ true
428
+ end
429
+
430
+ # Export the final thread storage state along with the result
431
+ thread_storage = Thread.current.keys.each_with_object({}) do |key, storage|
432
+ storage[key] = Thread.current[key]
433
+ end
434
+
435
+ [halted ? :halted : :completed, thread_storage]
436
+ rescue StandardError => e
437
+ # Store the exception for re-raising
438
+ [:error, e]
439
+ ensure
440
+ # Clean up the flag
441
+ Fiber.current.state_machine_fiber_pausable = false
442
+ end
443
+ end
444
+
445
+ # Run the fiber
446
+ result = fiber.resume
447
+
448
+ result_value = unwrap_fiber_result(result)
449
+
450
+ # Save if paused
451
+ if fiber.alive?
452
+ @paused_fiber = fiber
453
+ # Return true to indicate paused (treated as halted for flow control)
454
+ true
455
+ else
456
+ # Fiber completed, return whether it was halted
457
+ result_value == :halted
458
+ end
459
+ end
460
+ end
461
+
462
+ # Unwraps a result returned from a pausable fiber, re-raising any exception
463
+ # captured inside the fiber and importing its exported thread storage
464
+ def unwrap_fiber_result(result)
465
+ return result unless result.is_a?(Array)
466
+
467
+ if result[0] == :error
468
+ @paused_fiber = nil
469
+ raise result[1]
470
+ end
471
+
472
+ @fiber_thread_storage = result[1] if result.length == 2 && result[1].is_a?(Hash)
473
+ result[0]
474
+ end
475
+
476
+ # Pauses the current callback execution. This should only occur within
477
+ # around callbacks when the remainder of the callback will be executed at
478
+ # a later point in time.
479
+ def pause
480
+ # Don't pause if we're in the middle of resuming
481
+ return if @resuming
482
+
483
+ # Only yield if we're actually inside a fiber created by pausable
484
+ # We use a module extension to track this
485
+ current_fiber = Fiber.current
486
+ return unless current_fiber.respond_to?(:state_machine_fiber_pausable) && current_fiber.state_machine_fiber_pausable
487
+
488
+ Fiber.yield
489
+
490
+ # When we resume from the pause, execute the continuation block if present
491
+ return unless @continuation_block && !@result
492
+
493
+ action = { success: true }.merge(@continuation_block.call)
494
+ @result = action[:result]
495
+ @success = action[:success]
496
+ @continuation_block = nil
497
+ end
498
+
499
+ # Runs the machine's +before+ callbacks for this transition. Only
500
+ # callbacks that are configured to match the event, from state, and to
501
+ # state will be invoked.
502
+ #
503
+ # Once the callbacks are run, they cannot be run again until this transition
504
+ # is reset.
505
+ def before(complete = true, index = 0, &block)
506
+ return if @before_run
507
+
508
+ callback = machine.callbacks[:before][index]
509
+
510
+ if callback
511
+ # Check if callback matches this transition using branch
512
+ if callback.branch.matches?(object, context)
513
+ if callback.type == :around
514
+ # Around callback: need to handle recursively. Execution only gets
515
+ # paused if:
516
+ # * The block fails and the callback doesn't run on failures OR
517
+ # * The block succeeds, but after callbacks are disabled (in which
518
+ # case a continuation is stored for later execution)
519
+ callback.call(object, context, self) do
520
+ before(complete, index + 1, &block)
521
+
522
+ pause if @success && !complete
523
+
524
+ # If the block failed (success is false), we should halt
525
+ # the around callback from continuing
526
+ throw :halt unless @success
527
+ end
528
+ else
529
+ # Normal before callback
530
+ callback.call(object, context, self)
531
+ # Continue with next callback
532
+ before(complete, index + 1, &block)
533
+ end
534
+ else
535
+ # Skip to next callback if it doesn't match
536
+ before(complete, index + 1, &block)
537
+ end
538
+ else
539
+ # No more callbacks, execute the action block if at the end
540
+ if block_given?
541
+ action = { success: true }.merge(yield)
542
+ @result = action[:result]
543
+ @success = action[:success]
544
+ else
545
+ # No action block provided, default to success
546
+ @success = true
547
+ end
548
+
549
+ @before_run = true
550
+ end
551
+ end
552
+
553
+ # Runs the machine's +after+ callbacks for this transition. Only
554
+ # callbacks that are configured to match the event, from state, and to
555
+ # state will be invoked.
556
+ #
557
+ # Once the callbacks are run, they cannot be run again until this transition
558
+ # is reset.
559
+ #
560
+ # == Halting
561
+ #
562
+ # If any callback throws a <tt>:halt</tt> exception, it will be caught
563
+ # and the callback chain will be automatically stopped. However, this
564
+ # exception will not bubble up to the caller since +after+ callbacks
565
+ # should never halt the execution of a +perform+.
566
+ def after
567
+ return if @after_run
568
+
569
+ # Restore the fiber's thread storage to ensure consistency
570
+ # This preserves Thread.current state from before/around callbacks to after callbacks
571
+ @fiber_thread_storage.each { |key, value| Thread.current[key] = value } if @fiber_thread_storage
572
+
573
+ catch(:halt) do
574
+ type = @success ? :after : :failure
575
+ machine.callbacks[type].each { |callback| callback.call(object, context, self) }
576
+ end
577
+
578
+ @after_run = true
579
+ end
580
+
581
+ # Gets a hash of the context defining this unique transition (including
582
+ # event, from state, and to state).
583
+ #
584
+ # == Example
585
+ #
586
+ # machine = StateMachine.new(Vehicle)
587
+ # transition = StateMachines::Transition.new(Vehicle.new, machine, :ignite, :parked, :idling)
588
+ # transition.context # => {:on => :ignite, :from => :parked, :to => :idling}
589
+ def context
590
+ @context ||= { on: event, from: from_name, to: to_name }
591
+ end
592
+ end
593
+ end