Claude Code Plugins

Community-maintained marketplace

Feedback
14
0

Specialized skill for Rails business logic with ActiveInteraction, AASM state machines, and ActiveDecorator. Use when implementing complex operations, state transitions, or presentation logic. Enforces interaction pattern over service objects.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name rails-business-logic
description Specialized skill for Rails business logic with ActiveInteraction, AASM state machines, and ActiveDecorator. Use when implementing complex operations, state transitions, or presentation logic. Enforces interaction pattern over service objects.

Rails Business Logic

Implement business logic with ActiveInteraction, state machines, and decorators.

When to Use This Skill

  • Creating business operations with ActiveInteraction
  • Implementing state machines with AASM
  • Adding presentation logic with ActiveDecorator
  • Refactoring service objects to interactions
  • Managing complex workflows
  • Handling state transitions
  • Composing business operations

Core Principle: Use Interactions, NOT Service Objects

Why ActiveInteraction over service objects?

  • ✓ Built-in type checking
  • ✓ Automatic validation
  • ✓ Explicit contracts (inputs/outputs)
  • ✓ Composable
  • ✓ Testable
  • ✓ Self-documenting
# Gemfile
gem "active_interaction", "~> 5.3"
gem "aasm", "~> 5.5"
gem "active_decorator", "~> 1.4"

ActiveInteraction Basics

Simple Interaction

# app/interactions/users/create.rb
module Users
  class Create < ActiveInteraction::Base
    # Define inputs with types
    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 || generate_password
      )

      UserMailer.welcome(user).deliver_later

      user  # Return value becomes outcome.result
    end

    private

    def generate_password
      SecureRandom.alphanumeric(32)
    end
  end
end

Running Interactions

# In controller
def create
  outcome = Users::Create.run(
    email: params[:email],
    name: params[:name],
    password: params[:password]
  )

  if outcome.valid?
    @user = outcome.result
    redirect_to @user, 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")

Input Types

class MyInteraction < ActiveInteraction::Base
  # Primitives
  string :name
  integer :age
  float :price
  boolean :active
  symbol :status

  # Objects
  date :birthday
  time :created_at
  date_time :scheduled_at

  # Complex types
  array :tags
  hash :metadata

  # Model instances
  object :user, class: User

  # Arrays of specific types
  array :emails, default: [] do
    string
  end

  # Optional (nilable)
  string :optional_field, default: nil

  # Custom default
  integer :count, default: 0
end

Composing Interactions

module Users
  class Register < ActiveInteraction::Base
    string :email
    string :name
    string :password

    def execute
      # Compose other interactions
      user = compose(Users::Create,
        email: email,
        name: name,
        password: password
      )

      # compose raises if nested interaction fails
      # errors are merged automatically

      compose(Users::SendWelcomeEmail, user: user)

      user
    end
  end
end

Testing Interactions

RSpec.describe Users::Create do
  let(:valid_params) do
    { email: "user@example.com", name: "John", password: "SecurePass123" }
  end

  context "with valid parameters" do
    it "creates user" 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
    end

    it "returns created user" do
      outcome = described_class.run(valid_params)
      expect(outcome.result).to be_a(User)
    end
  end

  context "with invalid parameters" do
    it "returns invalid outcome" do
      outcome = described_class.run(valid_params.merge(email: nil))
      expect(outcome).not_to be_valid
    end
  end
end

State Machines (AASM)

Basic State Machine

# app/models/order.rb
class Order < ApplicationRecord
  include AASM

  aasm column: :status do
    state :pending, initial: true
    state :paid
    state :processing
    state :shipped
    state :delivered
    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

      after do
        TrackingService.create_shipment(self)
      end
    end

    event :deliver do
      transitions from: :shipped, to: :delivered
    end

    event :cancel do
      transitions from: [:pending, :paid], to: :cancelled

      before do
        refund_payment if paid?
      end
    end
  end

  private

  def refund_payment
    PaymentService.refund(self)
  end
end

Usage

order = Order.create!
order.pending?  # => true
order.status    # => "pending"

# Trigger transitions
order.pay!
order.paid?  # => true

# Check if transition allowed
order.may_ship?  # => false (must process first)
order.may_process?  # => true

order.process!
order.ship!
order.shipped?  # => true

# Get available events
order.aasm.events  # => [:deliver, :cancel]

Guards

class Order < ApplicationRecord
  include AASM

  aasm do
    state :pending, initial: true
    state :paid

    event :pay do
      transitions from: :pending, to: :paid, guard: :payment_valid?
    end
  end

  def payment_valid?
    payment_method.present? && total > 0
  end
end

# Usage
order.pay!  # Raises AASM::InvalidTransition if guard fails
order.pay   # Returns false if guard fails (no exception)

Scopes

AASM automatically creates scopes:

Order.pending    # All pending orders
Order.paid       # All paid orders
Order.shipped    # All shipped orders

# Combine with other scopes
Order.paid.where(user: current_user)

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 from pending to paid" do
      expect { order.pay! }
        .to change(order, :status).from("pending").to("paid")
    end

    it "sends payment receipt" do
      expect { order.pay! }
        .to have_enqueued_mail(OrderMailer, :payment_received)
    end
  end

  describe "ship event" do
    context "when order is paid" do
      before { order.pay!; order.process! }

      it "allows shipping" do
        expect(order.may_ship?).to be true
      end
    end

    context "when order is pending" do
      it "raises error" do
        expect { order.ship! }.to raise_error(AASM::InvalidTransition)
      end
    end
  end
end

Decorators (ActiveDecorator)

Basic Decorator

# app/decorators/user_decorator.rb
module UserDecorator
  def full_name
    "#{first_name} #{last_name}"
  end

  def avatar_url(size: :medium)
    if avatar.attached?
      helpers.url_for(avatar.variant(resize_to_limit: avatar_size(size)))
    else
      helpers.image_url("default-avatar.png")
    end
  end

  def formatted_created_at
    created_at.strftime("%B %d, %Y")
  end

  def status_badge
    case status
    when "active"
      helpers.content_tag(:span, "Active", class: "badge badge-success")
    when "inactive"
      helpers.content_tag(:span, "Inactive", class: "badge badge-secondary")
    end
  end

  private

  def avatar_size(size)
    { small: [50, 50], medium: [100, 100], large: [200, 200] }[size]
  end

  def helpers
    ActionController::Base.helpers
  end
end

Usage in Views

<%# Automatically decorated in views %>
<%= @user.full_name %>
<%= image_tag @user.avatar_url(size: :large) %>
<%= @user.formatted_created_at %>
<%= @user.status_badge %>

<%# Collections decorated automatically %>
<% @users.each do |user| %>
  <p><%= user.full_name %></p>
<% end %>

Testing Decorators

RSpec.describe UserDecorator do
  let(:user) { create(:user, first_name: "John", last_name: "Doe") }

  describe "#full_name" do
    it "combines first and last name" do
      expect(user.full_name).to eq("John Doe")
    end
  end

  describe "#formatted_created_at" do
    it "formats date" do
      user.update(created_at: Date.new(2025, 1, 15))
      expect(user.formatted_created_at).to eq("January 15, 2025")
    end
  end
end

Service Objects vs Interactions

DON'T Use Service Objects

# AVOID - Verbose service object
class UserCreationService
  def initialize(params)
    @params = params
    @errors = []
  end

  def call
    validate_params
    return false if errors.any?
    create_user
  end

  # ... lots of boilerplate
end

DO Use Interactions

# PREFER - Clean interaction
class Users::Create < ActiveInteraction::Base
  string :email
  string :name

  validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }

  def execute
    User.create!(email: email, name: name)
  end
end

Controller Pattern

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(params[:article])
      @article.errors.merge!(outcome.errors)
      render :new, status: :unprocessable_entity
    end
  end
end

Reference Documentation

For comprehensive business logic patterns:

  • Business logic guide: business-logic.md (detailed examples and advanced patterns)