At GoBoat we use the Workflow gem for managing the state of the reservation. Instead of specifying the state machine directly in a model, we opted to extract the state machine into a separate object.

class Reservation < ActiveRecord::Base
  ...
  def state
    @state ||= ReservationState.new(self)
  end
end
class ReservationState
  include Workflow

  attr_reader :reservation

  def initialize(reservation)
    @reservation = reservation
  end

  workflow do
    state :requested do
      event :refuse, transitions_to: :refused
      event :accept, transitions_to: :accepted
    end
    ...
  end
end

Advantages

Keeping the state machine outside of the model has a couple of advantages.

  • Cleans up the model: large state machines will definitely cause your class to go over 100 lines, which is bad for readability and maintainability.
  • Explicit: it’s more explicit that methods call a state machine. Calling reservation.state.accepted? makes it instantly clear that we’re dealing with a ‘magic’ Workflow-method here instead of a boolean attribute in the reservations table.
  • Prevents collisions: state machines have the habit of tacking on many methods for each state and event (GoBoat’s state machine adds about 60).