AASM Coder
State machine patterns for managing workflow states in Rails.
Setup
Gemfile
gem "aasm", "~> 5.5"
Basic State Machine
class Order < ApplicationRecord include AASM
aasm column: :status do state :pending, initial: true state :paid state :processing state :shipped state :cancelled
event :pay do
transitions from: :pending, to: :paid
after do
OrderMailer.payment_received(self).deliver_later
end
end
event :process do
transitions from: :paid, to: :processing
end
event :ship do
transitions from: :processing, to: :shipped
end
event :cancel do
transitions from: [:pending, :paid], to: :cancelled
before do
refund_payment if paid?
end
end
end end
Usage
order = Order.create! order.pending? # => true order.may_pay? # => true order.pay! # Transition + callbacks order.paid? # => true
order.may_ship? # => false (must process first) order.aasm.events # => [:process, :cancel]
Scopes created automatically
Order.pending Order.paid.where(user: current_user)
Guards
event :pay do transitions from: :pending, to: :paid, guard: :payment_valid? end
def payment_valid? payment_method.present? && total > 0 end
Usage
order.pay! # Raises AASM::InvalidTransition if guard fails order.pay # Returns false (no exception)
Callbacks
aasm do
State callbacks
state :paid, before_enter: :validate_payment, after_enter: :send_receipt
Event callbacks
event :ship do before do generate_tracking_number end
after do
notify_customer
end
transitions from: :processing, to: :shipped
end end
Callback order:
-
before (event)
-
before_exit (old state)
-
before_enter (new state)
-
State change persisted
-
after_exit (old state)
-
after_enter (new state)
-
after (event)
Multiple Transitions
event :approve do transitions from: :pending, to: :approved, guard: :auto_approvable? transitions from: :pending, to: :review, guard: :needs_review? transitions from: :pending, to: :rejected # fallback end
First matching guard wins.
Error Handling
Safe (returns false on failure)
order.pay # => false if invalid
Raises exception
begin order.pay! rescue AASM::InvalidTransition => e Rails.logger.error("Invalid transition: #{e.message}") end
Check before transition
if order.may_pay? order.pay! end
Testing State Machines
RSpec.describe Order do let(:order) { create(:order) }
it "starts in pending state" do expect(order).to be_pending end
describe "pay event" do it "transitions to paid" do expect { order.pay! } .to change(order, :status).from("pending").to("paid") end
it "sends payment received email" do
expect(OrderMailer).to receive_message_chain(:payment_received, :deliver_later)
order.pay!
end
end
describe "ship event" do context "when pending" do it "raises error" do expect { order.ship! }.to raise_error(AASM::InvalidTransition) end end
context "when processing" do
before { order.update!(status: :processing) }
it "transitions to shipped" do
expect { order.ship! }
.to change(order, :status).to("shipped")
end
end
end end
Advanced Patterns
For multiple state machines, persistence options, and history tracking see:
- references/aasm-patterns.md
Related Skills
- event-sourcing-coder
- For recording domain events when state transitions should trigger notifications, webhooks, or audit trails.