or run

tessl search
Log in

rails-architecture

tessl install github:ThibautBaissac/rails_ai_agents --skill rails-architecture

github.com/ThibautBaissac/rails_ai_agents

Guides modern Rails 8 code architecture decisions and patterns. Use when deciding where to put code, choosing between patterns (service objects vs concerns vs query objects), designing feature architecture, refactoring for better organization, or when user mentions architecture, code organization, design patterns, or layered design.

Review Score

91%

Validation Score

13/16

Implementation Score

85%

Activation Score

100%

Modern Rails 8 Architecture Patterns

Overview

Rails 8 follows "convention over configuration" with a layered architecture that separates concerns. This skill guides architectural decisions for clean, maintainable code.

Architecture Decision Tree

Where should this code go?
│
├─ Is it view/display formatting?
│   └─ → Presenter (see: rails-presenter skill)
│
├─ Is it complex business logic?
│   └─ → Service Object (see: rails-service-object skill)
│
├─ Is it a complex database query?
│   └─ → Query Object (see: rails-query-object skill)
│
├─ Is it shared behavior across models?
│   └─ → Concern (see: rails-concern skill)
│
├─ Is it authorization logic?
│   └─ → Policy (see: authorization-pundit skill)
│
├─ Is it reusable UI with logic?
│   └─ → ViewComponent (see: viewcomponent-patterns skill)
│
├─ Is it async/background work?
│   └─ → Job (see: solid-queue-setup skill)
│
├─ Is it a complex form (multi-model, wizard)?
│   └─ → Form Object (see: form-object-patterns skill)
│
├─ Is it a transactional email?
│   └─ → Mailer (see: action-mailer-patterns skill)
│
├─ Is it real-time/WebSocket communication?
│   └─ → Channel (see: action-cable-patterns skill)
│
├─ Is it data validation only?
│   └─ → Model (see: rails-model-generator skill)
│
└─ Is it HTTP request/response handling only?
    └─ → Controller (see: rails-controller skill)

Layer Interaction Flow

┌─────────────────────────────────────────────────────────────┐
│                        REQUEST                               │
└─────────────────────────┬───────────────────────────────────┘
                          ▼
┌─────────────────────────────────────────────────────────────┐
│                     CONTROLLER                               │
│  • Authenticate (Authentication concern)                     │
│  • Authorize (Policy)                                        │
│  • Parse params                                              │
│  • Delegate to Service/Query                                 │
└──────────┬─────────────────────────────────┬────────────────┘
           │                                 │
           ▼                                 ▼
┌─────────────────────┐           ┌─────────────────────┐
│      SERVICE        │           │       QUERY         │
│  • Business logic   │           │  • Complex queries  │
│  • Orchestration    │           │  • Aggregations     │
│  • Transactions     │           │  • Reports          │
└──────────┬──────────┘           └──────────┬──────────┘
           │                                 │
           ▼                                 ▼
┌─────────────────────────────────────────────────────────────┐
│                        MODEL                                 │
│  • Validations  • Associations  • Scopes  • Callbacks       │
└─────────────────────────┬───────────────────────────────────┘
                          │
           ┌──────────────┴──────────────┐
           ▼                             ▼
┌─────────────────────┐       ┌─────────────────────┐
│     PRESENTER       │       │    VIEW COMPONENT   │
│  • Formatting       │       │  • Reusable UI      │
│  • Display logic    │       │  • Encapsulated     │
└──────────┬──────────┘       └──────────┬──────────┘
           │                             │
           └──────────────┬──────────────┘
                          ▼
┌─────────────────────────────────────────────────────────────┐
│                       RESPONSE                               │
└─────────────────────────────────────────────────────────────┘

ASYNC FLOWS:
┌─────────────────────┐       ┌─────────────────────┐
│        JOB          │       │      CHANNEL        │
│  • Background work  │       │  • Real-time        │
│  • Solid Queue      │       │  • WebSockets       │
└─────────────────────┘       └─────────────────────┘

EMAIL FLOWS:
┌─────────────────────┐
│       MAILER        │
│  • Transactional    │
│  • Notifications    │
└─────────────────────┘

See layer-interactions.md for detailed examples.

Layer Responsibilities

LayerResponsibilityShould NOT contain
ControllerHTTP, params, responseBusiness logic, queries
ModelData, validations, relationsDisplay logic, HTTP
ServiceBusiness logic, orchestrationHTTP, display logic
QueryComplex database queriesBusiness logic
PresenterView formatting, badgesBusiness logic, queries
PolicyAuthorization rulesBusiness logic
ComponentReusable UI encapsulationBusiness logic
JobAsync processingHTTP, display logic
FormComplex form handlingPersistence logic
MailerEmail compositionBusiness logic
ChannelWebSocket communicationBusiness logic

Project Directory Structure

app/
├── channels/            # Action Cable channels
├── components/          # ViewComponents (UI + logic)
├── controllers/
│   └── concerns/        # Shared controller behavior
├── forms/               # Form objects
├── helpers/             # Simple view helpers (avoid)
├── jobs/                # Background jobs (Solid Queue)
├── mailers/             # Action Mailer classes
├── models/
│   └── concerns/        # Shared model behavior
├── policies/            # Pundit authorization
├── presenters/          # View formatting
├── queries/             # Complex queries
├── services/            # Business logic
│   └── result.rb        # Shared Result class
└── views/
    └── layouts/
        └── mailer.html.erb  # Email layout

Core Principles

1. Skinny Controllers

Controllers should only:

  • Authenticate/authorize
  • Parse params
  • Call service/query
  • Render response
# GOOD: Thin controller
class OrdersController < ApplicationController
  def create
    result = Orders::CreateService.new.call(
      user: current_user,
      params: order_params
    )

    if result.success?
      redirect_to result.data, notice: t(".success")
    else
      flash.now[:alert] = result.error
      render :new, status: :unprocessable_entity
    end
  end
end

# BAD: Fat controller with business logic
class OrdersController < ApplicationController
  def create
    @order = Order.new(order_params)
    @order.user = current_user

    if @order.valid?
      inventory_available = @order.items.all? do |item|
        Product.find(item.product_id).inventory >= item.quantity
      end

      if inventory_available
        # ... more logic
      end
    end
  end
end

2. Rich Models, Smart Services

Models handle:

  • Validations
  • Associations
  • Scopes
  • Simple derived attributes

Services handle:

  • Multi-model operations
  • External API calls
  • Complex business rules
  • Transactions across models

3. Result Objects for Services

All services return a consistent Result object:

class Result
  attr_reader :data, :error, :code

  def initialize(success:, data: nil, error: nil, code: nil)
    @success = success
    @data = data
    @error = error
    @code = code
  end

  def success? = @success
  def failure? = !@success
end

4. Multi-Tenancy by Default

All queries scoped through account:

# GOOD: Scoped through account
def index
  @events = current_account.events.recent
end

# BAD: Unscoped query
def index
  @events = Event.where(user_id: current_user.id)
end

When NOT to Abstract (Avoid Over-Engineering)

SituationKeep It SimpleDon't Create
Simple CRUD (< 10 lines)Keep in controllerService object
Used only onceInline the codeAbstraction
Simple query with 1-2 conditionsModel scopeQuery object
Basic text formattingHelper methodPresenter
Single model formform_with model:Form object
Simple partial without logicPartialViewComponent

Signs of Over-Engineering

# OVER-ENGINEERED: Service for simple save
class Users::UpdateEmailService
  def call(user, email)
    user.update(email: email)  # Just do this in controller!
  end
end

# KEEP IT SIMPLE
class UsersController < ApplicationController
  def update
    if @user.update(user_params)
      redirect_to @user
    else
      render :edit
    end
  end
end

When TO Abstract

SignalAction
Same code in 3+ placesExtract to concern/service
Controller action > 15 linesExtract to service
Model > 300 linesExtract concerns
Complex conditionalsExtract to policy/service
Query joins 3+ tablesExtract to query object
Form spans multiple modelsExtract to form object

Pattern Selection Guide

Use Service Objects When:

  • Logic spans multiple models
  • External API calls needed
  • Complex business rules
  • Need consistent error handling
  • Logic reused across controllers/jobs

→ See rails-service-object skill for details.

Use Query Objects When:

  • Complex SQL/ActiveRecord queries
  • Aggregations and statistics
  • Dashboard data
  • Reports

→ See rails-query-object skill for details.

Use Presenters When:

  • Formatting data for display
  • Status badges with colors
  • Currency/date formatting
  • Conditional display logic

→ See rails-presenter skill for details.

Use Concerns When:

  • Shared validations across models
  • Common scopes (e.g., Searchable)
  • Shared callbacks (e.g., HasUuid)
  • Keep it single-purpose!

→ See rails-concern skill for details.

Use ViewComponents When:

  • Reusable UI with logic
  • Complex partials
  • Need testable views
  • Cards, tables, badges

→ See viewcomponent-patterns skill for details.

Use Form Objects When:

  • Multi-model forms
  • Wizard/multi-step forms
  • Search/filter forms
  • Contact forms (no persistence)

→ See form-object-patterns skill for details.

Use Policies When:

  • Resource authorization
  • Role-based access
  • Action permissions
  • Scoped collections

→ See authorization-pundit skill for details.

Rails 8 Specific Features

Authentication (Built-in Generator)

bin/rails generate authentication

Uses has_secure_password with Session model, Current class, and password reset flow.

→ See authentication-flow skill for details.

Background Jobs (Solid Queue)

Database-backed job processing, no Redis required.

→ See solid-queue-setup skill for details.

Real-time (Action Cable + Solid Cable)

WebSocket support with database-backed adapter.

→ See action-cable-patterns skill for details.

Caching (Solid Cache)

Database-backed caching, no Redis required.

→ See caching-strategies skill for details.

Other Rails 8 Defaults

FeaturePurpose
PropshaftAsset pipeline (replaces Sprockets)
ImportmapJavaScript without bundling
KamalDocker deployment
ThrusterHTTP/2 proxy with caching

Anti-Patterns to Avoid

Anti-PatternProblemSolution
God ModelModel > 500 linesExtract services/concerns
Fat ControllerLogic in controllersMove to services
Callback HellComplex model callbacksUse services
Helper SoupMassive helper modulesUse presenters/components
N+1 QueriesUnoptimized queriesUse .includes(), query objects
Stringly TypedMagic strings everywhereUse constants, enums
Premature AbstractionService for 3 linesKeep in controller

→ See performance-optimization skill for N+1 detection.

Testing Strategy by Layer

LayerTest TypeFocus
ModelUnitValidations, scopes, methods
ServiceUnitBusiness logic, edge cases
QueryUnitQuery results, tenant isolation
PresenterUnitFormatting, HTML output
ControllerRequestIntegration, HTTP flow
ComponentComponentRendering, variants
PolicyUnitAuthorization rules
FormUnitValidations, persistence
SystemE2ECritical user paths

→ See tdd-cycle skill for TDD workflow.

Quick Reference

New Feature Checklist

  1. Model - Define data structure
  2. Policy - Add authorization rules
  3. Service - Create for complex logic (if needed)
  4. Query - Add for complex queries (if needed)
  5. Controller - Keep it thin!
  6. Form - Use for multi-model forms (if needed)
  7. Presenter - Format for display
  8. Component - Build reusable UI
  9. Mailer - Add transactional emails (if needed)
  10. Job - Add background processing (if needed)

Refactoring Signals

SignalAction
Model > 300 linesExtract concern or service
Controller action > 15 linesExtract service
View logic in helpersUse presenter
Repeated query patternsExtract query object
Complex partial with logicUse ViewComponent
Form with multiple modelsUse form object
Same code in 3+ placesExtract to shared module

Related Skills

CategorySkills
Data Layerrails-model-generator, rails-query-object, database-migrations
Business Logicrails-service-object, rails-concern, form-object-patterns
Presentationrails-presenter, viewcomponent-patterns
Controllersrails-controller, api-versioning
Authauthentication-flow, authorization-pundit
Backgroundsolid-queue-setup, action-mailer-patterns
Real-timeaction-cable-patterns, hotwire-patterns
Performancecaching-strategies, performance-optimization
I18ni18n-patterns
Testingtdd-cycle

References