Skill

layered-rails

Design Rails applications using layered architecture. Use when analyzing codebases for architecture violations, planning feature implementations, deciding where code belongs, or extracting abstractions from fat models/controllers. Complements dhh-coder (which keeps things simple) with guidance for when complexity demands structure.

From majestic-rails
Install
1
Run in your terminal
$
npx claudepluginhub majesticlabs-dev/majestic-marketplace --plugin majestic-rails
Tool Access

This skill uses the workspace's default tool permissions.

Supporting Assets
View in Repository
references/extraction-signals.md
references/pattern-catalog.md
Skill Content

Layered Rails Architecture

Audience: Rails developers working on applications that have outgrown single-file patterns. Goal: Know which layer code belongs in, when to extract, and which existing skill handles the implementation.

Four-Layer Architecture

Presentation  →  Application  →  Domain  →  Infrastructure
(HTTP/UI)        (Orchestration)  (Business)   (Persistence/APIs)

Core rule: Lower layers MUST NOT depend on higher layers. Data flows top-to-bottom only.

Layer Responsibilities

LayerOwnsDoes NOT Own
PresentationHTTP concerns, params, rendering, channels, mailersBusiness logic, direct DB queries
ApplicationOrchestration across models, authorization, form validationPersistence details, rendering
DomainBusiness rules, validations, associations, value objectsHTTP context, request objects, Current.*
InfrastructureActiveRecord, external APIs, file storage, cachingBusiness rules, presentation

Common Layer Violations

ViolationWhy It's WrongFix
Current.user in modelDomain depends on presentation contextPass user as explicit parameter
request param in serviceApplication depends on presentationExtract needed values before calling service
Pricing calc in controllerBusiness logic in presentationMove to model method or service
All logic in services, anemic modelsDomain layer is hollowKeep domain logic in models; services orchestrate
Model sends emails directlyDomain depends on infrastructure side-effectsUse callbacks only for data transforms; extract delivery

The Specification Test

Diagnostic for misplaced code:

  1. List every responsibility the object handles
  2. For each, ask: "Does this belong to this layer's primary concern?"
  3. If NO → extract to the appropriate layer

Example: A User model that handles authentication, avatar processing, notification preferences, and activity logging.

  • Authentication → Domain (keep)
  • Avatar processing → Infrastructure (extract to service/job)
  • Notification preferences → Domain (keep as concern)
  • Activity logging → Infrastructure (extract to observer/event)

See references/extraction-signals.md for the full methodology.

Callback Scoring

Rate each callback 1-5. Extract anything scoring 1-2.

ScoreTypeExampleAction
5Transformerbefore_validation :normalize_emailKeep
4Normalizerbefore_save :strip_whitespaceKeep
4Utilityafter_create :update_counter_cacheKeep
2Observerafter_save :notify_adminConsider extracting
1Operationafter_create :send_welcome_email, :provision_accountExtract

Rule of thumb: If removing the callback would break the model's own data integrity → keep. If it triggers external side-effects → extract.

Pattern Selection

"Where should this code go?"

SituationPatternLayerSkill
Complex multi-model formForm ObjectPresentation
Request param filteringFilter ObjectPresentation
View-specific formattingPresenter / ViewComponentPresentationviewcomponent-coder
Authorization rulesPolicy ObjectApplicationaction-policy-coder
Business operation (one-time)Service / InteractionApplicationactive-interaction-coder
Multi-model orchestrationService ObjectApplicationactive-interaction-coder
State lifecycle managementState MachineDomainaasm-coder
Complex reusable queryQuery ObjectDomain
Immutable concept (Money, DateRange)Value ObjectDomain
Shared model behaviorConcernDomain
Typed configurationConfig ObjectInfrastructureanyway-config-coder
Domain events / audit trailEvent SourcingInfrastructureevent-sourcing-coder
JSON-backed attributesStore ModelDomainstore-model-coder

Decision Tree

Is it about HTTP/params/rendering?
  YES → Presentation layer
    Multi-model form? → Form Object
    Filtering params? → Filter Object
    Formatting for view? → Presenter or ViewComponent
  NO ↓

Is it authorization?
  YES → Policy Object (action-policy-coder)
  NO ↓

Does it orchestrate multiple models/services?
  YES → Application layer
    One-time operation? → Service/Interaction (active-interaction-coder)
    Needs typed inputs? → ActiveInteraction (active-interaction-coder)
  NO ↓

Is it a business rule about a single model?
  YES → Domain layer (keep in model or concern)
    Has state transitions? → AASM (aasm-coder)
    Reusable query? → Query Object
    Immutable value? → Value Object
  NO ↓

Is it about persistence/external APIs/caching?
  YES → Infrastructure layer

Services as Waiting Rooms

app/services/ is a temporary staging area, not a permanent home.

  • Services that survive should eventually reveal the real abstraction they represent
  • If a service wraps a single model operation → it probably belongs in the model
  • If a service coordinates 3+ models → it's a legitimate orchestrator
  • If a service grows complex → look for Form Object, Policy, or Query Object hiding inside

Smell test: If app/services/ has 50+ files and no subdirectories, the waiting room has become permanent storage.

Extraction Signals

When to extract code from existing locations:

SignalThresholdAction
Method length> 15 linesExtract method or object
External API call in modelAnyExtract to service/gateway
God objectHigh churn × high complexityDecompose (see references/extraction-signals.md)
Spec exceeds layer concernSpecification test failsExtract to appropriate layer
Callback score1-2/5Extract to service or event handler
Duplicated query logic2+ locationsExtract Query Object
Current.* in modelAny usagePass as explicit parameter

See references/extraction-signals.md for the complete methodology.

Model Organization

Recommended ordering within model files:

class Order < ApplicationRecord
  # 1. Extensions/DSL (has_secure_password, acts_as_*)
  # 2. Associations
  # 3. Enums
  # 4. Normalizations
  # 5. Validations
  # 6. Scopes
  # 7. Callbacks (transformers/normalizers only — score 4-5)
  # 8. Delegations
  # 9. Public methods
  # 10. Private methods
end

When Layered vs DHH Style

This skill complements dhh-coder, not replaces it.

SituationUse
Small/medium app, standard CRUDdhh-coder — keep it simple
Complex domain, multiple bounded contextslayered-rails — add structure
Authorization beyond simple checksaction-policy-coder via layered guidance
Fat model with 500+ lineslayered-rails extraction signals
Standard controller actionsdhh-coder — 7 REST actions
Multi-step business operationactive-interaction-coder via layered guidance

Default to simplicity. Reach for layered patterns only when complexity demands it.

Success Checklist

  • No reverse dependencies (lower layers don't reference higher)
  • Models don't access Current attributes
  • Services don't accept request/controller objects
  • Controllers contain only HTTP concerns
  • Domain logic lives in models, not leaked into services
  • All callbacks score 4+ (or extracted)
  • Concerns group by behavior, not by artifact type
  • Each abstraction belongs to exactly one layer

Cross-References

NeedSkill
Authorization policiesaction-policy-coder
Typed business operationsactive-interaction-coder
State machinesaasm-coder
Operations + state routingbusiness-logic-coder
ViewComponentsviewcomponent-coder
Typed configurationanyway-config-coder
Event sourcing / auditevent-sourcing-coder
JSON attributesstore-model-coder
Refactoring executionrails-refactorer
DHH-style simplicitydhh-coder
Pattern catalog detailsreferences/pattern-catalog.md
Extraction methodologyreferences/extraction-signals.md
Stats
Parent Repo Stars30
Parent Repo Forks6
Last CommitFeb 15, 2026