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"

results matching ""

    No results matching ""