rails-idioms
SkillAI & modelsRails rewards convention over configuration, Active Record, and RESTful design. Idiomatic Rails = conventional, tested, Hotwire-aware.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the rails-idioms skill
About this capability
Comprehensive sets of standards and practices designed to elevate the capabilities of AI coding agents.
What this skill tells your AI
The instructions your AI receives, as published by irahardianto/awesome-agv in .agents/skills/rails-idioms/SKILL.md and read by ahel’s review.
Rails Idioms and Patterns
Rails rewards convention over configuration, Active Record, and RESTful design. Idiomatic Rails = conventional, tested, Hotwire-aware.
Scope: Rails-specific patterns. For Ruby:
@.agents/skills/ruby-idioms/SKILL.md.
Active Record
-
Scopes for reusable queries:
class Task < ApplicationRecord scope :active, -> { where(status: :active) } scope :by_priority, -> { order(priority: :desc) } scope :created_after, ->(date) { where('created_at > ?', date) } # ✅ Chainable # Task.active.by_priority.created_after(1.week.ago) end -
Validations in models, not controllers:
class Task < ApplicationRecord validates :title, presence: true, length: { maximum: 200 } validates :priority, inclusion: { in: %w[low medium high] } # ✅ Custom validation validate :deadline_must_be_in_future, on: :create private def deadline_must_be_in_future return unless deadline.present? && deadline < Time.current errors.add(:deadline, 'must be in the future') end end -
includes/preloadfor eager loading — avoid N+1:# ❌ N+1 — fires a query per task to load user Task.all.each { |t| puts t.user.name } # ✅ Eager load — 2 queries total Task.includes(:user).each { |t| puts t.user.name } # ✅ Use Bullet gem to detect N+1 in development -
Callbacks — use sparingly, prefer service objects:
# ❌ Complex callback chains — hard to test and debug before_save :normalize_title, :set_defaults, :notify_assignee # ✅ Service object — explicit, testable class CreateTask def call(params) task = Task.new(normalize(params)) task.save! NotificationService.notify(task.assignee, task) task end end
Controllers
-
RESTful actions — only standard 7 actions per controller. Custom actions = new controller:
# ❌ Custom action crammed into TasksController def complete; end # ✅ Dedicated controller class TaskCompletionsController < ApplicationController def create task = Task.find(params[:task_id]) task.complete! redirect_to task end end -
Strong parameters — never mass-assign without permit:
private def task_params params.require(:task).permit(:title, :priority, :deadline, :description) end -
Service objects for complex business logic:
class TasksController < ApplicationController def create result = CreateTask.new.call(task_params) if result.success? redirect_to result.task, notice: 'Task created' else @task = result.task render :new, status: :unprocessable_entity end end end
Error Handling
For universal error handling principles, see
.agents/rules/error-handling-principles.md.
-
rescue_fromfor controller-level error handling:class ApplicationController < ActionController::Base rescue_from ActiveRecord::RecordNotFound, with: :not_found rescue_from ActiveRecord::RecordInvalid, with: :unprocessable private def not_found(exception) render json: { error: exception.message }, status: :not_found end def unprocessable(exception) render json: { errors: exception.record.errors.full_messages }, status: :unprocessable_entity end end -
Custom domain errors:
module TaskErrors class NotAssignable < StandardError; end class DeadlinePassed < StandardError; end end -
Never rescue
Exception— always rescueStandardErroror specific subclasses.
Hotwire (7+)
-
Turbo Frames for partial page updates:
<!-- Wraps content that can be independently loaded/replaced --> <%= turbo_frame_tag "task_#{task.id}" do %> <%= render task %> <% end %> -
Turbo Streams for real-time updates:
# In controller — auto-broadcasts updates def create @task = Task.create!(task_params) respond_to do |format| format.turbo_stream format.html { redirect_to tasks_path } end end -
Stimulus for JavaScript sprinkles — minimal JS.
Security
For universal security principles, see
.agents/rules/security-principles.md.
- CSRF protection — enabled by default, never disable
- Content Security Policy — configure in
config/initializers/content_security_policy.rb - Parameterized queries — Active Record handles this, but never use string interpolation in
where:# ❌ SQL injection risk Task.where("title LIKE '%#{params[:q]}%'") # ✅ Parameterized Task.where("title LIKE ?", "%#{params[:q]}%")
Anti-Patterns
- ❌ Fat models — extract to service objects / form objects / query objects
- ❌ Business logic in controllers — controllers are thin routing layers
- ❌ Callbacks for complex side effects — use service objects
- ❌
default_scope— implicit, surprising, hard to override - ❌
update_attribute(skips validation) — useupdate! - ❌
rescue Exception— catches everything includingSystemExit,Interrupt - ❌ String interpolation in SQL — SQL injection vector
Testing
For universal testing principles, see
.agents/rules/testing-strategy.md. Below: language-specific patterns only.
-
RSpec (preferred):
RSpec.describe TasksController, type: :request do describe 'POST /tasks' do it 'creates a task with valid params' do post tasks_path, params: { task: { title: 'Test', priority: 'high' } } expect(response).to have_http_status(:created) expect(Task.last.title).to eq('Test') end it 'returns errors with invalid params' do post tasks_path, params: { task: { title: '' } } expect(response).to have_http_status(:unprocessable_entity) end end end -
FactoryBot for test data:
FactoryBot.define do factory :task do title { Faker::Lorem.sentence(word_count: 3) } priority { %w[low medium high].sample } association :user end end -
Database Cleaner for test isolation.
-
Shoulda Matchers for model spec shortcuts.
Formatting and Static Analysis
| Tool | Purpose | Command |
|---|---|---|
| RuboCop + rubocop-rails | Linting | rubocop --autocorrect |
| Brakeman | Security | brakeman --no-pager |
bundle audit | CVE scanning | bundle audit check --update |
rails_best_practices | Code quality | rails_best_practices . |
Related
- Ruby Idioms @.agents/skills/ruby-idioms/SKILL.md
- Database Design Principles @.agents/rules/database-design-principles.md
- Security Principles @.agents/rules/security-principles.md
- API Design Principles @.agents/rules/api-design-principles.md
Signals
- GitHub stars
- 156
- Forks
- 53
- Last commit
- Aug 2026
Advanced
- Catalog kind
- skill
- Gateway key
rails-idioms- Source
- github.com/irahardianto/awesome-agv