business-logic-coder

Installation
SKILL.md

Business Logic Patterns

Orchestrator for structured business logic in Rails.

Skill Routing

Need Skill Use When
Typed operations active-interaction-coder Creating business operations, refactoring service objects
State machines aasm-coder Implementing workflows, managing state transitions

When to Use Each

ActiveInteraction

Use for operations - things that happen once:

  • User registration
  • Order placement
  • Payment processing
  • Data imports
  • Report generation
# Example: One-time operation with typed inputs
outcome = Users::Create.run(email: email, name: name)

AASM

Use for state management - things with lifecycle:

  • Order status (pending → paid → shipped)
  • Subscription state (trial → active → cancelled)
  • Document workflow (draft → review → published)
  • Task status (todo → in_progress → done)
# Example: Stateful entity with transitions
order.pay!  # pending → paid
order.ship! # paid → shipped

Combined Pattern

Often used together - interactions trigger state changes:

module Orders
  class Process < ActiveInteraction::Base
    object :order, class: Order

    def execute
      order.process!  # AASM transition
      fulfill_order(order)
      order
    end
  end
end

Setup

# Gemfile
gem "active_interaction", "~> 5.3"
gem "aasm", "~> 5.5"

Quick Reference

ActiveInteraction:

  • .run - Returns outcome (check .valid?)
  • .run! - Raises on failure
  • compose - Call nested interactions

AASM:

  • .event! - Transition or raise
  • .event - Transition or return false
  • .may_event? - Check if transition valid
  • .aasm.events - List available events

Transaction Boundaries

When business logic involves multi-step database operations, enforce proper transaction boundaries.

Atomic Operations

# PROBLEM: Partial failure leaves inconsistent state
def transfer_funds(from, to, amount)
  from.update!(balance: from.balance - amount)
  to.update!(balance: to.balance + amount)  # May fail!
end

# SOLUTION: Transaction wrapper with locking
def transfer_funds(from, to, amount)
  ActiveRecord::Base.transaction do
    from.lock!
    to.lock!
    from.update!(balance: from.balance - amount)
    to.update!(balance: to.balance + amount)
  end
end

Isolation Levels

# Default: READ COMMITTED (each query sees latest committed data)
# REPEATABLE READ: All reads see same snapshot
ActiveRecord::Base.transaction(isolation: :repeatable_read) do
  # Consistent reads for reports
end

# SERIALIZABLE: Full isolation (may cause serialization failures)
ActiveRecord::Base.transaction(isolation: :serializable) do
  # Strictest isolation, retry on failure
end

Deadlock Prevention

Rules:

  1. Lock records in consistent order (by ID ascending)
  2. Keep transactions short
  3. No user input inside transactions
  4. No external API calls inside transactions
# PROBLEM: Deadlock risk (inconsistent lock order)
def swap_owners(item_a, item_b)
  transaction do
    item_a.lock!  # Thread 1 locks A
    item_b.lock!  # Thread 2 locks B -> deadlock!
  end
end

# SOLUTION: Consistent lock order
def swap_owners(item_a, item_b)
  items = [item_a, item_b].sort_by(&:id)
  transaction do
    items.each(&:lock!)
    # Safe: always locks lower ID first
  end
end

Transaction Anti-Patterns

Anti-Pattern Problem Solution
Long transactions Lock contention Split into smaller units
API calls inside Timeout blocks DB Call outside, then transact
User input inside Indefinite locks Validate first, transact last
Nested transactions Savepoint confusion Use requires_new: true explicitly

Nested Transactions

# PROBLEM: Inner rollback doesn't work as expected
transaction do
  create_order!
  transaction do  # This is a savepoint, not new transaction
    charge_card!  # Failure rolls back to savepoint only
  end
end

# SOLUTION: Explicit new transaction
transaction do
  create_order!
  transaction(requires_new: true) do
    charge_card!  # Failure rolls back only this block
  end
end

Related Skills

  • event-sourcing-coder - Record domain events from state transitions
Weekly Installs
36
GitHub Stars
37
First Seen
3 days ago