state
state
-Aware Methods
Class Methods
For class-level methods to run certain behaviors based on the current state, the method's last
argument must allow a conditions hash (:if
, :unless
) to be passed in. e.g.
def validates_presence_of(attribute, options = {})
...
end
Example
class Vehicle < ActiveRecord::Base
state_machine do
...
state :first_gear, :second_gear, :third_gear do
validates_presence_of :speed
validates_inclusion_of :speed, :in => 0..25, :if => :in_school_zone?
end
end
end
In the above ActiveRecord
model, two validations have been defined which will only run when the
Vehicle object is in one of the three states: first_gear
, second_gear
, or third_gear
. Notice,
also, that if/unless conditions can continue to be used.
Validations
- You can define validations that only execute for certain states.
Custom validators will not work as expected when defined to run in multiple states:
class Vehicle < ActiveRecord::Base state_machine do ... state :first_gear, :second_gear do validate :speed_is_legal end end end
In this case, :speed_is_legal
validation will only run for the :second_gear
state. To avoid
this, you can define your custom validation like so:
class Vehicle < ActiveRecord::Base
state_machine do
...
state :first_gear, :second_gear do
validate {|vehicle| vehicle.speed_is_legal}
end
end
end
Instance Methods (Behaviors)
Allows instance methods to behave a specific way depending on what the value of the object's state is.
class Vehicle
attr_accessor :driver
attr_accessor :passenger
state_machine :initial => :parked do
event :ignite do
transition :parked => :idling
end
state :parked do
def speed
0
end
def rotate_driver
driver = self.driver
self.driver = passenger
self.passenger = driver
true
end
end
state :idling, :first_gear do
def speed
20
end
def rotate_driver
self.state = 'parked'
rotate_driver
end
end
other_states :backing_up
end
end
In the above example, there are two dynamic behaviors defined for the class:
speed
rotate_driver
Each of these behaviors are instance methods on the Vehicle class. However, which method actually gets invoked is based on the current state of the object. Using the above class as the example:
vehicle = Vehicle.new
vehicle.driver = 'John'
vehicle.passenger = 'Jane'
# Behaviors in the "parked" state
vehicle.state # => "parked"
vehicle.speed # => 0
vehicle.rotate_driver # => true
vehicle.driver # => "Jane"
vehicle.passenger # => "John"
vehicle.ignite # => true
# Behaviors in the "idling" state
vehicle.state # => "idling"
vehicle.speed # => 20
vehicle.rotate_driver # => true
vehicle.driver # => "John"
vehicle.passenger # => "Jane"
As can be seen, both the speed
and rotate_driver
instance method implementations changed how
they behave based on what the current state of the vehicle was.
Invalid behaviors
If a specific behavior has not been defined for a state, then a NoMethodError exception will be raised, indicating that that method would not normally exist for an object with that state.
Using the example from before:
vehicle = Vehicle.new
vehicle.state = 'backing_up'
vehicle.speed
# => NoMethodError: undefined method 'speed' for #<Vehicle:0xb7d296ac> in state "backing_up"