Implement business logic with ActiveInteraction and AASM state machines. Use when creating typed operations, managing state transitions, or refactoring service objects. Triggers on interaction creation, state machines, workflows, or typed business operations.
Implement business logic with typed ActiveInteraction operations and AASM state machines instead of service objects. Use when creating operations with type safety, managing workflow states, or refactoring services. Triggers on interaction creation, state machines, workflows, or typed business operations.
/plugin marketplace add majesticlabs-dev/majestic-marketplace/plugin install majestic-rails@majestic-marketplaceThis skill inherits all available tools. When active, it can use any tool Claude has access to.
references/aasm-patterns.mdreferences/active-interaction.mdImplement business logic with ActiveInteraction and AASM state machines - the structured alternatives to service objects.
Use ActiveInteraction instead of service objects.
| Service Objects | ActiveInteraction |
|---|---|
| No standard interface | Consistent .run / .run! |
| Manual type checking | Built-in type declarations |
| Manual validation | Standard Rails validations |
| Hard to compose | Native composition |
| Verbose boilerplate | Clean, self-documenting |
# Gemfile
gem "active_interaction", "~> 5.3"
gem "aasm", "~> 5.5"
# app/interactions/users/create.rb
module Users
class Create < ActiveInteraction::Base
# Typed inputs
string :email
string :name
string :password, default: nil
# Validations
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :name, presence: true
# Main logic
def execute
user = User.create!(
email: email,
name: name,
password: password || SecureRandom.alphanumeric(32)
)
UserMailer.welcome(user).deliver_later
user # Return value becomes outcome.result
end
end
end
# In controller
def create
outcome = Users::Create.run(
email: params[:email],
name: params[:name]
)
if outcome.valid?
redirect_to outcome.result, notice: "User created"
else
@errors = outcome.errors
render :new, status: :unprocessable_entity
end
end
# With bang method (raises on error)
user = Users::Create.run!(email: "user@example.com", name: "John")
class MyInteraction < ActiveInteraction::Base
# Primitives
string :name
integer :age
float :price
boolean :active
symbol :status
# Date/Time
date :birthday
time :created_at
date_time :scheduled_at
# Complex types
array :tags
hash :metadata
# Model instances
object :user, class: User
# Typed arrays
array :emails, default: [] do
string
end
# Optional with default
string :optional_field, default: nil
integer :count, default: 0
end
module Users
class Register < ActiveInteraction::Base
string :email, :name, :password
def execute
# Compose calls another interaction
user = compose(Users::Create,
email: email,
name: name,
password: password
)
# Errors automatically merged if nested fails
compose(Users::SendWelcomeEmail, user: user)
user
end
end
end
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
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)
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)
class ArticlesController < ApplicationController
def create
outcome = Articles::Create.run(
title: params[:article][:title],
body: params[:article][:body],
author: current_user
)
if outcome.valid?
redirect_to article_path(outcome.result), notice: "Article created"
else
@article = Article.new(article_params)
@article.errors.merge!(outcome.errors)
render :new, status: :unprocessable_entity
end
end
end
RSpec.describe Users::Create do
let(:valid_params) { { email: "user@example.com", name: "John" } }
it "creates user with valid inputs" do
expect { described_class.run(valid_params) }
.to change(User, :count).by(1)
end
it "returns valid outcome" do
outcome = described_class.run(valid_params)
expect(outcome).to be_valid
expect(outcome.result).to be_a(User)
end
it "validates email format" do
outcome = described_class.run(valid_params.merge(email: "invalid"))
expect(outcome).not_to be_valid
expect(outcome.errors[:email]).to be_present
end
end
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
end
describe "ship event" do
context "when pending" do
it "raises error" do
expect { order.ship! }.to raise_error(AASM::InvalidTransition)
end
end
end
end
For advanced patterns:
references/active-interaction.md - Composition, error handling, custom typesreferences/aasm-patterns.md - Callbacks, multiple state machines, persistenceevent-sourcing-coder - For recording domain events and dispatching to inbox handlers. Use when AASM state transitions should trigger notifications, webhooks, or audit trails.This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.