Loading...
Loading...
Implement state machines with AASM for workflow management. Covers state transitions, guards, callbacks, and testing.
npx skill4agent add majesticlabs-dev/majestic-marketplace aasm-coder# Gemfile
gem "aasm", "~> 5.5"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
endorder = 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)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)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
endbeforebefore_exitbefore_enterafter_exitafter_enterafterevent :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# 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!
endRSpec.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
endreferences/aasm-patterns.mdevent-sourcing-coder