From reactree-rails-dev
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
npx claudepluginhub Kaakati/rails-enterprise-dev --plugin reactree-rails-devThis skill uses the workspace's default tool permissions.
Authoritative guidance on Ruby on Rails conventions and design patterns.
Guides Rails code with rubocop-rails-omakase conventions on Turbo Streams, controllers, concerns, services, modern Ruby style, enums, scopes, error handling, and Minitest testing.
Applies Basecamp's Ruby on Rails best practices for models, concerns, controllers, background jobs, and Turbo/Hotwire when writing, reviewing, or refactoring code.
Provides Ruby on Rails conventions, patterns, and best practices for ActiveRecord models, controllers, migrations, configuration, background jobs, logging, and request specs.
Share bugs, ideas, or general feedback.
Authoritative guidance on Ruby on Rails conventions and design patterns.
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 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
| 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 |
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
| 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) |
| 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 |
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
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)
Before shipping any code:
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 '^$'
Detailed patterns and examples in references/:
controllers.md - RESTful, API, Hotwire, nested resource controllersdesign-patterns.md - Form objects, decorators, presenters, repositories, DTOsbackground-jobs-mailers.md - ActiveJob, Sidekiq, mailers, Action Cablemodern-rails.md - Rails 7.1+/8.0+ features, Ruby 3.3+, concerns, visibility