Rails_ai_agents extraction-timing

install
source · Clone the upstream repo
git clone https://github.com/ThibautBaissac/rails_ai_agents
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/ThibautBaissac/rails_ai_agents "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.claude/skills/extraction-timing" ~/.claude/skills/thibautbaissac-rails-ai-agents-extraction-timing && rm -rf "$T"
manifest: .claude/skills/extraction-timing/SKILL.md
source content

You are an expert in Rails code organization and extraction decisions. Help decide when to extract, what pattern to use, and when to keep it simple.

Core Philosophy: Skinny Everything

The 2025 Rails consensus has evolved beyond "Fat Models" to Skinny Everything:

  • Controllers: orchestrate (delegate to services, render responses) -- max ~10 lines per action
  • Models: persist (validations, associations, scopes, simple predicates) -- max ~100 lines
  • Services: contain business logic (multi-step operations, external calls, orchestration)
  • Views: display markup with zero logic

Extraction Thresholds

SignalAction
Controller action exceeds ~10 lines of business logicExtract to service object
Model exceeds ~100 linesExtract business logic to services, complex queries to query objects
Query joins multiple tables or has conditional clausesExtract to query object
Form touches multiple models or has custom validationExtract to form object
Display formatting logic in modelExtract to presenter
UI element reused across 2+ viewsExtract to ViewComponent
Shared behavior across 2+ models (narrow, simple)Extract to concern
5+ concrete implementations with identical structureExtract base class
One-off operationDon't extract. Inline is fine.

Decision Tree: "Where Should This Code Go?"

Is it a database query?
  ├── Simple (one table, one condition) → Model scope
  └── Complex (joins, conditionals, reused) → Query object

Is it business logic?
  ├── Simple CRUD on one model → Controller inline (or model method)
  ├── Multi-step operation → Service object
  ├── Involves external API → Service object
  └── Spans multiple models → Service object with transaction

Is it shared behavior?
  ├── Property of the model (soft-delete, slugs, search) → Concern
  └── Operation on the model (checkout, import, sync) → Service object

Is it display logic?
  ├── Formatting one model's data → Presenter (SimpleDelegator)
  ├── Reusable UI element → ViewComponent
  └── Simple helper method → Keep in helper (use sparingly)

Is it validation?
  ├── Single model, standard rules → Model validation
  ├── Multi-model form → Form object
  └── Business rule (not data integrity) → Service validation

Settled Debates

Concerns vs Service Objects

ConcernsService Objects
Use forSimple shared model propertiesMulti-step business operations
Examples
SoftDeletable
,
Searchable
,
Sluggable
CreateOrder
,
ProcessRefund
,
ImportCsv
Max size~30 linesNo hard limit (but SRP applies)
Test viaIncluding model's specsIsolated unit specs

Rule: If the behavior is a property of the model, use a concern. If it's an operation on the model, use a service.

STI vs Polymorphic Associations

STIPolymorphic
Use whenSubclasses share >80% of columnsTypes have unique attributes
TableOne shared table with
type
column
Separate tables per type
Avoid when>20% columns are NULL for some subtypesTypes are fundamentally similar

Callbacks vs Explicit Calls

Rule: Callbacks for data normalization only. Everything else is explicit.

Acceptable CallbacksMust Be Explicit (in services)
before_validation :strip_whitespace
Sending emails
before_save :downcase_email
Enqueuing background jobs
before_destroy :check_dependencies
Calling external APIs
after_initialize :set_defaults
Creating related records with business logic

Anti-Pattern Checklist

Before extracting, verify you're not creating:

  • A service that wraps a single
    model.update!
    call (Service Graveyard)
  • A base class for only 2 services (Premature Abstraction)
  • A concern with multiple responsibilities (Kitchen Sink Concern)
  • A helper that should be a presenter or component
  • An abstraction for a hypothetical future need (YAGNI violation)

Reference

See @docs/rails-development-principles.md for the complete development principles guide including SOLID, testing strategy, security, and performance.