Back to list
Kaakati

rails-conventions-patterns

by Kaakati

Enterprise-grade Rails development workflow with multi-agent orchestration, beads task tracking, and quality gates. Generic and portable across any Rails project.

2🍴 0📅 Jan 24, 2026

SKILL.md


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:

# 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:

# 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:

# 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:

# 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:

# 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

TypeLocationMax LinesPurpose
Modelsapp/models/200Associations, validations, scopes
Controllersapp/controllers/100REST actions, request handling
Servicesapp/services/150Business logic, orchestration
Formsapp/forms/100Multi-model forms, complex validation
Queriesapp/queries/100Complex reusable queries
Presentersapp/presenters/100View-specific logic
Jobsapp/jobs/50Background processing
Mailersapp/mailers/50Email generation

Naming Conventions

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

PatternExample
Guard clausesreturn unless user.active?
Safe navigationuser&.profile&.avatar
Keyword arguments (2+ params)def call(user:, params:)
Struct/Data for value objectsUser = Data.define(:id, :name)
frozen_string_literal: trueAt top of every file
Explicit returns for clarityreturn Result.failure(errors)

Avoid

Anti-PatternWhy
unless with elseConfusing logic
Nested ternariesHard to read
and/or for control flowUnexpected precedence
Monkey patchingMaintenance nightmare
More than 15 lines/methodSingle responsibility

Service Object Template

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:

# 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

Score

Total Score

60/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

0/10
説明文

100文字以上の説明がある

+10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

Reviews

💬

Reviews coming soon