Module: StateMachines::EvalHelpers
- Included in:
- Branch, Callback, Machine, StateContext
- Defined in:
- lib/state_machines/eval_helpers.rb
Overview
Provides a set of helper methods for evaluating methods within the context of an object.
Instance Method Summary collapse
-
#evaluate_method(object, method, *args, &block) ⇒ Object
Evaluates one of several different types of methods within the context of the given object.
-
#evaluate_method_with_event_args(object, method, event_args = []) ⇒ Object
Evaluates a guard method with support for event arguments passed to transitions.
Instance Method Details
#evaluate_method(object, method, *args, &block) ⇒ Object
Evaluates one of several different types of methods within the context of the given object. Methods can be one of the following types:
-
Symbol
-
Method / Proc
-
String
Examples
Below are examples of the various ways that a method can be evaluated on an object:
class Person
def initialize(name)
@name = name
end
def name
@name
end
end
class PersonCallback
def self.run(person)
person.name
end
end
person = Person.new('John Smith')
evaluate_method(person, :name) # => "John Smith"
evaluate_method(person, PersonCallback.method(:run)) # => "John Smith"
evaluate_method(person, Proc.new {|person| person.name}) # => "John Smith"
evaluate_method(person, lambda {|person| person.name}) # => "John Smith"
evaluate_method(person, '@name') # => "John Smith"
Additional arguments
Additional arguments can be passed to the methods being evaluated. If the method defines additional arguments other than the object context, then all arguments are required.
For guard conditions in state machines, event arguments can be passed automatically based on the guard’s arity:
-
Guards with arity 1 receive only the object (backward compatible)
-
Guards with arity -1 or > 1 receive object + event arguments
For example,
person = Person.new('John Smith')
evaluate_method(person, lambda {|person| person.name}, 21) # => "John Smith"
evaluate_method(person, lambda {|person, age| "#{person.name} is #{age}"}, 21) # => "John Smith is 21"
evaluate_method(person, lambda {|person, age| "#{person.name} is #{age}"}, 21, 'male') # => ArgumentError: wrong number of arguments (3 for 2)
With event arguments for guards:
# Single parameter guard (backward compatible)
guard = lambda {|obj| obj.valid? }
evaluate_method_with_event_args(object, guard, [arg1, arg2]) # => calls guard.call(object)
# Multi-parameter guard (receives event args)
guard = lambda {|obj, *args| obj.valid? && args[0] == :force }
evaluate_method_with_event_args(object, guard, [:force]) # => calls guard.call(object, :force)
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/state_machines/eval_helpers.rb', line 72 def evaluate_method(object, method, *args, **, &block) case method in Symbol => sym klass = (class << object; self; end) args = [] if (klass.method_defined?(sym) || klass.private_method_defined?(sym)) && object.method(sym).arity.zero? object.send(sym, *args, **, &block) in Proc => proc args.unshift(object) arity = proc.arity # Handle blocks for Procs case [block_given?, arity] in [true, arity] if arity != 0 case arity in 1 | 2 # Force the block to be either the only argument or the second one # after the object (may mean additional arguments get discarded) args = args[0, arity - 1] + [block] else # insert the block to the end of the args args << block end in [_, 0 | 1] # These method types are only called with 0, 1, or n arguments args = args[0, arity] else # No changes needed for other cases end # Call the Proc with the arguments proc.call(*args, **) in Method => meth args.unshift(object) arity = meth.arity # Methods handle blocks via &block, not as arguments # Only limit arguments if necessary based on arity args = args[0, arity] if [0, 1].include?(arity) # Call the Method with the arguments and pass the block meth.call(*args, **, &block) in String => str # Input validation for string evaluation validate_eval_string(str) case [block_given?, StateMachines::Transition.pause_supported?] in [true, true] eval(str, object.instance_eval { binding }, &block) in [true, false] # Support for JRuby and Truffle Ruby, which don't support binding blocks # Need to check with @headius, if jruby 10 does now. eigen = class << object; self; end eigen.class_eval " def __temp_eval_method__(*args, &b)\n \#{str}\n end\n RUBY\n result = object.__temp_eval_method__(*args, &block)\n eigen.send(:remove_method, :__temp_eval_method__)\n result\n in [false, _]\n eval(str, object.instance_eval { binding })\n end\n else\n raise ArgumentError, 'Methods must be a symbol denoting the method to call, a block to be invoked, or a string to be evaluated'\n end\nend\n", __FILE__, __LINE__ + 1 |
#evaluate_method_with_event_args(object, method, event_args = []) ⇒ Object
Evaluates a guard method with support for event arguments passed to transitions. This method uses arity detection to determine whether to pass event arguments to the guard, ensuring backward compatibility.
Parameters
-
object - The object context to evaluate within
-
method - The guard method/proc to evaluate
-
event_args - Array of arguments passed to the event (optional)
Arity-based behavior
-
Arity 1: Only passes the object (backward compatible)
-
Arity -1 or > 1: Passes object + event arguments
Examples
# Backward compatible single-parameter guard
guard = lambda {|obj| obj.valid? }
evaluate_method_with_event_args(object, guard, [:force]) # => calls guard.call(object)
# New multi-parameter guard receiving event args
guard = lambda {|obj, *args| obj.valid? && args[0] != :skip }
evaluate_method_with_event_args(object, guard, [:skip]) # => calls guard.call(object, :skip)
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
# File 'lib/state_machines/eval_helpers.rb', line 162 def evaluate_method_with_event_args(object, method, event_args = []) case method in Symbol # Symbol methods currently don't support event arguments # This maintains backward compatibility evaluate_method(object, method) in Proc => proc arity = proc.arity # Arity-based decision for backward compatibility using pattern matching case arity in 0 proc.call in 1 proc.call(object) in -1 # Splat parameters: object + all event args proc.call(object, *event_args) in arity if arity > 1 # Explicit parameters: object + limited event args args_needed = arity - 1 # Subtract 1 for the object parameter proc.call(object, *event_args[0, args_needed]) else # Negative arity other than -1 (unlikely but handle gracefully) proc.call(object, *event_args) end in Method => meth arity = meth.arity case arity in 0 meth.call in 1 meth.call(object) in -1 meth.call(object, *event_args) in arity if arity > 1 args_needed = arity - 1 meth.call(object, *event_args[0, args_needed]) else meth.call(object, *event_args) end in String # String evaluation doesn't support event arguments for security evaluate_method(object, method) else # Fall back to standard evaluation evaluate_method(object, method) end end |