class StateMachine::Callback

Callbacks represent hooks into objects that allow logic to be triggered before, after, or around a specific set of transitions.

Attributes

bind_to_object[RW]

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
terminator[RW]

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.

branch[R]

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.

terminator[R]

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
type[RW]

The type of callback chain this callback is for. This can be one of the following:

  • before

  • after

  • around

  • failure

Public Class Methods

new(type, *args, &block) click to toggle source

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 default throw :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

call(object, context = {}, *args, &block) click to toggle source

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
known_states() click to toggle source

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

bound_method(block) click to toggle source

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
run_methods(object, context = {}, index = 0, *args) { || ... } click to toggle source

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