class StateMachine::Callback
Callbacks represent hooks into objects that allow logic to be triggered before, after, or around a specific set of transitions.
Attributes
Determines whether to automatically bind the callback to the object being transitioned. This only applies to callbacks that are defined as lambda blocks (or Procs). Some integrations, such as DataMapper, handle callbacks by executing them bound to the object involved, while other integrations, such as ActiveRecord, pass the object as an argument to the callback. This can be configured on an application-wide basis by setting this configuration to true
or false
. The default value is false
.
Note that the DataMapper and Sequel integrations automatically configure this value on a per-callback basis, so it does not have to be enabled application-wide.
Examples¶ ↑
When not bound to the object:
class Vehicle state_machine do before_transition do |vehicle| vehicle.set_alarm end end def set_alarm ... end end
When bound to the object:
StateMachine::Callback.bind_to_object = true class Vehicle state_machine do before_transition do self.set_alarm end end def set_alarm ... end end
The application-wide terminator to use for callbacks when not explicitly defined. Terminators determine whether to cancel a callback chain based on the return value of the callback.
See StateMachine::Callback#terminator
for more information.
The branch that determines whether or not this callback can be invoked based on the context of the transition. The event, from state, and to state must all match in order for the branch to pass.
See StateMachine::Branch
for more information.
An optional block for determining whether to cancel the callback chain based on the return value of the callback. By default, the callback chain never cancels based on the return value (i.e. there is no implicit terminator). Certain integrations, such as ActiveRecord and Sequel, change this default value.
Examples¶ ↑
Canceling the callback chain without a terminator:
class Vehicle state_machine do before_transition do |vehicle| throw :halt end end end
Canceling the callback chain with a terminator value of false
:
class Vehicle state_machine do before_transition do |vehicle| false end end end
The type of callback chain this callback is for. This can be one of the following:
-
before
-
after
-
around
-
failure
Public Class Methods
Creates a new callback that can get called based on the configured options.
In addition to the possible configuration options for branches, the following options can be configured:
-
:bind_to_object
- Whether to bind the callback to the object involved. If set to false, the object will be passed as a parameter instead. Default is integration-specific or set to the application default. -
:terminator
- A block/proc that determines what callback results should cause the callback chain to halt (if not using the defaultthrow :halt
technique).
More information about how those options affect the behavior of the callback can be found in their attribute definitions.
# File lib/state_machine/callback.rb 123 def initialize(type, *args, &block) 124 @type = type 125 raise ArgumentError, 'Type must be :before, :after, :around, or :failure' unless [:before, :after, :around, :failure].include?(type) 126 127 options = args.last.is_a?(Hash) ? args.pop : {} 128 @methods = args 129 @methods.concat(Array(options.delete(:do))) 130 @methods << block if block_given? 131 raise ArgumentError, 'Method(s) for callback must be specified' unless @methods.any? 132 133 options = {:bind_to_object => self.class.bind_to_object, :terminator => self.class.terminator}.merge(options) 134 135 # Proxy lambda blocks so that they're bound to the object 136 bind_to_object = options.delete(:bind_to_object) 137 @methods.map! do |method| 138 bind_to_object && method.is_a?(Proc) ? bound_method(method) : method 139 end 140 141 @terminator = options.delete(:terminator) 142 @branch = Branch.new(options) 143 end
Public Instance Methods
Runs the callback as long as the transition context matches the branch requirements configured for this callback. If a block is provided, it will be called when the last method has run.
If a terminator has been configured and it matches the result from the evaluated method, then the callback chain should be halted.
# File lib/state_machine/callback.rb 157 def call(object, context = {}, *args, &block) 158 if @branch.matches?(object, context) 159 run_methods(object, context, 0, *args, &block) 160 true 161 else 162 false 163 end 164 end
Gets a list of the states known to this callback by looking at the branch's known states
# File lib/state_machine/callback.rb 147 def known_states 148 branch.known_states 149 end
Private Instance Methods
Generates a method that can be bound to the object being transitioned when the callback is invoked
# File lib/state_machine/callback.rb 199 def bound_method(block) 200 type = self.type 201 arity = block.arity 202 arity += 1 if arity >= 0 # Make sure the object gets passed 203 arity += 1 if arity == 1 && type == :around # Make sure the block gets passed 204 205 method = if RUBY_VERSION >= '1.9' 206 lambda do |object, *args| 207 object.instance_exec(*args, &block) 208 end 209 else 210 # Generate a thread-safe unbound method that can be used on any object. 211 # This is a workaround for not having Ruby 1.9's instance_exec 212 unbound_method = Object.class_eval do 213 time = Time.now 214 method_name = "__bind_#{time.to_i}_#{time.usec}" 215 define_method(method_name, &block) 216 method = instance_method(method_name) 217 remove_method(method_name) 218 method 219 end 220 221 # Proxy calls to the method so that the method can be bound *and* 222 # the arguments are adjusted 223 lambda do |object, *args| 224 unbound_method.bind(object).call(*args) 225 end 226 end 227 228 # Proxy arity to the original block 229 (class << method; self; end).class_eval do 230 define_method(:arity) { arity } 231 end 232 233 method 234 end
Runs all of the methods configured for this callback.
When running around
callbacks, this will evaluate each method and yield when the last method has yielded. The callback will only halt if one of the methods does not yield.
For all other types of callbacks, this will evaluate each method in order. The callback will only halt if the resulting value from the method passes the terminator.
# File lib/state_machine/callback.rb 176 def run_methods(object, context = {}, index = 0, *args, &block) 177 if type == :around 178 if method = @methods[index] 179 yielded = false 180 evaluate_method(object, method, *args) do 181 yielded = true 182 run_methods(object, context, index + 1, *args, &block) 183 end 184 185 throw :halt unless yielded 186 else 187 yield if block_given? 188 end 189 else 190 @methods.each do |method| 191 result = evaluate_method(object, method, *args) 192 throw :halt if @terminator && @terminator.call(result) 193 end 194 end 195 end