state_machines 0.201.0 → 0.202.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fcd3d5e6fc0f3982c8ddabc40480b43375d6e323957dd392ca4b3cc10f9c0f40
4
- data.tar.gz: 89556aa64bb82045527f260098dd801acce298be5ad873ed7f250805b7839c87
3
+ metadata.gz: e579c5afa633f1402bfd1e1a5ebd0c446ff410eb3b06940d74dce0d0e024f530
4
+ data.tar.gz: 629c2f403c71e966e98c5d35c64fa8ffa99caaa8e5aced5e205a569eb867e811
5
5
  SHA512:
6
- metadata.gz: d44ae6b303dc577db3ed8aa70d7132521b8be94c3651fa9afe886e90c7561ebea686d1c743adc9464d08bd01d2903a4e75eabf7aa567abacab81d0b1c889fea4
7
- data.tar.gz: b04cbf3f1cb69b943fdf19aa91f37b3376f976c4ed28bd08ab4aed18af6473b3508c05fe52317f10f2627660a67a3e8c3e8481a520ba2985a5c7c37316499e51
6
+ metadata.gz: 1bf56f62b3f6e495fc799ba48d499c0bb7325dfdb3c24d1f8aafc2c9daaade581bef63495e8562a633ee32d2ca3870e8e8131052d97bb152c793ab86f0a3c0fb
7
+ data.tar.gz: f2ae6e891727b60a4310160fbfa7044b4b949a8bccb9f8f80d7d3733dda6e528bf14f3449e7ab420b35d84ef971a6e6349e43a162e16b735e0be61e2ae7571be
@@ -114,8 +114,7 @@ module StateMachines
114
114
  end
115
115
 
116
116
  Async do
117
- # Use the bang version which raises exceptions on failure
118
- machine.events[event_name].fire(self, *args) || raise(StateMachines::InvalidTransition.new(self, machine, event_name))
117
+ fire_with_bang(machine, event_name, *args)
119
118
  end
120
119
  end
121
120
 
@@ -140,11 +139,11 @@ module StateMachines
140
139
 
141
140
  if defined?(::Async::Task) && ::Async::Task.current?
142
141
  # Already in async context, just fire directly with bang behavior
143
- machine.events[event_name].fire(self, *args) || raise(StateMachines::InvalidTransition.new(self, machine, event_name))
142
+ fire_with_bang(machine, event_name, *args)
144
143
  else
145
144
  # Create async context and wait for result (may raise exception)
146
145
  Async do
147
- machine.events[event_name].fire(self, *args) || raise(StateMachines::InvalidTransition.new(self, machine, event_name))
146
+ fire_with_bang(machine, event_name, *args)
148
147
  end.wait
149
148
  end
150
149
  end
@@ -199,6 +198,11 @@ module StateMachines
199
198
 
200
199
  private
201
200
 
201
+ # Fires the event on the given machine, raising InvalidTransition on failure
202
+ def fire_with_bang(machine, event_name, *)
203
+ machine.events[event_name].fire(self, *) || raise(StateMachines::InvalidTransition.new(self, machine, event_name))
204
+ end
205
+
202
206
  # Check if this event method should have async versions
203
207
  def async_method_for_event?(event_method)
204
208
  # Find which machine contains this event
@@ -55,6 +55,33 @@ module StateMachines
55
55
  target.method_defined?(method) || target.private_method_defined?(method)
56
56
  end
57
57
 
58
+ # Generates the warning message for a method conflict, including where
59
+ # the conflicting method was defined (when it has a Ruby source
60
+ # location) and which class the state machine is being defined on
61
+ def method_conflict_message(scope, method, defined_in)
62
+ scope_label = scope == :class ? 'Class' : 'Instance'
63
+ defined_in_name = defined_in.name && !defined_in.name.empty? ? defined_in.name : defined_in.to_s
64
+ location = conflicting_method_location(scope, method, defined_in)
65
+ location_label = location ? " at #{location[0]}:#{location[1]}" : ''
66
+
67
+ "#{scope_label} method \"#{method}\" is already defined in #{defined_in_name}#{location_label}, " \
68
+ 'use generic helper instead or set StateMachines::Machine.ignore_method_conflicts = true. ' \
69
+ "Defining #{name.inspect} state machine on #{owner_class}."
70
+ end
71
+
72
+ # Looks up the source location of the conflicting method. Returns nil
73
+ # for methods without a Ruby source (e.g. C-defined methods like
74
+ # Kernel#fail) and for the machine's own helper modules, where the
75
+ # location would just point inside this gem
76
+ def conflicting_method_location(scope, method, defined_in)
77
+ return if @helper_modules.value?(defined_in)
78
+
79
+ target = scope == :class && defined_in.is_a?(Class) ? defined_in.singleton_class : defined_in
80
+ target.instance_method(method).source_location
81
+ rescue NameError
82
+ nil
83
+ end
84
+
58
85
  # Pluralizes the given word using #pluralize (if available) or simply
59
86
  # adding an "s" to the end of the word
60
87
  def pluralize(word)
@@ -355,7 +355,7 @@ module StateMachines
355
355
  # module that gets included in every Object. As a result, state_machine will
356
356
  # generate the following warning:
357
357
  #
358
- # Instance method "open" is already defined in Object, use generic helper instead or set StateMachines::Machine.ignore_method_conflicts = true.
358
+ # Instance method "open" is already defined in Object, use generic helper instead or set StateMachines::Machine.ignore_method_conflicts = true. Defining :state state machine on Vehicle.
359
359
  #
360
360
  # Even though you may not be using Kernel's implementation of the "open"
361
361
  # instance method, state_machine isn't aware of this and, as a result, stays
@@ -517,8 +517,7 @@ module StateMachines
517
517
 
518
518
  if block_given?
519
519
  if !self.class.ignore_method_conflicts && (conflicting_ancestor = owner_class_ancestor_has_method?(scope, method))
520
- ancestor_name = conflicting_ancestor.name && !conflicting_ancestor.name.empty? ? conflicting_ancestor.name : conflicting_ancestor.to_s
521
- warn "#{scope == :class ? 'Class' : 'Instance'} method \"#{method}\" is already defined in #{ancestor_name}, use generic helper instead or set StateMachines::Machine.ignore_method_conflicts = true."
520
+ warn method_conflict_message(scope, method, conflicting_ancestor)
522
521
  else
523
522
  name = self.name
524
523
  helper_module.class_eval do
@@ -289,8 +289,8 @@ module StateMachines
289
289
  def add_predicate
290
290
  predicate_method = "#{qualified_name}?"
291
291
 
292
- if machine.send(:owner_class_ancestor_has_method?, :instance, predicate_method)
293
- warn_about_method_conflict(predicate_method, machine.owner_class.ancestors.first)
292
+ if (conflicting_ancestor = machine.send(:owner_class_ancestor_has_method?, :instance, predicate_method))
293
+ warn_about_method_conflict(predicate_method, conflicting_ancestor)
294
294
  elsif machine.send(:owner_class_has_method?, :instance, predicate_method)
295
295
  warn_about_method_conflict(predicate_method, machine.owner_class)
296
296
  else
@@ -308,7 +308,7 @@ module StateMachines
308
308
  def warn_about_method_conflict(method, defined_in)
309
309
  return if StateMachines::Machine.ignore_method_conflicts
310
310
 
311
- warn "Instance method #{method.inspect} is already defined in #{defined_in.inspect}, use generic helper instead or set StateMachines::Machine.ignore_method_conflicts = true."
311
+ warn machine.send(:method_conflict_message, :instance, method, defined_in)
312
312
  end
313
313
  end
314
314
  end
@@ -407,9 +407,7 @@ module StateMachines
407
407
  else
408
408
  # Capture current fiber's Thread.current storage to preserve object identity
409
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
410
+ parent_fiber_locals = capture_thread_locals
413
411
 
414
412
  # Create a new fiber to run the block
415
413
  fiber = Fiber.new do
@@ -428,11 +426,7 @@ module StateMachines
428
426
  end
429
427
 
430
428
  # 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]
429
+ [halted ? :halted : :completed, capture_thread_locals]
436
430
  rescue StandardError => e
437
431
  # Store the exception for re-raising
438
432
  [:error, e]
@@ -459,6 +453,14 @@ module StateMachines
459
453
  end
460
454
  end
461
455
 
456
+ # Snapshots the current fiber's Thread.current storage, preserving object
457
+ # identity of the stored values
458
+ def capture_thread_locals
459
+ Thread.current.keys.each_with_object({}) do |key, storage|
460
+ storage[key] = Thread.current[key]
461
+ end
462
+ end
463
+
462
464
  # Unwraps a result returned from a pausable fiber, re-raising any exception
463
465
  # captured inside the fiber and importing its exported thread storage
464
466
  def unwrap_fiber_result(result)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StateMachines
4
- VERSION = '0.201.0'
4
+ VERSION = '0.202.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: state_machines
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.201.0
4
+ version: 0.202.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abdelkader Boudih