Callbacks
before_transition
Creates a callback to be invoked before a transition is performed so long as all the requirements are met.
Definition
As an argument:
before_transition :set_alarm before_transition :set_alarm, all => :parked
In
:do
option:before_transition all => :parked, :do => :set_alarm
As a block:
before_transition all => :parked do |vehicle, transition| vehicle.set_alarm end
Even as a group at the same time with different ways of defining:
class Vehicle state_machine do before_transition :set_alarm, :lock_doors, all => :parked before_transition all => :parked, :do => [:set_alarm, :lock_doors] before_transition :set_alarm do |vehicle, transition| vehicle.lock_doors end end end
Requirements
state
Requirements
Requirement so the callback is only invoked on a trasnsition from and to specific states, normally using a Hash syntax mapping beginning to ending states
before_transition :parked => :idling, :idling => :first_gear, :do => :set_alarm
# `set_alarm` is only called if the machine is transitioning from
# `parked` to `idling` or
# `idling` to `parked`
For more complex matching, see StateMachines::MatcherHelpers.
event
Requirements
Requirement so the callback is only invoked on specific events using the on
option
before_transition :on => :ignite, :do => ... # Matches only on ignite
before_transition :on => all - :ignite, :do => ... # Matches on every event except ignite
before_transition :parked => :idling, :on => :ignite, :do => ... # Matches from parked to idling on ignite
For more complex matching, see StateMachines::MatcherHelpers.
Verbose Requirements
Requirements can be defined directly using verbose options:
:from
One or more states being transitioned from. If none are specified, then all states will match.
:to
One or more states being transitioned to. If none are specified, then all states will match.
:on
One or more events that fired the transition. If none are specified, then all events will match.
:except_from
One or more states not being transitioned from
:except_to
One more states not being transitioned to
:except_on
One or more events that did not fire the transition
Examples:
before_transition :from => :ignite, :to => :idling, :on => :park, :do => ...
before_transition :except_from => :ignite, :except_to => :idling, :except_on => :park, :do => ...
Conditions
Conditions can also be defined to determine if the callback should be invoked, using:
:if
A method, proc or string to call to determine if the callback should occur (e.g. :if => :allow_callbacks
, or :if => lambda {|user| user.signup_step > 2}
). The method, proc or string should return or evaluate to a true
or false
.
:unless
A method, proc or string to call to determine if the callback should not occur (e.g. :unless => :skip_callbacks
, or :unless => lambda {|user| user.signup_step <= 2}
). The method, proc or string should return or evaluate to a true
or false
.
Examples
before_transition :parked => :idling, :if => :moving?, :do => ...
before_transition :on => :ignite, :unless => :seatbelt_on?, :do => ...
Accessing the Transition
The actual transition describing the context (e.g. event
, from
, to
) can be be passed in as an argument if callbacks are configured to not be bound to the object involved. This is the default behavior
class Vehicle
# Only specifies one parameter (the object being transitioned)
before_transition all => :parked do |vehicle|
vehicle.set_alarm
end
# Specifies 2 parameters (object being transitioned and actual transition)
before_transition all => :parked do |vehicle, transition|
vehicle.set_alarm(transition)
end
end
See StateMachines::Transition for more about the attributes available on the transition.
Usage with Delegation
- state_machine uses the callback method's argument list arity to determine whether to include the transition in the method call
If you're using delegates, such as those defined in
ActiveSupport
orForwardable
, the actual arity of the delegated method gets masked and so callbacks referencing delegates will be passed the transition as an argumentclass Vehicle extend Forwardable delegate :refresh => :dashboard state_machine do before_transition :refresh ... end def dashboard @dashboard ||= Dashboard.new end end class Dashboard def refresh(transition) # ... end end
In the above example,
Dashboard#refresh
must define atransition
argument. Otherwise, anArgumentError
exception will get raised. The only way around this is to avoid the use of delegates and manually define the delegate method so that the correct arity is used.
Examples
class Vehicle
state_machine do
# Before all transitions
before_transition :update_dashboard
# Before specific transition:
before_transition [:first_gear, :idling] => :parked, :on => :park, :do => :take_off_seatbelt
# With conditional callback:
before_transition all => :parked, :do => :take_off_seatbelt, :if => :seatbelt_on?
# Using helpers:
before_transition all - :stalled => same, :on => any - :crash, :do => :update_dashboard
...
end
end