.claude/skills/extraction-timing/SKILL.md
Guides decisions about when and how to extract code into services, queries, concerns, form objects, or other patterns. Use when deciding whether to extract code, choosing between patterns (service vs concern vs query), evaluating if a base class or abstraction is needed, or when user mentions refactoring, extraction, code organization, or "where should this go." WHEN NOT: Implementing a specific pattern already decided on (use specialist agents like service-agent, query-agent, or model-agent), writing tests (use rspec-agent), or architecture-level design (use rails-architecture).
npx skillsauth add ThibautBaissac/rails_ai_agents extraction-timingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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.
The 2025 Rails consensus has evolved beyond "Fat Models" to Skinny Everything:
| Signal | Action | |--------|--------| | Controller action exceeds ~10 lines of business logic | Extract to service object | | Model exceeds ~100 lines | Extract business logic to services, complex queries to query objects | | Query joins multiple tables or has conditional clauses | Extract to query object | | Form touches multiple models or has custom validation | Extract to form object | | Display formatting logic in model | Extract to presenter | | UI element reused across 2+ views | Extract to ViewComponent | | Shared behavior across 2+ models (narrow, simple) | Extract to concern | | 5+ concrete implementations with identical structure | Extract base class | | One-off operation | Don't extract. Inline is fine. |
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
| | Concerns | Service Objects |
|--|---------|----------------|
| Use for | Simple shared model properties | Multi-step business operations |
| Examples | SoftDeletable, Searchable, Sluggable | CreateOrder, ProcessRefund, ImportCsv |
| Max size | ~30 lines | No hard limit (but SRP applies) |
| Test via | Including model's specs | Isolated 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 | Polymorphic |
|--|-----|------------|
| Use when | Subclasses share >80% of columns | Types have unique attributes |
| Table | One shared table with type column | Separate tables per type |
| Avoid when | >20% columns are NULL for some subtypes | Types are fundamentally similar |
Rule: Callbacks for data normalization only. Everything else is explicit.
| Acceptable Callbacks | Must 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 |
Before extracting, verify you're not creating:
model.update! call (Service Graveyard)See @docs/rails-development-principles.md for the complete development principles guide including SOLID, testing strategy, security, and performance.
development
Creates Turbo Streams, Turbo Frames, and morphing patterns for real-time UI updates. Use when adding real-time updates, partial page rendering, form submissions, or broadcasting. WHEN NOT: For Stimulus JavaScript controllers (see stimulus-patterns skill). For general view conventions (see rules/views.md).
testing
Writes Minitest tests with fixtures following 37signals conventions. Uses Minitest (not RSpec) and fixtures (not factories). Use when writing tests, adding test coverage, or creating fixtures. WHEN NOT: For RSpec or FactoryBot patterns (this project uses Minitest + fixtures exclusively). For test configuration/CI setup (see project docs).
tools
Builds focused, single-purpose Stimulus controllers for progressive enhancement. Use when adding JavaScript behavior, UI interactions, form enhancements, or building reusable client-side components. WHEN NOT: For Turbo Stream/Frame patterns (see turbo-patterns skill). For server-side view logic (see rules/views.md).
testing
Implements the state-as-records-not-booleans pattern for rich state tracking. Use when modeling state changes, replacing boolean flags with record-based state, or when user mentions state records, closures, publications, or toggling state. WHEN NOT: Technical flags like cached/processed (use booleans), concern extraction (use concern-patterns), general model work (use model-patterns).