The Active Record 7.2+ integration adds support for database transactions, automatically saving the record, named scopes, validation errors.
- Ruby 3.2+
- Rails 7.2+
Add this line to your application's Gemfile:
gem 'state_machines-activerecord'
And then execute:
$ bundle
Or install it yourself as:
$ gem install state_machines-activerecord
For the complete usage guide, see http://www.rubydoc.info/github/state-machines/state_machines-activerecord/StateMachines/Integrations/ActiveRecord
class Vehicle < ApplicationRecord
state_machine :initial => :parked do
before_transition :parked => any - :parked, :do => :put_on_seatbelt
after_transition any => :parked do |vehicle, transition|
vehicle.seatbelt = 'off'
end
around_transition :benchmark
event :ignite do
transition :parked => :idling
end
state :first_gear, :second_gear do
validates :seatbelt_on, presence: true
end
end
def put_on_seatbelt
...
end
def benchmark
...
yield
...
end
end
Usage of the generated scopes (assuming default column state
):
Vehicle.with_state(:parked) # also plural #with_states
Vehicle.without_states(:first_gear, :second_gear) # also singular #without_state
State scopes will return all records when nil
is passed, making them perfect for search filters:
Vehicle.with_state(nil) # Returns all vehicles
Vehicle.with_state(params[:state]) # Returns all vehicles if params[:state] is nil
Vehicle.where(color: 'red').with_state(nil) # Returns all red vehicles (chainable)
When your ActiveRecord model uses Rails enums and defines a state machine on the same attribute, this gem automatically detects the conflict and provides seamless integration. This prevents method name collisions between Rails enum methods and state machine methods.
class Order < ApplicationRecord
# Rails enum definition
enum :status, { pending: 0, processing: 1, completed: 2, cancelled: 3 }
# State machine on the same attribute
state_machine :status do
state :pending, :processing, :completed, :cancelled
event :process do
transition pending: :processing
end
event :complete do
transition processing: :completed
end
event :cancel do
transition [:pending, :processing] => :cancelled
end
end
end
When enum integration is detected, the gem automatically:
- Preserves original Rails enum methods (
pending?
,processing?
, etc.) - Generates prefixed state machine methods to avoid conflicts (
status_pending?
,status_processing?
, etc.) - Creates prefixed scope methods (
Order.status_pending
,Order.status_processing
, etc.)
Original Rails enum methods (preserved):
order = Order.create(status: :pending)
order.pending? # => true (Rails enum method)
order.processing? # => false (Rails enum method)
order.processing! # Sets status to :processing (Rails enum method)
Order.pending # Rails enum scope
Order.processing # Rails enum scope
Generated state machine methods (prefixed):
# Predicate methods
order.status_pending? # => true (state machine method)
order.status_processing? # => false (state machine method)
order.status_completed? # => false (state machine method)
# Bang methods (for conflict resolution only)
# These are placeholders and raise runtime errors
order.status_processing! # => raises RuntimeError
# Scope methods
Order.status_pending # State machine scope
Order.status_processing # State machine scope
Order.not_status_pending # Negative state machine scope
The integration provides a comprehensive introspection API for advanced use cases:
machine = Order.state_machine(:status)
# Check if enum integration is enabled
machine.enum_integrated? # => true
# Get the Rails enum mapping
machine.enum_mapping # => {"pending"=>0, "processing"=>1, "completed"=>2, "cancelled"=>3}
# Get original Rails enum methods that were preserved
machine.original_enum_methods
# => ["pending?", "processing?", "completed?", "cancelled?", "pending!", "processing!", ...]
# Get state machine methods that were generated
machine.state_machine_methods
# => ["status_pending?", "status_processing?", "status_completed?", "status_cancelled?", ...]
- The state machine attribute must match an existing Rails enum attribute
- Auto-detection is enabled by default when this condition is met
The enum integration supports several configuration options:
prefix
(default: true) - Adds a prefix to generated methods to avoid conflictssuffix
(default: false) - Alternative naming strategy using suffixes instead of prefixesscopes
(default: true) - Controls whether state machine scopes are generated
As mentioned in StateMachines::Machine#state
, you can define behaviors,
like validations, that only execute for certain states. One important
caveat here is that, due to a constraint in ActiveRecord's validation
framework, custom validators will not work as expected when defined to run
in multiple states. For example:
class Vehicle < ApplicationRecord
state_machine do
state :first_gear, :second_gear do
validate :speed_is_legal
end
end
end
In this case, the :speed_is_legal validation will only get run for the :second_gear state. To avoid this, you can define your custom validation like so:
class Vehicle < ApplicationRecord
state_machine do
state :first_gear, :second_gear do
validate {|vehicle| vehicle.speed_is_legal}
end
end
end
- Fork it ( https://github.com/state-machines/state_machines-activerecord/fork )
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a new Pull Request