event

  • Methods

  • Defines one or more states for the machine and the transitions that can be performed when those events are run

  • The main interface to transitioning states for an object
  • Aliased as on
  • Attributes are generated for every event to allow transitions to be performed automatically when :save is called
    vehicle = Vehicle.create          # => #<Vehicle id: 1, name: nil, state: "parked">
    vehicle.state_event               # => nil
    vehicle.state_event = 'invalid'
    vehicle.valid?                    # => false
    vehicle.errors.full_messages      # => ["State event is invalid"]

    vehicle.state_event = 'ignite'
    vehicle.valid?                    # => true
    vehicle.save                      # => true
    vehicle.state                     # => "idling"
    vehicle.state_event               # => nil
  • This can also be done on a mass-assignment basis:

    vehicle = Vehicle.create(:state_event => 'ignite')  # => #<Vehicle id: 1, name: nil, state: "idling">
    vehicle.state                                       # => "idling"
    

This technique is always used for transitioning states when save is left as default action for the machine.

Security implications

  • Public event attributes mean events can be fired whenever mass-assignment is used
  • To prevent tampering with events through URLs / forms, the attribute should be protected:
    class Vehicle < ActiveRecord::Base
      attr_protected :state_event
      # attr_accessible ... # Alternative technique

      state_machine do
        ...
      end
    end
  • To have only some events fire via mass-assignment, you can build two state machines (one public and one protected):
    class Vehicle < ActiveRecord::Base
      attr_protected :state_event # Prevent access to events in the first machine

      state_machine do
        # Define private events here
      end

      # Public machine targets the same state as the private machine
      state_machine :public_state, :attribute => :state do
        # Define public events here
      end
    end

Defining

Options

:human_name

  • Human-readable version of this event's name
  • Default: Stringifies the name and underscores spaces

Block

  • Each event requires a block that defines all transitions possible as a result of the event:

    event :park, :stop do
      transition :idling => :parked
    end
    
    event :first_gear do
      transition :parked => :first_gear, :if => :seatbelt_on?
      transition :parked => same # Allow to loopback if seatbelt is off
    end
    
  • The block is executed within the context of the actual event object

  • Referencing any class methods on the model requires referencing the class itself:

    class Vehicle
      def self.safe_states
        [:parked, :idling, :stalled]
      end
    
      state_machine do
        event :park do
          transition Vehicle.safe_states => :parked
        end
      end
    end
    

More @ transition

Errors (AR)

  • If an event fails to fire because there are no matching transitions for the current record, a validation error is added to the record's state attribute to help in determining why it failed and for reporting via the UI

    vehicle = Vehicle.create(:state => 'idling')  # => #<Vehicle id: 1, name: nil, state: "idling">
    vehicle.ignite                                # => false
    vehicle.errors.full_messages                  # => ["State cannot transition via \"ignite\""]
    
  • If an event fails to fire because of a validation error on the record and not because a matching transition was not available, no error messages will be added to the state attribute - if you're using the bang version of the event, then the failure reason (such as the current validation errors) will be included in the exception that gets raised when the event fails. For example, assuming there's a validation on a field called name on the class:

    vehicle = Vehicle.new
    vehicle.ignite!       # => StateMachines::InvalidTransition: Cannot transition state via :ignite from :parked (Reason(s): Name cannot be blank)
    

results matching ""

    No results matching ""