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,227 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'syntax_validator'
4
+
5
+ module StateMachines
6
+ # Provides a set of helper methods for evaluating methods within the context
7
+ # of an object.
8
+ module EvalHelpers
9
+ # Evaluates one of several different types of methods within the context
10
+ # of the given object. Methods can be one of the following types:
11
+ # * Symbol
12
+ # * Method / Proc
13
+ # * String
14
+ #
15
+ # == Examples
16
+ #
17
+ # Below are examples of the various ways that a method can be evaluated
18
+ # on an object:
19
+ #
20
+ # class Person
21
+ # def initialize(name)
22
+ # @name = name
23
+ # end
24
+ #
25
+ # def name
26
+ # @name
27
+ # end
28
+ # end
29
+ #
30
+ # class PersonCallback
31
+ # def self.run(person)
32
+ # person.name
33
+ # end
34
+ # end
35
+ #
36
+ # person = Person.new('John Smith')
37
+ #
38
+ # evaluate_method(person, :name) # => "John Smith"
39
+ # evaluate_method(person, PersonCallback.method(:run)) # => "John Smith"
40
+ # evaluate_method(person, Proc.new {|person| person.name}) # => "John Smith"
41
+ # evaluate_method(person, lambda {|person| person.name}) # => "John Smith"
42
+ # evaluate_method(person, '@name') # => "John Smith"
43
+ #
44
+ # == Additional arguments
45
+ #
46
+ # Additional arguments can be passed to the methods being evaluated. If
47
+ # the method defines additional arguments other than the object context,
48
+ # then all arguments are required.
49
+ #
50
+ # For guard conditions in state machines, event arguments can be passed
51
+ # automatically based on the guard's arity:
52
+ # - Guards with arity 1 receive only the object (backward compatible)
53
+ # - Guards with arity -1 or > 1 receive object + event arguments
54
+ #
55
+ # For example,
56
+ #
57
+ # person = Person.new('John Smith')
58
+ #
59
+ # evaluate_method(person, lambda {|person| person.name}, 21) # => "John Smith"
60
+ # evaluate_method(person, lambda {|person, age| "#{person.name} is #{age}"}, 21) # => "John Smith is 21"
61
+ # evaluate_method(person, lambda {|person, age| "#{person.name} is #{age}"}, 21, 'male') # => ArgumentError: wrong number of arguments (3 for 2)
62
+ #
63
+ # With event arguments for guards:
64
+ #
65
+ # # Single parameter guard (backward compatible)
66
+ # guard = lambda {|obj| obj.valid? }
67
+ # evaluate_method_with_event_args(object, guard, [arg1, arg2]) # => calls guard.call(object)
68
+ #
69
+ # # Multi-parameter guard (receives event args)
70
+ # guard = lambda {|obj, *args| obj.valid? && args[0] == :force }
71
+ # evaluate_method_with_event_args(object, guard, [:force]) # => calls guard.call(object, :force)
72
+ def evaluate_method(object, method, *args, **, &block)
73
+ case method
74
+ in Symbol => sym
75
+ klass = (class << object; self; end)
76
+ args = [] if (klass.method_defined?(sym) || klass.private_method_defined?(sym)) && object.method(sym).arity.zero?
77
+ object.send(sym, *args, **, &block)
78
+ in Proc => proc
79
+ args.unshift(object)
80
+ arity = proc.arity
81
+ # Handle blocks for Procs
82
+ case [block_given?, arity]
83
+ in [true, arity] if arity != 0
84
+ case arity
85
+ in 1 | 2
86
+ # Force the block to be either the only argument or the second one
87
+ # after the object (may mean additional arguments get discarded)
88
+ args = args[0, arity - 1] + [block]
89
+ else
90
+ # insert the block to the end of the args
91
+ args << block
92
+ end
93
+ in [_, 0 | 1]
94
+ # These method types are only called with 0, 1, or n arguments
95
+ args = args[0, arity]
96
+ else
97
+ # No changes needed for other cases
98
+ end
99
+
100
+ # Call the Proc with the arguments
101
+ proc.call(*args, **)
102
+
103
+ in Method => meth
104
+ args.unshift(object)
105
+ arity = meth.arity
106
+
107
+ # Methods handle blocks via &block, not as arguments
108
+ # Only limit arguments if necessary based on arity
109
+ args = args[0, arity] if [0, 1].include?(arity)
110
+
111
+ # Call the Method with the arguments and pass the block
112
+ meth.call(*args, **, &block)
113
+ in String => str
114
+ # Input validation for string evaluation
115
+ validate_eval_string(str)
116
+
117
+ # Evaluate the string in the object's context
118
+ if block_given?
119
+ # TruffleRuby and some other implementations need special handling for blocks
120
+ # Create a temporary method to evaluate the string with block support
121
+ eigen = class << object; self; end
122
+ eigen.class_eval <<-RUBY, __FILE__, __LINE__ + 1
123
+ def __temp_eval_method__(*args, &b)
124
+ #{str}
125
+ end
126
+ RUBY
127
+ result = object.__temp_eval_method__(*args, &block)
128
+ eigen.send(:remove_method, :__temp_eval_method__)
129
+ result
130
+ else
131
+ object.instance_eval(str, __FILE__, __LINE__)
132
+ end
133
+ else
134
+ raise ArgumentError, 'Methods must be a symbol denoting the method to call, a block to be invoked, or a string to be evaluated'
135
+ end
136
+ end
137
+
138
+ # Evaluates a guard method with support for event arguments passed to transitions.
139
+ # This method uses arity detection to determine whether to pass event arguments
140
+ # to the guard, ensuring backward compatibility.
141
+ #
142
+ # == Parameters
143
+ # * object - The object context to evaluate within
144
+ # * method - The guard method/proc to evaluate
145
+ # * event_args - Array of arguments passed to the event (optional)
146
+ #
147
+ # == Arity-based behavior
148
+ # * Arity 1: Only passes the object (backward compatible)
149
+ # * Arity -1 or > 1: Passes object + event arguments
150
+ #
151
+ # == Examples
152
+ #
153
+ # # Backward compatible single-parameter guard
154
+ # guard = lambda {|obj| obj.valid? }
155
+ # evaluate_method_with_event_args(object, guard, [:force]) # => calls guard.call(object)
156
+ #
157
+ # # New multi-parameter guard receiving event args
158
+ # guard = lambda {|obj, *args| obj.valid? && args[0] != :skip }
159
+ # evaluate_method_with_event_args(object, guard, [:skip]) # => calls guard.call(object, :skip)
160
+ def evaluate_method_with_event_args(object, method, event_args = [])
161
+ case method
162
+ in Symbol
163
+ # Symbol methods currently don't support event arguments
164
+ # This maintains backward compatibility
165
+ evaluate_method(object, method)
166
+ in Proc | Method => callable
167
+ arity = callable.arity
168
+
169
+ # Arity-based decision for backward compatibility using pattern matching
170
+ case arity
171
+ in 0
172
+ callable.call
173
+ in 1
174
+ callable.call(object)
175
+ in -1
176
+ # Splat parameters: object + all event args
177
+ callable.call(object, *event_args)
178
+ in arity if arity > 1
179
+ # Explicit parameters: object + limited event args
180
+ args_needed = arity - 1 # Subtract 1 for the object parameter
181
+ callable.call(object, *event_args[0, args_needed])
182
+ else
183
+ # Negative arity other than -1 (unlikely but handle gracefully)
184
+ callable.call(object, *event_args)
185
+ end
186
+ in String
187
+ # String evaluation doesn't support event arguments for security
188
+ evaluate_method(object, method)
189
+ else
190
+ # Fall back to standard evaluation
191
+ evaluate_method(object, method)
192
+ end
193
+ end
194
+
195
+ private
196
+
197
+ # Validates string input before eval to prevent code injection
198
+ # This is a basic safety check - not foolproof security
199
+ def validate_eval_string(method_string)
200
+ # Check for obviously dangerous patterns
201
+ dangerous_patterns = [
202
+ /`.*`/, # Backticks (shell execution)
203
+ /system\s*\(/, # System calls
204
+ /exec\s*\(/, # Exec calls
205
+ /eval\s*\(/, # Nested eval
206
+ /require\s+['"]/, # Require statements
207
+ /load\s+['"]/, # Load statements
208
+ /File\./, # File operations
209
+ /IO\./, # IO operations
210
+ /Dir\./, # Directory operations
211
+ /Kernel\./ # Kernel operations
212
+ ]
213
+
214
+ dangerous_patterns.each do |pattern|
215
+ raise SecurityError, "Potentially dangerous code detected in eval string: #{method_string.inspect}" if method_string.match?(pattern)
216
+ end
217
+
218
+ # Basic syntax validation - but allow yield since it's valid in block context
219
+ begin
220
+ test_code = method_string.include?('yield') ? "def dummy_method; #{method_string}; end" : method_string
221
+ SyntaxValidator.validate!(test_code, '(eval)')
222
+ rescue SyntaxError => e
223
+ raise ArgumentError, "Invalid Ruby syntax in eval string: #{e.message}"
224
+ end
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,247 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'options_validator'
4
+
5
+ module StateMachines
6
+ # An event defines an action that transitions an attribute from one state to
7
+ # another. The state that an attribute is transitioned to depends on the
8
+ # branches configured for the event.
9
+ class Event
10
+ include MatcherHelpers
11
+
12
+ # The state machine for which this event is defined
13
+ attr_accessor :machine
14
+
15
+ # The name of the event
16
+ attr_reader :name
17
+
18
+ # The fully-qualified name of the event, scoped by the machine's namespace
19
+ attr_reader :qualified_name
20
+
21
+ # The human-readable name for the event
22
+ attr_writer :human_name
23
+
24
+ # The list of branches that determine what state this event transitions
25
+ # objects to when fired
26
+ attr_reader :branches
27
+
28
+ # A list of all of the states known to this event using the configured
29
+ # branches/transitions as the source
30
+ attr_reader :known_states
31
+
32
+ # Creates a new event within the context of the given machine
33
+ #
34
+ # Configuration options:
35
+ # * <tt>:human_name</tt> - The human-readable version of this event's name
36
+ def initialize(machine, name, options = nil, human_name: nil, **extra_options) # :nodoc:
37
+ # Handle both old hash style and new kwargs style for backward compatibility
38
+ case options
39
+ in Hash
40
+ # Old style: initialize(machine, name, {human_name: 'Custom Name'})
41
+ StateMachines::OptionsValidator.assert_valid_keys!(options, :human_name)
42
+ human_name = options[:human_name]
43
+ in nil
44
+ # New style: initialize(machine, name, human_name: 'Custom Name')
45
+ StateMachines::OptionsValidator.assert_valid_keys!(extra_options, :human_name) unless extra_options.empty?
46
+ else
47
+ # Handle unexpected options
48
+ raise ArgumentError, "Unexpected positional argument in Event initialize: #{options.inspect}"
49
+ end
50
+
51
+ @machine = machine
52
+ @name = name
53
+ @qualified_name = machine.namespace ? :"#{name}_#{machine.namespace}" : name
54
+ @human_name = human_name || @name.to_s.tr('_', ' ')
55
+ reset
56
+
57
+ # Output a warning if another event has a conflicting qualified name
58
+ if (conflict = machine.owner_class.state_machines.detect { |_other_name, other_machine| other_machine != @machine && other_machine.events[qualified_name, :qualified_name] })
59
+ _name, other_machine = conflict
60
+ warn "Event #{qualified_name.inspect} for #{machine.name.inspect} is already defined in #{other_machine.name.inspect}"
61
+ else
62
+ add_actions
63
+ end
64
+ end
65
+
66
+ # Creates a copy of this event in addition to the list of associated
67
+ # branches to prevent conflicts across events within a class hierarchy.
68
+ def initialize_copy(orig) # :nodoc:
69
+ super
70
+ @branches = @branches.dup
71
+ @known_states = @known_states.dup
72
+ end
73
+
74
+ # Transforms the event name into a more human-readable format, such as
75
+ # "turn on" instead of "turn_on"
76
+ def human_name(klass = @machine.owner_class)
77
+ @human_name.is_a?(Proc) ? @human_name.call(self, klass) : @human_name
78
+ end
79
+
80
+ # Evaluates the given block within the context of this event. This simply
81
+ # provides a DSL-like syntax for defining transitions.
82
+ def context(&)
83
+ instance_eval(&)
84
+ end
85
+
86
+ # Creates a new transition that determines what to change the current state
87
+ # to when this event fires.
88
+ #
89
+ # Since this transition is being defined within an event context, you do
90
+ # *not* need to specify the <tt>:on</tt> option for the transition. For
91
+ # example:
92
+ #
93
+ # state_machine do
94
+ # event :ignite do
95
+ # transition :parked => :idling, :idling => same, :if => :seatbelt_on? # Transitions to :idling if seatbelt is on
96
+ # transition all => :parked, :unless => :seatbelt_on? # Transitions to :parked if seatbelt is off
97
+ # end
98
+ # end
99
+ #
100
+ # See StateMachines::Machine#transition for a description of the possible
101
+ # configurations for defining transitions.
102
+ def transition(options)
103
+ raise ArgumentError, 'Must specify as least one transition requirement' if options.empty?
104
+
105
+ # Only a certain subset of explicit options are allowed for transition
106
+ # requirements
107
+ StateMachines::OptionsValidator.assert_valid_keys!(options, :from, :to, :except_from, :except_to, :if, :unless, :if_state, :unless_state, :if_all_states, :unless_all_states, :if_any_state, :unless_any_state) if (options.keys - %i[from to on except_from except_to except_on if unless if_state unless_state if_all_states unless_all_states if_any_state unless_any_state]).empty?
108
+
109
+ branches << branch = Branch.new(options.merge(on: name))
110
+ @known_states |= branch.known_states
111
+ branch
112
+ end
113
+
114
+ # Determines whether any transitions can be performed for this event based
115
+ # on the current state of the given object.
116
+ #
117
+ # If the event can't be fired, then this will return false, otherwise true.
118
+ #
119
+ # *Note* that this will not take the object context into account. Although
120
+ # a transition may be possible based on the state machine definition,
121
+ # object-specific behaviors (like validations) may prevent it from firing.
122
+ def can_fire?(object, requirements = {})
123
+ !transition_for(object, requirements).nil?
124
+ end
125
+
126
+ # Finds and builds the next transition that can be performed on the given
127
+ # object. If no transitions can be made, then this will return nil.
128
+ #
129
+ # Valid requirement options:
130
+ # * <tt>:from</tt> - One or more states being transitioned from. If none
131
+ # are specified, then this will be the object's current state.
132
+ # * <tt>:to</tt> - One or more states being transitioned to. If none are
133
+ # specified, then this will match any to state.
134
+ # * <tt>:guard</tt> - Whether to guard transitions with the if/unless
135
+ # conditionals defined for each one. Default is true.
136
+ #
137
+ # Event arguments are passed to guard conditions if they accept multiple parameters.
138
+ def transition_for(object, requirements = {}, *event_args)
139
+ StateMachines::OptionsValidator.assert_valid_keys!(requirements, :from, :to, :guard)
140
+ requirements[:from] = machine.states.match!(object).name unless (custom_from_state = requirements.include?(:from))
141
+
142
+ branches.each do |branch|
143
+ next unless (match = branch.match(object, requirements, event_args))
144
+
145
+ # Branch allows for the transition to occur
146
+ from = requirements[:from]
147
+ to = if match[:to].is_a?(LoopbackMatcher)
148
+ from
149
+ else
150
+ values = requirements.include?(:to) ? [requirements[:to]].flatten : [from] | machine.states.map { |state| state.name }
151
+
152
+ match[:to].filter(values).first
153
+ end
154
+
155
+ return Transition.new(object, machine, name, from, to, !custom_from_state)
156
+ end
157
+
158
+ # No transition matched
159
+ nil
160
+ end
161
+
162
+ # Attempts to perform the next available transition on the given object.
163
+ # If no transitions can be made, then this will return false, otherwise
164
+ # true.
165
+ #
166
+ # Any additional arguments are passed to the StateMachines::Transition#perform
167
+ # instance method.
168
+ def fire(object, *event_args)
169
+ machine.reset(object)
170
+
171
+ if (transition = transition_for(object, {}, *event_args))
172
+ transition.perform(*event_args)
173
+ else
174
+ on_failure(object, *event_args)
175
+ false
176
+ end
177
+ end
178
+
179
+ # Marks the object as invalid and runs any failure callbacks associated with
180
+ # this event. This should get called anytime this event fails to transition.
181
+ def on_failure(object, *args)
182
+ state = machine.states.match!(object)
183
+ machine.invalidate(object, :state, :invalid_transition, [[:event, human_name(object.class)], [:state, state.human_name(object.class)]])
184
+
185
+ transition = Transition.new(object, machine, name, state.name, state.name)
186
+ transition.args = args if args.any?
187
+ transition.run_callbacks(before: false)
188
+ end
189
+
190
+ # Resets back to the initial state of the event, with no branches / known
191
+ # states associated. This allows you to redefine an event in situations
192
+ # where you either are re-using an existing state machine implementation
193
+ # or are subclassing machines.
194
+ def reset
195
+ @branches = []
196
+ @known_states = []
197
+ end
198
+
199
+ def draw(graph, options = {}, io = $stdout)
200
+ machine.renderer.draw_event(self, graph, options, io)
201
+ end
202
+
203
+ # Generates a nicely formatted description of this event's contents.
204
+ #
205
+ # For example,
206
+ #
207
+ # event = StateMachines::Event.new(machine, :park)
208
+ # event.transition all - :idling => :parked, :idling => same
209
+ # event # => #<StateMachines::Event name=:park transitions=[all - :idling => :parked, :idling => same]>
210
+ def inspect
211
+ transitions = branches.flat_map do |branch|
212
+ branch.state_requirements.map do |state_requirement|
213
+ "#{state_requirement[:from].description} => #{state_requirement[:to].description}"
214
+ end
215
+ end.join(', ')
216
+
217
+ "#<#{self.class} name=#{name.inspect} transitions=[#{transitions}]>"
218
+ end
219
+
220
+ protected
221
+
222
+ # Add the various instance methods that can transition the object using
223
+ # the current event
224
+ def add_actions
225
+ # Checks whether the event can be fired on the current object
226
+ machine.define_helper(:instance, "can_#{qualified_name}?") do |machine, object, *args, **kwargs|
227
+ machine.event(name).can_fire?(object, *args, **kwargs)
228
+ end
229
+
230
+ # Gets the next transition that would be performed if the event were
231
+ # fired now
232
+ machine.define_helper(:instance, "#{qualified_name}_transition") do |machine, object, *args, **kwargs|
233
+ machine.event(name).transition_for(object, *args, **kwargs)
234
+ end
235
+
236
+ # Fires the event
237
+ machine.define_helper(:instance, qualified_name) do |machine, object, *args, **kwargs|
238
+ machine.event(name).fire(object, *args, **kwargs)
239
+ end
240
+
241
+ # Fires the event, raising an exception if it fails
242
+ machine.define_helper(:instance, "#{qualified_name}!") do |machine, object, *args, **kwargs|
243
+ object.send(qualified_name, *args, **kwargs) || raise(StateMachines::InvalidTransition.new(object, machine, name))
244
+ end
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StateMachines
4
+ # Represents a collection of events in a state machine
5
+ class EventCollection < NodeCollection
6
+ def initialize(machine) # :nodoc:
7
+ super(machine, index: %i[name qualified_name])
8
+ end
9
+
10
+ # Gets the list of events that can be fired on the given object.
11
+ #
12
+ # Valid requirement options:
13
+ # * <tt>:from</tt> - One or more states being transitioned from. If none
14
+ # are specified, then this will be the object's current state.
15
+ # * <tt>:to</tt> - One or more states being transitioned to. If none are
16
+ # specified, then this will match any to state.
17
+ # * <tt>:on</tt> - One or more events that fire the transition. If none
18
+ # are specified, then this will match any event.
19
+ # * <tt>:guard</tt> - Whether to guard transitions with the if/unless
20
+ # conditionals defined for each one. Default is true.
21
+ #
22
+ # == Examples
23
+ #
24
+ # class Vehicle
25
+ # state_machine :initial => :parked do
26
+ # event :park do
27
+ # transition :idling => :parked
28
+ # end
29
+ #
30
+ # event :ignite do
31
+ # transition :parked => :idling
32
+ # end
33
+ # end
34
+ # end
35
+ #
36
+ # events = Vehicle.state_machine(:state).events
37
+ #
38
+ # vehicle = Vehicle.new # => #<Vehicle:0xb7c464b0 @state="parked">
39
+ # events.valid_for(vehicle) # => [#<StateMachines::Event name=:ignite transitions=[:parked => :idling]>]
40
+ #
41
+ # vehicle.state = 'idling'
42
+ # events.valid_for(vehicle) # => [#<StateMachines::Event name=:park transitions=[:idling => :parked]>]
43
+ def valid_for(object, requirements = {})
44
+ match(requirements).select { |event| event.can_fire?(object, requirements) }
45
+ end
46
+
47
+ # Gets the list of transitions that can be run on the given object.
48
+ #
49
+ # Valid requirement options:
50
+ # * <tt>:from</tt> - One or more states being transitioned from. If none
51
+ # are specified, then this will be the object's current state.
52
+ # * <tt>:to</tt> - One or more states being transitioned to. If none are
53
+ # specified, then this will match any to state.
54
+ # * <tt>:on</tt> - One or more events that fire the transition. If none
55
+ # are specified, then this will match any event.
56
+ # * <tt>:guard</tt> - Whether to guard transitions with the if/unless
57
+ # conditionals defined for each one. Default is true.
58
+ #
59
+ # == Examples
60
+ #
61
+ # class Vehicle
62
+ # state_machine :initial => :parked do
63
+ # event :park do
64
+ # transition :idling => :parked
65
+ # end
66
+ #
67
+ # event :ignite do
68
+ # transition :parked => :idling
69
+ # end
70
+ # end
71
+ # end
72
+ #
73
+ # events = Vehicle.state_machine.events
74
+ #
75
+ # vehicle = Vehicle.new # => #<Vehicle:0xb7c464b0 @state="parked">
76
+ # events.transitions_for(vehicle) # => [#<StateMachines::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>]
77
+ #
78
+ # vehicle.state = 'idling'
79
+ # events.transitions_for(vehicle) # => [#<StateMachines::Transition attribute=:state event=:park from="idling" from_name=:idling to="parked" to_name=:parked>]
80
+ #
81
+ # # Search for explicit transitions regardless of the current state
82
+ # events.transitions_for(vehicle, :from => :parked) # => [#<StateMachines::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>]
83
+ def transitions_for(object, requirements = {})
84
+ match(requirements).map { |event| event.transition_for(object, requirements) }.compact
85
+ end
86
+
87
+ # Gets the transition that should be performed for the event stored in the
88
+ # given object's event attribute. This also takes an additional parameter
89
+ # for automatically invalidating the object if the event or transition are
90
+ # invalid. By default, this is turned off.
91
+ #
92
+ # *Note* that if a transition has already been generated for the event, then
93
+ # that transition will be used.
94
+ #
95
+ # == Examples
96
+ #
97
+ # class Vehicle < ActiveRecord::Base
98
+ # state_machine :initial => :parked do
99
+ # event :ignite do
100
+ # transition :parked => :idling
101
+ # end
102
+ # end
103
+ # end
104
+ #
105
+ # vehicle = Vehicle.new # => #<Vehicle id: nil, state: "parked">
106
+ # events = Vehicle.state_machine.events
107
+ #
108
+ # vehicle.state_event = nil
109
+ # events.attribute_transition_for(vehicle) # => nil # Event isn't defined
110
+ #
111
+ # vehicle.state_event = 'invalid'
112
+ # events.attribute_transition_for(vehicle) # => false # Event is invalid
113
+ #
114
+ # vehicle.state_event = 'ignite'
115
+ # events.attribute_transition_for(vehicle) # => #<StateMachines::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>
116
+ def attribute_transition_for(object, invalidate = false)
117
+ return unless machine.action
118
+
119
+ # TODO, simplify
120
+ # First try the regular event_transition
121
+ transition = machine.read(object, :event_transition)
122
+
123
+ # If not found and we have stored transitions by machine (issue #91)
124
+ if !transition && (transitions_by_machine = object.instance_variable_get(:@_state_machine_event_transitions))
125
+ transition = transitions_by_machine[machine.name]
126
+ end
127
+
128
+ transition || if event_name = machine.read(object, :event)
129
+ if event = self[event_name.to_sym, :name]
130
+ event.transition_for(object) || begin
131
+ # No valid transition: invalidate
132
+ machine.invalidate(object, :event, :invalid_event, [[:state, machine.states.match!(object).human_name(object.class)]]) if invalidate
133
+ false
134
+ end
135
+ else
136
+ # Event is unknown: invalidate
137
+ machine.invalidate(object, :event, :invalid) if invalidate
138
+ false
139
+ end
140
+ end
141
+ end
142
+
143
+ private
144
+
145
+ def match(requirements) # :nodoc:
146
+ requirements && requirements[:on] ? [fetch(requirements.delete(:on))] : self
147
+ end
148
+ end
149
+ end