Callbacks
Callbacks are supported for hooking before and after every possible transition in the machine
- Callbacks are executed ONLY within the context of an event
- Behave in the same way that other ActiveRecord callbacks behave
- Object involved in the transition is passed in as an argument
class Vehicle < ActiveRecord::Base
state_machine :initial => :parked do
before_transition any => :idling do |vehicle|
vehicle.put_on_seatbelt
end
before_transition do |vehicle, transition|
# log message
end
event :ignite do
transition :parked => :idling
end
end
def put_on_seatbelt
...
end
end
- The transition can be accessed by defining additional arguments in the callback block
Callback Order
- (-) save
- (-) begin transaction
- (1) before_transition (Runs
before
callbacks, persists new states, then validates) - (-) valid
- (2) before_validation
- (-) validate
- (3) after_validation
- (~) before_transition (Only if validation was skipped)
- (4) before_save
- (5) before_create
- (-) create
- (6) after_create
- (7) after_save
- (8) after_transition
- (-) end transaction (if enabled)
- (9) after_commit
Callbacks on Object Creation
Callbacks on an initial state when an object is created will NOT get triggered:
class Vehicle state_machine :initial => :parked do after_transition all => :parked do raise ArgumentError end ... end end vehicle = Vehicle.new # => #<Vehicle id: 1, state: "parked"> vehicle.save # => true (So no exception raised)
Workaround 1
Use a before_create
hook:
class Vehicle
before :create, :track_initial_transition
state_machine do
...
end
end
Workaround 2
- Set a fake
:pending
initial state with a:value => nil
- Use the correct event to create the object with the proper state.
class Vehicle
state_machine :initial => :pending
after_transition :pending => :parked, :do => :track_initial_transition
event :park do
transition :pending => :parked
end
state :pending, :value => nil
end
end
vehicle = Vehicle.new
vehicle.park
Callbacks will be triggered and the object gets persisted
Workaround 3
Use a default event attribute that will automatically trigger when the configured action gets run:
class Vehicle < ActiveRecord::Base
state_machine :initial => :pending
after_transition :pending => :parked,
:do => :track_initial_transition
event :park do
transition :pending => :parked
end
state :pending, :value => nil
end
def initialize(*)
super
self.state_event = 'park'
end
end
vehicle = Vehicle.new
vehicle.save
Canceling Callbacks
- Done by throwing
:halt
at any point during a callback:... throw :halt ...
Halt Effect
after
Callback
- Later callbacks are canceled
- Transition is still successful
around
Callback
- Any callback that doesn't
yield
will throw in a:halt
. - Code executed after
yield
will behave in the same way asafter
callbacks
before
Callback
Transition and all later callbacks are canceled
if a
before
callback fails and the bang version of an event was invoked, an exception will be raised instead of returningfalse
: ```ruby class Vehicle state_machine :initial => :parked do before_transition any => :idling, :do => lambda {|vehicle| throw :halt} ... end end
vehicle = Vehicle.new vehicle.park # => false vehicle.park! # => StateMachines::InvalidTransition: Cannot transition state via :park from "idling" ```