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 as after 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 returning false: ```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" ```

results matching ""

    No results matching ""