home / skills / kaakati / rails-enterprise-dev / rails-conventions

This skill helps you apply Rails conventions and design patterns to controllers, models, and services for clean, scalable code.

npx playbooks add skill kaakati/rails-enterprise-dev --skill rails-conventions

Review the files below or copy the command above to add this skill to your agents.

Files (5)
SKILL.md
6.0 KB
---
name: "Rails Conventions & Patterns"
description: "Ruby on Rails conventions, design patterns, and idiomatic code standards. Use when: (1) Writing controllers/models/services, (2) Choosing patterns (Service, Form, Query objects), (3) Making architectural decisions, (4) Reviewing code for conventions. Trigger keywords: rails conventions, design patterns, idiomatic code, best practices, code organization, naming conventions, MVC patterns"
version: 1.1.0
---

# Rails Conventions & Patterns

Authoritative guidance on Ruby on Rails conventions and design patterns.

## Pattern Decision Tree

```
What are you building?
│
├─ Business logic spanning multiple models?
│   └─ Service Object (app/services/)
│
├─ Form spanning multiple models or complex validation?
│   └─ Form Object (app/forms/)
│
├─ Complex queries with multiple conditions?
│   └─ Query Object (app/queries/)
│
├─ View logic becoming complex?
│   └─ Decorator/Presenter (app/decorators/, app/presenters/)
│
├─ Truly shared behavior across 3+ unrelated models?
│   └─ Concern (app/models/concerns/)
│
└─ Simple single-model operation?
    └─ Keep in model/controller (no extra pattern)
```

---

## NEVER Do This

**NEVER** use concerns for 1-2 models:
```ruby
# WRONG - Concern for single model
module UserHelpers
  def full_name
    "#{first_name} #{last_name}"
  end
end

# RIGHT - Keep in model if only used there
class User < ApplicationRecord
  def full_name
    "#{first_name} #{last_name}"
  end
end
```

**NEVER** put business logic in controllers:
```ruby
# WRONG - Fat controller
def create
  @order = Order.new(order_params)
  @order.calculate_tax
  @order.apply_discount(params[:coupon])
  @order.reserve_inventory
  PaymentGateway.charge(@order.total)
  @order.save
end

# RIGHT - Delegate to service
def create
  @order = CreateOrderService.call(current_user, order_params)
  redirect_to @order
end
```

**NEVER** use `unless` with `else`:
```ruby
# WRONG
unless user.admin?
  deny_access
else
  grant_access
end

# RIGHT
if user.admin?
  grant_access
else
  deny_access
end
```

**NEVER** exceed 4 parameters without keyword arguments:
```ruby
# WRONG
def create_user(email, password, name, role, department, manager_id)

# RIGHT - Use keyword arguments
def create_user(email:, password:, name:, role:, department:, manager_id:)

# RIGHT - Use parameter object for many params
def create_user(user_params)
```

**NEVER** monkey patch in application code:
```ruby
# WRONG - Monkey patching String
class String
  def to_slug
    downcase.gsub(' ', '-')
  end
end

# RIGHT - Create utility method
module StringUtils
  def self.slugify(text)
    text.downcase.gsub(' ', '-')
  end
end
```

---

## File Organization Standards

| Type | Location | Max Lines | Purpose |
|------|----------|-----------|---------|
| Models | app/models/ | 200 | Associations, validations, scopes |
| Controllers | app/controllers/ | 100 | REST actions, request handling |
| Services | app/services/ | 150 | Business logic, orchestration |
| Forms | app/forms/ | 100 | Multi-model forms, complex validation |
| Queries | app/queries/ | 100 | Complex reusable queries |
| Presenters | app/presenters/ | 100 | View-specific logic |
| Jobs | app/jobs/ | 50 | Background processing |
| Mailers | app/mailers/ | 50 | Email generation |

---

## Naming Conventions

```yaml
classes: "PascalCase"         # UserProfile, OrderService
methods: "snake_case"         # create_order, find_by_email
predicates: "end with ?"      # active?, valid?, admin?
dangerous_methods: "end with !" # save!, destroy!, update!
constants: "SCREAMING_SNAKE"  # MAX_RETRIES, DEFAULT_LIMIT
private_methods: "descriptive" # NOT underscore prefix
```

---

## Ruby Idioms

### Prefer

| Pattern | Example |
|---------|---------|
| Guard clauses | `return unless user.active?` |
| Safe navigation | `user&.profile&.avatar` |
| Keyword arguments (2+ params) | `def call(user:, params:)` |
| `Struct`/`Data` for value objects | `User = Data.define(:id, :name)` |
| `frozen_string_literal: true` | At top of every file |
| Explicit returns for clarity | `return Result.failure(errors)` |

### Avoid

| Anti-Pattern | Why |
|--------------|-----|
| `unless` with `else` | Confusing logic |
| Nested ternaries | Hard to read |
| `and`/`or` for control flow | Unexpected precedence |
| Monkey patching | Maintenance nightmare |
| More than 15 lines/method | Single responsibility |

---

## Service Object Template

```ruby
class CreateOrderService
  def initialize(user:, params:)
    @user = user
    @params = params
  end

  def call
    validate_params
    order = build_order
    process_payment
    send_confirmation
    Result.success(order)
  rescue PaymentError => e
    Result.failure(e.message)
  end

  private

  attr_reader :user, :params

  def validate_params
    # ...
  end

  def build_order
    # ...
  end
end
```

---

## Implementation Order

Always implement bottom-up (dependencies first):

```
1. Database migrations
2. Models (foundation)
3. Services (business logic)
4. Components (presentation wrappers)
5. Controllers (orchestration)
6. Views (final layer)
7. Tests (verify everything)
```

---

## Code Quality Checklist

Before shipping any code:

- [ ] Methods ≤ 15 lines
- [ ] Max 4 parameters (use keyword args)
- [ ] No business logic in controllers
- [ ] No view logic in models
- [ ] Concerns used by 3+ models
- [ ] Guard clauses used
- [ ] Tests exist for new code

---

## Quick Reference

**Before Writing Any Code:**
```bash
# Check existing patterns
ls app/services/
ls app/forms/ 2>/dev/null

# Check naming conventions
head -30 $(find app/services -name '*.rb' | head -1)

# Check dependencies
grep -v '^#' Gemfile | grep -v '^$'
```

---

## References

Detailed patterns and examples in `references/`:
- `controllers.md` - RESTful, API, Hotwire, nested resource controllers
- `design-patterns.md` - Form objects, decorators, presenters, repositories, DTOs
- `background-jobs-mailers.md` - ActiveJob, Sidekiq, mailers, Action Cable
- `modern-rails.md` - Rails 7.1+/8.0+ features, Ruby 3.3+, concerns, visibility

Overview

This skill codifies Ruby on Rails conventions, idiomatic patterns, and file-organization standards for enterprise projects. It guides pattern selection (service, form, query, presenter, concern), naming, file size limits, and implementation order to keep code maintainable and testable. Use it to make consistent architectural decisions and to enforce readable, idiomatic Rails code.

How this skill works

The skill inspects the nature of a task and maps it to a recommended pattern using a simple decision tree: multi-model business logic → Service, multi-model forms → Form, complex queries → Query, view-specific logic → Presenter/Decorator, widely shared behavior → Concern. It enforces practical rules (no business logic in controllers, avoid monkey patching, prefer guard clauses and keyword args) and supplies file locations, max line counts, and a service object template. It also provides a pre-commit checklist and implementation ordering to reduce rework.

When to use it

  • Creating or refactoring business logic that spans models
  • Designing forms that touch multiple models or complex validation
  • Writing reusable complex queries with conditions and scopes
  • Choosing naming, file layout, and size limits for maintainability
  • Reviewing code for Rails idioms and anti-patterns

Best practices

  • Map work to patterns with the decision tree before coding
  • Keep controllers slim; delegate orchestration to services
  • Use keyword arguments for 2+ params and parameter objects for many params
  • Limit models to ~200 lines, controllers to ~100, services to ~150
  • Use guard clauses, safe navigation, and freeze string literals at file top

Example use cases

  • Implement CreateOrderService for order orchestration, payment, and confirmation
  • Introduce an OrderForm when checkout touches Order, Payment, and Shipping models
  • Extract a SearchQuery object for reusable complex filtering logic
  • Create a presenter for product list rendering logic used only by views
  • Refactor repeated behavior used by 3+ models into a Concern

FAQ

When should I add a Concern vs keep logic in a model?

Only extract behavior to a Concern when it is shared by three or more unrelated models; otherwise keep it in the model to avoid unnecessary indirection.

How many parameters are acceptable for a method?

Prefer keyword arguments for 2+ parameters and use a parameter object (like a hash or form object) when you exceed four explicit parameters.