AgentSkillsCN

Rails Conventions & Patterns

Ruby on Rails的约定、设计模式和惯用代码标准。在以下情况使用:(1) 编写控制器/模型/服务,(2) 选择模式(服务、表单、查询对象),(3) 做出架构决策,(4) 审查代码是否符合约定。触发关键词:rails约定、设计模式、惯用代码、最佳实践、代码组织、命名约定、MVC模式

SKILL.md
--- frontmatter
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

code
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

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

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

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

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

code
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