### Migration Guide: String to Integer Column
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/examples-and-patterns.md
A migration example for changing the state storage from a string column to an integer column, including updating the model to use integer values for states.
```ruby
# Migration
class MigrateVehicleStatesToInteger < ActiveRecord::Migration[7.2]
def change
# Add new integer column
add_column :vehicles, :status_code, :integer, default: 0, null: false
# Copy data with conversion
reversible do |dir|
dir.up do
Vehicle.find_each do |vehicle|
code = case vehicle.state
when 'parked' then 0
when 'idling' then 1
when 'first_gear' then 2
else 0
end
vehicle.update_column(:status_code, code)
end
end
dir.down do
# Reverse conversion if needed
end
end
# Remove old column, rename new one
remove_column :vehicles, :state
rename_column :vehicles, :status_code, :state
end
end
# Update model
class Vehicle < ApplicationRecord
state_machine :state do
state :parked, value: 0
state :idling, value: 1
state :first_gear, value: 2
end
end
```
--------------------------------
### Callback Configuration Example
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/configuration.md
Example demonstrating various callback configurations within a state machine definition.
```ruby
class Vehicle < ApplicationRecord
state_machine initial: :parked do
before_transition :any => :idling do |vehicle|
vehicle.put_on_seatbelt
end
after_transition :any => :parked do |vehicle|
vehicle.remove_keys_from_ignition
end
around_transition do |block|
puts "Before transition"
block.call
puts "After transition"
end
after_failure do |vehicle, transition|
LogFailedTransition.create(vehicle_id: vehicle.id)
end
end
end
```
--------------------------------
### Gem Installation
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/configuration.md
Instructions for installing the state_machines-activerecord gem using Bundler.
```ruby
# Gemfile
gem 'state_machines-activerecord'
# Then: bundle install
```
--------------------------------
### Automatic Configuration Example
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/configuration.md
Provides an example of the configuration object generated when enum integration is automatically detected.
```ruby
class Order < ApplicationRecord
enum :status, { pending: 0, processing: 1, completed: 2 }
state_machine :status do
state :pending, :processing, :completed
end
end
# Auto-detected configuration:
# {
# enabled: true,
# prefix: true, # Use "status_" prefix
# suffix: false, # No suffix
# scopes: true, # Generate scope methods
# enum_values: { "pending" => 0, "processing" => 1, ... },
# original_enum_methods: ["pending?", "processing?", ...],
# state_machine_methods: ["status_pending?", "status_processing?", ...]
# }
```
--------------------------------
### Testing State Transitions
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/examples-and-patterns.md
Provides an example of how to test state transitions in a Rails application using ActiveSupport::TestCase.
```ruby
require 'test_helper'
class VehicleTest < ActiveSupport::TestCase
def setup
@vehicle = Vehicle.new(state: 'parked')
end
test 'should transition from parked to idling' do
assert @vehicle.ignite
assert_equal 'idling', @vehicle.state
end
test 'should not transition from idling to idling' do
@vehicle.state = 'idling'
assert !@vehicle.ignite
end
test 'should persist state to database' do
@vehicle.ignite
@vehicle.save
loaded = Vehicle.find(@vehicle.id)
assert_equal 'idling', loaded.state
end
test 'should run before_transition callback' do
called = false
@vehicle.class.state_machine do
before_transition { called = true }
end
@vehicle.ignite
assert called
end
end
```
--------------------------------
### Verify Integer Type Setup
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/machine-methods.md
Example showing how to verify if the integer type is registered for compact storage in state machines.
```ruby
class Vehicle < ApplicationRecord
state_machine :status do
state :pending, value: 0
state :active, value: 1
end
end
machine = Vehicle.state_machine(:status)
if machine.integer_type_registered?
puts "Integer type is registered"
puts "Using compact storage"
else
puts "Using string storage"
end
```
--------------------------------
### Event Method Examples
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/activerecord-integration.md
Examples of generated instance methods for handling state machine events.
```ruby
vehicle.ignite # => true if transition succeeds, false otherwise
vehicle.ignite! # => true if succeeds, raises StateMachines::InvalidTransition if fails
vehicle.can_ignite? # => true if transition is possible from current state
vehicle.ignite_transition # => Returns the Transition object that would be performed
```
--------------------------------
### before_transition Callback Example
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/activerecord-integration.md
Example of defining a `before_transition` callback to perform an action or log information before a state change.
```ruby
class Vehicle < ApplicationRecord
state_machine do
before_transition :any => :idling do |vehicle|
vehicle.put_on_seatbelt
end
before_transition do |vehicle, transition|
puts "Transitioning from #{transition.from} to #{transition.to}"
end
end
end
```
--------------------------------
### Installation
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/README.md
Instructions for installing the state_machines-activerecord gem using Bundler.
```ruby
# Gemfile
gem 'state_machines-activerecord'
```
```bash
bundle install
```
--------------------------------
### Integer Column Storage
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/examples-and-patterns.md
Shows how to use integer columns for compact state storage, including a migration example and model definition with explicit integer values for states.
```ruby
# Migration
class CreateMessages < ActiveRecord::Migration[7.2]
def change
create_table :messages do |t|
t.string :content
t.integer :status, null: false, default: 0 # Compact integer storage
t.timestamps
end
end
end
# Model with explicit integer values
class Message < ApplicationRecord
state_machine :status do
state :pending, value: 0
state :sending, value: 1
state :sent, value: 2
state :failed, value: 3
event :send do
transition :pending => :sending
end
event :succeed do
transition :sending => :sent
end
event :fail do
transition [:pending, :sending] => :failed
end
end
end
# Usage
message = Message.create(status: :pending)
message.status # => "pending" (string, convenient)
message.read_attribute_before_type_cast(:status) # => 0 (stored in DB)
message.succeed
# Database stores integer 2, less space than storing "sent"
```
--------------------------------
### around_transition Callback Example
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/activerecord-integration.md
Example of an `around_transition` callback used for wrapping the transition process, such as for benchmarking.
```ruby
class Vehicle < ApplicationRecord
state_machine do
around_transition do |block|
start = Time.current
block.call
puts "Transition took #{Time.current - start} seconds"
end
end
end
```
--------------------------------
### Async Job Processing
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/examples-and-patterns.md
An example of a state machine for background job states, including transitions, callbacks for triggering background workers, and the worker implementation.
```ruby
class AnalysisJob < ApplicationRecord
state_machine :state, initial: :queued do
state :queued
state :processing
state :completed
state :failed
state :retrying
event :start_processing do
transition :queued => :processing
end
event :complete do
transition :processing => :completed
end
event :fail do
transition [:processing, :retrying] => :failed
end
event :retry do
transition :failed => :retrying
end
after_transition :queued => :processing do |job|
AnalysisWorker.perform_in(5.seconds, job.id)
end
after_transition :failed => :retrying do |job|
AnalysisWorker.perform_in(1.hour, job.id)
end
end
end
# Worker
class AnalysisWorker
include Sidekiq::Worker
def perform(job_id)
job = AnalysisJob.find(job_id)
return unless job.start_processing
begin
result = perform_analysis(job)
job.complete
rescue => e
job.fail
job.retry if job.retries < 3
end
end
private
def perform_analysis(job)
# Long-running analysis
end
end
```
--------------------------------
### Basic State Machine
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/examples-and-patterns.md
A simple state machine with states and events.
```ruby
class Vehicle < ApplicationRecord
state_machine :state, initial: :parked do
state :parked
state :idling
state :first_gear
state :second_gear
event :ignite do
transition :parked => :idling
end
event :shift_up do
transition :idling => :first_gear
transition :first_gear => :second_gear
end
event :park do
transition [:idling, :first_gear, :second_gear] => :parked
end
end
end
# Usage
vehicle = Vehicle.create(state: :parked)
vehicle.parked? # => true
vehicle.ignite # => true
vehicle.state # => 'idling'
vehicle.can_shift_up? # => true
vehicle.shift_up # => true
vehicle.can_ignite? # => false (already running)
vehicle.park # => true
```
--------------------------------
### after_transition Callback Example
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/activerecord-integration.md
Example of using an `after_transition` callback to update a record after a successful state change.
```ruby
class Vehicle < ApplicationRecord
state_machine do
after_transition :any => :parked do |vehicle|
vehicle.update_column(:last_parked_at, Time.current)
end
end
end
```
--------------------------------
### Translation Example
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/activerecord-integration.md
An example of how to define translations for states and events in YAML format.
```yaml
en:
activerecord:
state_machines:
vehicle:
state:
states:
parked: "Parked"
idling: "Idling"
events:
ignite: "Start engine"
```
--------------------------------
### Default Scope Names
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/configuration.md
Example showing the automatically generated state scopes and how they are used.
```ruby
class Vehicle < ApplicationRecord
state_machine :state do
state :parked, :idling
end
end
# Generated scopes:
Vehicle.with_state(:parked) # Find all parked vehicles
Vehicle.with_states(:parked, :idling) # Find parked or idling
Vehicle.without_state(:parked) # Exclude parked vehicles
Vehicle.without_states(:parked, :idling) # Exclude both
# Transparent scopes (when nil is passed):
Vehicle.with_state(nil) # All vehicles
Vehicle.where(color: 'red').with_state(params[:state]) # Chainable
```
--------------------------------
### Scopes in Search/Filter Interfaces
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/examples-and-patterns.md
Shows how to use state scopes for filtering records in a search interface, along with an example of how to implement this in a Rails controller and view.
```ruby
class JobsController < ApplicationController
def index
@jobs = Job.all
@jobs = @jobs.with_state(params[:status]) if params[:status].present?
@jobs = @jobs.where(category: params[:category]) if params[:category].present?
@jobs = @jobs.order(created_at: :desc)
end
end
# View
<%= form_with method: :get, local: true do |form|
<%= form.select :status, [
['All', nil],
['Open', 'open'],
['Closed', 'closed']
], {} %>
<%= form.submit "Filter" %>
<% end %>
<%= link_to "All", jobs_path %>
<%= link_to "Open", jobs_path(status: 'open') %>
<%= link_to "Closed", jobs_path(status: 'closed') %>
```
--------------------------------
### Example: Default Integration Settings
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/configuration.md
Illustrates how the default integration settings for action and use_transactions are applied when defining a state machine.
```ruby
class Vehicle < ApplicationRecord
# action defaults to :save
# use_transactions defaults to true
state_machine initial: :parked do
# ...
end
# Same as:
state_machine initial: :parked, action: :save, use_transactions: true do
# ...
end
end
```
--------------------------------
### Multiple State Machines on One Model
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/examples-and-patterns.md
Demonstrates how a single ActiveRecord model can manage multiple independent state machines, with an example of defining two state machines and using their states in validations.
```ruby
class ProjectTask < ApplicationRecord
# First state machine for task workflow
state_machine :status, initial: :todo do
state :todo
state :in_progress
state :completed
state :cancelled
event :start do
transition :todo => :in_progress
end
event :finish do
transition :in_progress => :completed
end
event :abandon do
transition [:todo, :in_progress] => :cancelled
end
end
# Second state machine for review process
state_machine :review_status, initial: :unreviewed do
state :unreviewed
state :approved
state :rejected
state :changes_requested
event :approve do
transition :unreviewed => :approved
end
event :request_changes do
transition [:unreviewed, :changes_requested] => :changes_requested
end
end
# Can check either state
validates :name, presence: true
validates :assignee_id, presence: true, if: -> { in_progress? }
validates :summary, presence: true, if: -> { approved? }
end
# Usage
task = ProjectTask.create(name: "Fix bug", status: :todo, review_status: :unreviewed)
task.start # => status: in_progress
task.start # => false (can't start again)
task.request_changes # => review_status: changes_requested
task.status # => 'in_progress'
task.review_status # => 'changes_requested'
```
--------------------------------
### State Event Setter and Getter Example
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/generated-methods.md
Demonstrates setting and getting a state event, checking validity, saving, and observing attribute clearing.
```ruby
vehicle = Vehicle.new(state: :parked)
vehicle.state_event # => nil
vehicle.state_event = 'ignite'
vehicle.state_event # => 'ignite'
vehicle.valid? # => true (transition is valid)
vehicle.save # Performs the ignite event
vehicle.state # => 'idling'
vehicle.state_event # => nil (cleared after save)
```
--------------------------------
### State Machine Example
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/activerecord-integration.md
An example demonstrating how to define a state machine on an ActiveRecord model, including states, events, transitions, validations, and callbacks.
```ruby
class Vehicle < ApplicationRecord
state_machine :state, initial: :parked do
event :ignite do
transition :parked => :idling
end
event :shift_up do
transition :idling => :first_gear
transition :first_gear => :second_gear
end
state :first_gear, :second_gear do
validates :seatbelt_on, presence: true
end
before_transition :any => :idling do |vehicle|
vehicle.put_on_seatbelt
end
after_transition any => :parked do |vehicle, transition|
vehicle.seatbelt = 'off'
end
around_transition do |block|
puts "Before transition"
block.call
puts "After transition"
end
end
def put_on_seatbelt
self.seatbelt_on = true
end
end
# Usage
vehicle = Vehicle.create(state: 'parked')
vehicle.ignite # => true, saves to database
vehicle.state # => 'idling'
vehicle = Vehicle.new
vehicle.state_event = 'ignite' # Set event to fire on save
vehicle.save # Fires the event during save
```
--------------------------------
### Bank Account with Transaction Rollback
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/examples-and-patterns.md
Illustrates how state machine transitions can be transactional, with examples of `before_transition` callbacks that can cause a rollback if they fail, and `after_failure` callbacks that execute outside the transaction.
```ruby
class BankAccount < ApplicationRecord
state_machine :status, initial: :active use_transitions: true do
state :active
state :suspended
state :closed
event :suspend do
transition :active => :suspended
end
event :close do
transition [:active, :suspended] => :closed
end
before_transition :active => :suspended do |account|
# If this fails, the entire transition is rolled back
account.notify_owner("Account suspended")
account.log_suspension_event
end
after_transition :any => :closed do |account|
account.send_closure_letter
end
after_failure do |account, transition|
# This runs OUTSIDE the transaction
AuditLog.create(
account_id: account.id,
event: transition.trigger,
status: 'failed'
)
end
end
end
class SuspensionNotification < ApplicationRecord
# This would be rolled back if the transition fails
end
class AuditLog < ApplicationRecord
# Use a separate database connection that won't be rolled back
connects_to database: { writing: :analytics, reading: :analytics }
end
# Usage
account = BankAccount.create(status: :active)
account.suspend # => If notify_owner or log_suspension_event fails,
# no changes are made and AuditLog is NOT created
account.suspend # => If successful, SuspensionNotification is created,
# AuditLog is created (persisted outside transaction)
```
--------------------------------
### Testing Scopes
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/examples-and-patterns.md
Examples of testing state machine scopes in ActiveRecord, including filtering by state, handling nil values, and chaining scopes.
```ruby
test 'should filter by state with scope' do
Vehicle.create(state: 'parked')
Vehicle.create(state: 'parked')
Vehicle.create(state: 'idling')
assert_equal 2, Vehicle.with_state(:parked).count
assert_equal 1, Vehicle.with_state(:idling).count
end
test 'with_state should handle nil transparently' do
Vehicle.create(state: 'parked')
Vehicle.create(state: 'idling')
assert_equal 2, Vehicle.with_state(nil).count
end
test 'scopes should be chainable' do
Vehicle.create(state: 'parked', color: 'red')
Vehicle.create(state: 'parked', color: 'blue')
Vehicle.create(state: 'idling', color: 'red')
result = Vehicle.with_state(:parked).where(color: 'red')
assert_equal 1, result.count
end
```
--------------------------------
### State Bang Method Example
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/activerecord-integration.md
Example of generated state bang methods for transitioning to a specific state and saving the model.
```ruby
vehicle.parked! # Transitions to parked state and saves
vehicle.idling! # Transitions to idling state and saves
```
--------------------------------
### Event Attribute Example
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/activerecord-integration.md
Example of using the event attribute to queue a transition for the next save operation.
```ruby
vehicle.state_event # => nil (no pending event)
vehicle.state_event = 'ignite'
vehicle.valid? # => Validates that the transition is valid
vehicle.save # Performs the transition during save
```
--------------------------------
### after_failure Callback Example
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/activerecord-integration.md
Example of an `after_failure` callback to log transition failures without rolling back the transaction.
```ruby
class TransitionLog < ApplicationRecord
connects_to database: { writing: :primary, reading: :primary }
end
class Vehicle < ApplicationRecord
state_machine do
after_failure do |vehicle, transition|
TransitionLog.create(vehicle: vehicle, transition: transition)
end
end
end
```
--------------------------------
### State Predicate Method Example
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/activerecord-integration.md
Example of generated state predicate methods for checking the current state of a model.
```ruby
vehicle.parked? # => true
vehicle.idling? # => false
```
--------------------------------
### Example: Automatic Type Registration
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/configuration.md
Demonstrates automatic type registration when column is :integer and states have explicit integer values.
```ruby
class Vehicle < ApplicationRecord
# Assumes :status column is of type :integer
state_machine :status do
state :pending, value: 0
state :active, value: 1
state :archived, value: 2
end
end
# StateMachines::Type::Integer is automatically registered
# No manual configuration needed
vehicle = Vehicle.new(status: :pending)
vehicle.save
# Database stores: 0
# Attribute returns: "pending"
```
--------------------------------
### Event Attributes in Forms
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/examples-and-patterns.md
Using event attributes for form-based transitions.
```ruby
# Model
class Document < ApplicationRecord
state_machine :approval_state, initial: :submitted do
state :submitted
state :approved
state :rejected
event :approve do
transition :submitted => :approved
end
event :reject do
transition :submitted => :rejected
end
end
end
# View
<%= form_with(model: document, local: true) do |form|
%><% if document.submitted? %>
<%= form.label :approval_state_event %>
<%= form.select :approval_state_event, [
['Approve', 'approve'],
['Reject', 'reject']
], { include_blank: 'Choose action...' } %>
<% end %>
<%= form.submit %>
<% end %>
# Controller
class DocumentsController < ApplicationController
def update
@document = Document.find(params[:id])
if @document.update(document_params)
redirect_to @document, notice: 'Document updated'
else
render :edit, alert: 'Update failed'
end
end
private
def document_params
params.require(:document).permit(:approval_state_event)
end
end
```
--------------------------------
### Transition Object Callback Example
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/types.md
An example of using the transition object within `before_transition` and `after_transition` callbacks.
```ruby
class Vehicle < ApplicationRecord
state_machine do
before_transition do |vehicle, transition|
puts "From #{transition.from} to #{transition.to} via #{transition.trigger}"
end
after_transition do |vehicle, transition|
TransitionLog.create(
vehicle_id: vehicle.id,
from_state: transition.from,
to_state: transition.to,
event: transition.trigger
)
end
end
end
```
--------------------------------
### Callbacks and Side Effects
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/examples-and-patterns.md
State machines with callbacks that perform actions on transitions.
```ruby
class Order < ApplicationRecord
state_machine :status, initial: :pending do
state :pending
state :processing
state :completed
state :cancelled
event :process do
transition :pending => :processing
end
event :complete do
transition :processing => :completed
end
event :cancel do
transition [:pending, :processing] => :cancelled
end
before_transition :pending => :processing do |order|
order.charge_customer
end
after_transition :processing => :completed do |order|
order.send_confirmation_email
order.trigger_fulfillment
end
after_failure do |order, transition|
OrderFailureLog.create(
order_id: order.id,
event: transition.trigger,
reason: order.errors.full_messages.join(', ')
)
end
end
def charge_customer
return if charged?
# Payment logic
self.charged_at = Time.current
end
def send_confirmation_email
OrderMailer.confirmation_email(self).deliver_later
end
def trigger_fulfillment
FulfillmentService.start(self)
end
end
# Usage
order = Order.create(status: :pending)
order.process # => Charges customer, saves
order.charged_at # => Time when charged
order.complete # => Sends email, triggers fulfillment
```
--------------------------------
### Event Transition Method Example
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/generated-methods.md
Example of using the event transition method to retrieve the transition object without performing it.
```Ruby
vehicle = Vehicle.create(state: :parked)
transition = vehicle.ignite_transition
# => #
transition.from # => :parked
transition.to # => :idling
transition.trigger # => :ignite
transition.record # => vehicle
```
--------------------------------
### State Bang Method Example
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/generated-methods.md
Example showing how to use bang methods to transition a record to a new state and save it immediately.
```Ruby
class Vehicle < ApplicationRecord
state_machine :state, initial: :parked do
state :parked, :idling, :first_gear
event :ignite do
transition :parked => :idling
end
end
end
vehicle = Vehicle.create
vehicle.idling! # Transitions to idling state
vehicle.state # => 'idling'
vehicle.reload # Verify saved to database
vehicle.state # => 'idling'
```
--------------------------------
### Event Bang Method Example
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/generated-methods.md
Example demonstrating the event bang method, which performs a transition and raises an error on failure.
```Ruby
vehicle = Vehicle.create
vehicle.ignite! # => true
vehicle.ignite! # Raises: StateMachines::InvalidTransition
# Message: "Cannot transition state via :ignite from :idling"
```
--------------------------------
### State Driven Validations Example 1
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/README.md
Demonstrates defining validations that should execute for specific states. This example shows a potential issue with custom validators in multiple states.
```ruby
class Vehicle < ApplicationRecord
state_machine do
state :first_gear, :second_gear do
validate :speed_is_legal
end
end
end
```
--------------------------------
### Event Predicate Method Example
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/generated-methods.md
Example showing how to use the 'can_event_name?' predicate method to check if an event can be triggered.
```Ruby
vehicle = Vehicle.create(state: :parked)
vehicle.can_ignite? # => true
vehicle.ignite
vehicle.can_ignite? # => false (can't ignite from idling)
```
--------------------------------
### Custom Plural Names
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/configuration.md
Example of configuring custom plural names for state scopes.
```ruby
class Vehicle < ApplicationRecord
state_machine :vehicle_state, plural: { vehicle_state: 'vehicle_states' } do
state :parked, :idling
end
end
# Plural name is used internally in scope implementation
# but scope methods still use standard names
```
--------------------------------
### Transition Syntax Examples
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/types.md
Provides various syntaxes for defining transitions between states.
```ruby
transition :from => :to # Single source and target
transition [:from1, :from2] => :to # Multiple sources, single target
transition :any => :to # Any state to target
transition :from => [:to1, :to2] # Single source, multiple targets (less common)
```
--------------------------------
### Default Configuration
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/configuration.md
Example of using default options when defining a state machine on an ActiveRecord model.
```ruby
class Vehicle < ApplicationRecord
# Uses default options
state_machine :state, initial: :parked do
# ...
end
end
# Equivalent to:
state_machine :state, initial: :parked, action: :save, use_transactions: true do
# ...
end
```
--------------------------------
### Manual Type Registration
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/configuration.md
Example of manually registering the StateMachines::Type::Integer when automatic detection doesn't work.
```ruby
class Vehicle < ApplicationRecord
attribute :status, StateMachines::Type::Integer.new(state_machine(:status).states)
state_machine :status do
state :pending, value: 0
state :active, value: 1
end
end
```
--------------------------------
### State Predicate Method Example
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/generated-methods.md
Example demonstrating the usage of state predicate methods to check the current state of a record.
```Ruby
class Vehicle < ApplicationRecord
state_machine :state, initial: :parked do
state :parked, :idling, :first_gear
end
end
vehicle = Vehicle.create
vehicle.parked? # => true
vehicle.idling? # => false
vehicle.first_gear? # => false
vehicle.ignite
vehicle.parked? # => false
vehicle.idling? # => true
```
--------------------------------
### Event Trigger Method Example
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/generated-methods.md
Example illustrating the use of event trigger methods to perform state transitions.
```Ruby
class Vehicle < ApplicationRecord
state_machine :state, initial: :parked do
event :ignite do
transition :parked => :idling
end
end
end
vehicle = Vehicle.create
vehicle.ignite # => true
vehicle.state # => 'idling'
vehicle.ignite # => false (can't ignite from idling)
```
--------------------------------
### Model Definition Example
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/README.md
An example of how to define a state machine within an Active Record model, including event transitions, callbacks, and state-specific validations.
```ruby
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
```
--------------------------------
### Example: Instance Method `deserialize`
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/integer-type.md
Illustrates how the `deserialize` method converts an integer value from the database into a state name string.
```ruby
type = StateMachines::Type::Integer.new(states)
type.deserialize(0) # => "pending"
type.deserialize(1) # => "active"
type.deserialize(999) # => "999" (unknown value)
type.deserialize(nil) # => nil
```
--------------------------------
### Testing Event Attributes
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/examples-and-patterns.md
Demonstrates testing the use of event attributes for triggering state transitions and validating invalid events.
```ruby
test 'should queue event via event attribute' do
vehicle = Vehicle.new
vehicle.state_event = 'ignite'
assert vehicle.valid?
assert vehicle.save
assert_equal 'idling', vehicle.state
end
test 'should validate invalid event' do
vehicle = Vehicle.new
vehicle.state_event = 'invalid_event'
assert !vehicle.valid?
assert vehicle.errors[:state_event].any?
end
```
--------------------------------
### Optimizing Storage Example
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/integer-type.md
Illustrates the storage space difference between string and integer representations of state names.
```ruby
# String storage
state = "very_long_state_name" # => ~20 bytes per record
# Integer storage
state = 5 # => 4 bytes per record
```
--------------------------------
### Custom Action
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/configuration.md
Example of configuring a custom action for state transitions, such as updating a specific column instead of saving the entire record.
```ruby
class Vehicle < ApplicationRecord
# Transition to idle state without saving
state_machine :state, initial: :parked, action: :idle_transition do
# ...
end
def idle_transition
# Custom action - not the standard save
update_column(:last_transition_at, Time.current)
end
end
```
--------------------------------
### Enum Integration Example
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/integer-type.md
Demonstrates seamless integration with Rails enums on integer columns.
```ruby
class Order < ApplicationRecord
enum :status, { pending: 0, processing: 1, completed: 2 }
state_machine :status do
state :pending, value: 0
state :processing, value: 1
state :completed, value: 2
end
end
order = Order.new(status: :pending)
order.pending? # => true (works with enum)
```
--------------------------------
### State-Scoped Validations
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/examples-and-patterns.md
Validations that only apply in specific states.
```ruby
class BlogPost < ApplicationRecord
state_machine :state, initial: :draft do
state :draft
state :published
state :archived
state :published do
validates :title, presence: true
validates :content, presence: true, length: { minimum: 100 }
validates :published_at, presence: true
# Inline validator for multiple states
validate { |post| post.check_post_quality }
end
event :publish do
transition :draft => :published
end
event :archive do
transition [:draft, :published] => :archived
end
end
def check_post_quality
return if content.blank?
if content.length < 500
errors.add(:content, "should be substantial (500+ chars recommended)")
end
end
end
# Usage
post = BlogPost.new(state: :draft)
post.valid? # => true (validations don't apply in draft)
post.state = :published
post.title = ""
post.valid? # => false (title is required in published)
# errors.full_messages => ["Title can't be blank", ...]
```
--------------------------------
### Auto-Detection Example
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/configuration.md
Shows how enum integration is automatically enabled when a Rails enum exists on the same attribute and no explicit configuration is provided.
```ruby
class Order < ApplicationRecord
enum :status, { pending: 0, processing: 1, completed: 2 }
state_machine :status do
# Auto-detection: enum integration is enabled
state :pending, :processing, :completed
end
end
# Check if enum integration is active
Order.state_machine(:status).enum_integrated? # => true
```
--------------------------------
### InvalidTransition Exception Handling
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/activerecord-integration.md
Example of how to rescue and handle the StateMachines::InvalidTransition exception.
```ruby
begin
vehicle.ignite!
rescue StateMachines::InvalidTransition => e
puts e.message # "Cannot transition state via :ignite from :parked"
puts e.reason # "Name cannot be blank"
end
```
--------------------------------
### State-Scoped Validations
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/configuration.md
Example of state-scoped validations where a validation only runs in specific states.
```ruby
class Vehicle < ApplicationRecord
state_machine do
state :first_gear, :second_gear do
validates :seatbelt_on, presence: true
end
end
end
# The validates callback runs only in first_gear and second_gear states
```
--------------------------------
### i18n Integration
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/configuration.md
Example of how the state machine gem automatically registers its locale with Rails' i18n on load.
```ruby
# In state_machines-activerecord.rb
ActiveSupport.on_load(:i18n) do
I18n.load_path << File.expand_path('state_machines/integrations/active_record/locale.rb', __dir__)
end
```
--------------------------------
### Example Translation File
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/types.md
A sample YAML file demonstrating translations for state machine elements.
```yaml
en:
activerecord:
errors:
messages:
invalid: "is invalid"
invalid_event: "cannot transition when %{state}"
invalid_transition: "cannot transition via %{event}"
state_machines:
vehicle:
state:
states:
parked: "Parked"
idling: "Idling"
first_gear: "1st Gear"
events:
ignite: "Start engine"
shift_up: "Shift up"
```
--------------------------------
### Use Transition Introspection
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/errors.md
Example of using state machine introspection to check if a transition is possible and get details about it.
```ruby
machine = Vehicle.state_machine(:state)
transition = machine.transitions(vehicle, :ignite)
if transition
puts "Transition possible: #{transition.from} => #{transition.to}"
else
puts "No transition found"
end
```
--------------------------------
### State Scope Example
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/generated-methods.md
Demonstrates using `with_state` and `with_states` scopes with different parameter types and chaining.
```ruby
class Vehicle < ApplicationRecord
state_machine :state do
state :parked, :idling, :first_gear
end
end
# Singular form
Vehicle.with_state(:parked) # Records in parked state
Vehicle.with_state('parked') # String form (converted internally)
# Plural form
Vehicle.with_states(:parked, :idling) # Records in either state
Vehicle.with_states([:parked, :idling]) # Array form
# Transparent scope (nil returns all)
Vehicle.with_state(nil) # All records
# Chainable
Vehicle.where(color: 'red').with_state(:parked).order(created_at: :asc)
# In search interface
class SearchService
def vehicles_by_state(state = nil)
Vehicle.with_state(state) # Returns all if state is nil
end
end
```
--------------------------------
### Custom Plural
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/configuration.md
Example of using custom plural names for state scopes, which affects the naming of generated scope methods.
```ruby
class Person < ApplicationRecord
state_machine :state, plural: { state: 'states' } do
state :active, :inactive
end
end
# Generated scope: Person.with_state(:active)
# The plural name is used internally
```
--------------------------------
### State-Driven Validations Workaround
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/activerecord-integration.md
Example showing how to use inline validators to ensure custom validators run on multiple states.
```ruby
class Vehicle < ApplicationRecord
state_machine do
state :first_gear, :second_gear do
# This will only run in second_gear
validate :speed_is_legal
# This will run in both states
validate { |vehicle| vehicle.speed_is_legal }
end
end
def speed_is_legal
errors.add(:speed, "is too high") if speed > 100
end
end
```
--------------------------------
### Example: Instance Method `type`
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/integer-type.md
Shows how the `type` method returns the ActiveRecord attribute type identifier.
```ruby
type = StateMachines::Type::Integer.new(states)
type.type # => :integer
```
--------------------------------
### State-based Scopes: with_state / with_states
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/activerecord-integration.md
Examples of using the generated `with_state` and `with_states` scopes to query records based on their state.
```ruby
Vehicle.with_state(:parked) # Records in parked state
Vehicle.with_states(:parked, :idling) # Records in either state
Vehicle.with_state(nil) # All records (transparent scope)
Vehicle.where(color: 'red').with_state(params[:state]) # Chainable
```
--------------------------------
### State Machine Accessor Example
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/generated-methods.md
Demonstrates retrieving state machine instances and performing introspection on them.
```ruby
class Vehicle < ApplicationRecord
state_machine :state do
state :parked, :idling
event :ignite do
transition :parked => :idling
end
end
state_machine :status do
state :active, :inactive
end
end
# Get default state machine
machine = Vehicle.state_machine
machine.attribute # => :state
# Get specific state machine
state_machine = Vehicle.state_machine(:state)
status_machine = Vehicle.state_machine(:status)
# Introspection
state_machine.states.map(&:name) # => [:parked, :idling]
state_machine.events.map(&:name) # => [:ignite, ...]
```
--------------------------------
### generate_state_method_name Example
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/machine-methods.md
Examples of using generate_state_method_name with and without enum integration.
```ruby
# With enum integration (prefix applied)
machine.generate_state_method_name(:pending, :predicate) # => "status_pending?"
machine.generate_state_method_name(:pending, :bang) # => "status_pending!"
machine.generate_state_method_name(:pending, :scope) # => "status_pending"
# Without enum integration (no prefix)
machine.generate_state_method_name(:parked, :predicate) # => "parked?"
```
--------------------------------
### Validation Errors Example
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/activerecord-integration.md
Demonstrates how validation errors are added to the state attribute when a transition is invalid or a save fails.
```ruby
vehicle = Vehicle.create(state: 'idling')
vehicle.ignite # => false
vehicle.errors.full_messages # => ["State cannot transition via \"ignite\""]
vehicle = Vehicle.new
vehicle.ignite! # Raises StateMachines::InvalidTransition
```
--------------------------------
### Callback Block Arguments - Two Arguments
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/types.md
Example of a callback receiving both the record instance and the transition object.
```ruby
before_transition do |vehicle, transition|
# vehicle is the model instance
# transition is the Transition object
puts "From #{transition.from} to #{transition.to}"
end
```
--------------------------------
### Introspect Configuration
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/machine-methods.md
Example of how to introspect the configuration of a state machine, including enum integration and storage type.
```ruby
class Product < ApplicationRecord
enum :status, { draft: 0, published: 1 }
state_machine :status do
state :draft, :published
end
end
machine = Product.state_machine(:status)
config = {
enum_integrated: machine.enum_integrated?,
enum_mapping: machine.enum_mapping,
original_enum_methods: machine.original_enum_methods,
state_machine_methods: machine.state_machine_methods,
integer_type_registered: machine.integer_type_registered?
}
puts config.inspect
```
--------------------------------
### Without State Scope Example
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/generated-methods.md
Demonstrates using `without_state` and `without_states` scopes with different parameter types and chaining.
```ruby
# Singular form
Vehicle.without_state(:parked) # Excludes parked records
# Plural form
Vehicle.without_states(:parked, :idling) # Excludes both states
# Transparent scope
Vehicle.without_state(nil) # All records
# Chainable
Vehicle.where(color: 'red').without_state(:parked).limit(10)
# Combining with and without
Vehicle.with_state(:parked, :idling).without_state(:broken)
```
--------------------------------
### Use Transactions (Default: true)
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/configuration.md
Demonstrates how state machine transitions are wrapped in database transactions by default, and how a `before_transition` hook can halt the transition and cause a rollback.
```ruby
class Vehicle < ApplicationRecord
# Transactions enabled (default)
state_machine :state, use_transactions: true do
before_transition do |vehicle|
Message.create(content: "Transitioning...")
false # Halt - transaction rolls back
end
end
end
vehicle = Vehicle.create
vehicle.ignite # => false
Message.count # => 0 (rolled back)
```
--------------------------------
### Index-Based Fallback Example
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/integer-type.md
Illustrates how the type uses the index position of a state as its integer value when no explicit value is defined.
```ruby
states = [
State.new(:pending), # index 0
State.new(:processing), # index 1
State.new(:cancelled, value: 999), # explicit value
State.new(:archived) # index 2 (skips cancelled's explicit value)
]
type.serialize("pending") # => 0 (index)
type.serialize("archived") # => 2 (index)
type.serialize("cancelled") # => 999 (explicit)
```
--------------------------------
### State Translation Keys Example
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/types.md
Specific example of state translation keys for a Vehicle model.
```text
activerecord.state_machines.vehicle.state.states.parked
activerecord.state_machines.vehicle.states.parked
activerecord.state_machines.state.states.parked
activerecord.state_machines.states.parked
```
--------------------------------
### State-based Scopes: without_state / without_states
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/activerecord-integration.md
Examples of using the generated `without_state` and `without_states` scopes to query records that are not in specific states.
```ruby
Vehicle.without_state(:parked) # Excludes parked records
Vehicle.without_states(:parked, :idling) # Excludes both states
Vehicle.without_state(nil) # All records (transparent scope)
```
--------------------------------
### Example: Instance Method `serialize`
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/integer-type.md
Demonstrates how the `serialize` method converts a state name (string or symbol) into its corresponding integer value for database storage.
```ruby
type = StateMachines::Type::Integer.new(states)
type.serialize("pending") # => 0
type.serialize(:active) # => 1
type.serialize("archived") # => 2
type.serialize(nil) # => nil
```
--------------------------------
### detect_existing_enum_methods Example Return Value
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/machine-methods.md
Example return value for detect_existing_enum_methods, showing detected Rails enum methods.
```ruby
[
"pending?", # Predicate method
"processing?",
"pending!", # Bang method
"processing!",
"pending", # Scope method
"processing",
"not_pending", # Negated scope
"not_processing"
]
```
--------------------------------
### API with Exception-Based Error Handling
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/examples-and-patterns.md
Demonstrates how to use 'bang' methods (methods ending with '!') for state machine transitions in an API context, and how to handle `StateMachines::InvalidTransition` exceptions for robust error reporting.
```ruby
class PaymentsController < ApplicationController
rescue_from StateMachines::InvalidTransition, with: :handle_invalid_transition
def capture
@payment = Payment.find(params[:id])
begin
@payment.capture!
render json: { status: 'captured', payment: @payment }
rescue StateMachines::InvalidTransition => e
render json: { error: e.reason }, status: :unprocessable_entity
end
end
def refund
@payment = Payment.find(params[:id])
begin
@payment.refund!
render json: { status: 'refunded', payment: @payment }
rescue StateMachines::InvalidTransition => e
render json: { error: e.reason }, status: :conflict
end
end
private
def handle_invalid_transition(exception)
render json: { error: 'Invalid state transition' }, status: :conflict
end
end
```
--------------------------------
### Integration Defaults
Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/configuration.md
Demonstrates how to inspect the default integration settings for ActiveRecord state machines.
```ruby
StateMachines::Integrations::ActiveRecord.defaults
# => { action: :save, use_transactions: true }
```