| name | layered-rails |
| description | Design and review Rails applications using layered architecture principles from "Layered Design for Ruby on Rails Applications". Use when analyzing Rails codebases, reviewing PRs for architecture violations, planning feature implementations, or implementing patterns like authorization, view components, or AI integration. Triggers on "layered design", "architecture layers", "abstraction", "refactor","specification test", "layer violation", "extract service", "fat controller", "god object". |
| allowed-tools | ["Grep","Glob","Read","Task"] |
Layered Rails
Design and review Rails applications using layered architecture principles.
Quick Start
Rails applications are organized into four architecture layers with unidirectional data flow:
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā PRESENTATION LAYER ā
ā Controllers, Views, Channels, Mailers ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā APPLICATION LAYER ā
ā Service Objects, Form Objects, etc. ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā DOMAIN LAYER ā
ā Models, Value Objects, Domain Events ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā INFRASTRUCTURE LAYER ā
ā Active Record, APIs, File Storage ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Core Rule: Lower layers must never depend on higher layers.
See Architecture Layers Reference for the full layer responsibilities and the Four Rules deep-dive.
What Would You Like To Do?
- Analyze codebase - Run
/layers:analyze for full analysis or /layers:analyze:services, /layers:analyze:callbacks, /layers:analyze:gods for specific checks
- Review code changes - Run
/layers:review for layered architecture review
- Run specification test - Run
/layers:spec-test on specific files
- Plan gradual adoption - Run
/layers:gradual [goal] to plan incremental layerification
- Plan feature implementation - I'll guide you using layered principles
- Implement specific pattern - I'll help with authorization, notifications, view components, AI integration, etc.
Core Principles
The Four Rules
- Unidirectional Data Flow - Data flows top-to-bottom only
- No Reverse Dependencies - Lower layers never depend on higher layers
- Abstraction Boundaries - Each abstraction belongs to exactly one layer
- Minimize Connections - Fewer inter-layer connections = looser coupling
Common Violations
| Violation | Example | Fix |
|---|
| Model uses Current | Current.user in model | Pass user as explicit parameter |
| Service accepts request | param :request in service | Extract value object from request |
| Controller has business logic | Pricing calculations in action | Extract to service or model |
| Anemic models | All logic in services | Keep domain logic in models |
| Category | Reference |
|---|
| Layer violations (Current in models, request in services, notifications in models, business logic in controllers) | layer-violations.md |
| Service objects (anemic models, bag of random objects, premature abstraction) | service-objects.md |
| Callbacks (operation callbacks, skip callbacks, control flags) | callbacks.md |
| Concerns (code-slicing, overgrown) | concerns.md |
| Helpers (HTML construction in helpers) | helpers.md |
| Jobs (anemic jobs) | jobs.md |
| Testing (testing wrong layer) | testing.md |
The Specification Test
If the specification of an object describes features beyond the primary responsibility of its abstraction layer, such features should be extracted into lower layers.
How to apply:
- List responsibilities the code handles
- Evaluate each against the layer's primary concern
- Extract misplaced responsibilities to appropriate layers
See Specification Test Reference for detailed guide.
Pattern Catalog
| Pattern | Layer | Use When | Reference |
|---|
| Service Object | Application | Orchestrating domain operations | service-objects.md |
| Query Object | Domain | Complex, reusable queries | query-objects.md |
| Form Object | Presentation | Multi-model forms, complex validation | form-objects.md |
| Filter Object | Presentation | Request parameter transformation | filter-objects.md |
| Presenter | Presentation | View-specific logic, multiple models | presenters.md |
| Serializer | Presentation | API response formatting | serializers.md |
| Policy Object | Application | Authorization decisions | policy-objects.md |
| Value Object | Domain | Immutable, identity-less concepts | value-objects.md |
| Collaborator Object | Domain | A slice of one model's behavior in a typed delegate | collaborator-objects.md |
| State Machine | Domain | States, events, transitions | state-machines.md |
| Concern | Domain | Shared behavioral extraction | concerns.md |
| Repository | Application | Last resort ā returning custom domain objects mapped from AR data, after AR scopes (simple) and query objects (query building) are insufficient | repositories.md |
Pattern Selection Guide
"Where should this code go?"
| If you have... | Consider... |
|---|
| Complex multi-model form | Form Object |
| Request parameter filtering/transformation | Filter Object |
| View-specific formatting | Presenter |
| Complex database query used in multiple places | Query Object |
| Business operation spanning multiple models | Service Object (as waiting room) |
| Authorization rules | Policy Object |
| Multi-channel notifications | Delivery Object (Active Delivery) |
Remember: Services are a "waiting room" for code until proper abstractions emerge. Don't let app/services become a bag of random objects.
Refactoring Scenarios
Canonical before/after transformations for the most common layerification moves. The /layers:gradual agent uses these as reference templates when proposing phases.
Commands Reference
| Command | Purpose |
|---|
/layers:review | Review code changes from layered architecture perspective |
/layers:spec-test | Run specification test on specific files |
/layers:analyze | Full codebase abstraction layer analysis |
/layers:analyze:services | Audit app/services/ and service-like classes ā conventions, clusters, layer hygiene, test consequences |
/layers:analyze:callbacks | Score model callbacks, find extraction candidates |
/layers:analyze:gods | Find God objects via churn Ć complexity |
/layers:gradual [goal] | Plan gradual adoption of layered patterns |
Topic References
For deep dives on specific topics:
Gem References
For library-specific guidance:
Extraction Signals
When to extract from models:
| Signal | Metric | Action |
|---|
| God object | High churn Ć complexity | Decompose into concerns, delegates, or separate models |
| Operation callback | Score 1-2/5 | Extract to service or event handler |
| Code-slicing concern | Groups by artifact type | Convert to behavioral concern or extract |
| Current dependency | Model reads Current.* | Pass as explicit parameter |
Callback Scoring:
| Type | Score | Keep? |
|---|
| Transformer (compute values) | 5/5 | Yes |
| Normalizer (sanitize input) | 4/5 | Yes |
| Utility (counter caches) | 4/5 | Yes |
| Observer (side effects) | 2/5 | Maybe |
| Operation (business steps) | 1/5 | Extract |
See Extraction Signals Reference for detailed guide.
Model Organization
Recommended order within model files:
class User < ApplicationRecord
has_secure_password
belongs_to :account
has_many :posts
enum :status, { pending: 0, active: 1 }
normalizes :email, with: -> { _1.strip.downcase }
validates :email, presence: true
scope :active, -> { where(status: :active) }
before_validation :set_defaults
delegate :name, to: :account, prefix: true
def full_name = "#{first_name} #{last_name}"
private
def set_defaults
self.locale ||= I18n.default_locale
end
end
Success Checklist
Well-layered code:
Guidelines
- Use domain language - Name models after business concepts (Participant, not User; Cloud, not GeneratedImage)
- Patterns before abstractions - Let code age before extracting; premature abstraction is worse than duplication
- Services as waiting room - Don't let
app/services become permanent residence for code
- Explicit over implicit - Prefer explicit parameters over Current attributes
- Extraction thresholds - Consider extraction when methods exceed 15 lines or call external APIs