LLMSkillHub
mirroredCopied from irahardianto/awesome-agv under MIT. irahardianto has not claimed this page, and is not affiliated with LLM SkillHub.

irahardianto / rails-idioms

Ruby on Rails framework patterns: ActiveRecord scopes, N+1 query prevention, model validations, RESTful routing, and RSpec/Minitest testing. Use when building, maintaining, or auditing Rails web applications and APIs. Pair with ruby-idioms.

0
SKILL.md
7.2 KB

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

  1. Scopes for reusable queries:
    `ruby
    class Task < ApplicationRecord
    scope :active, -> { where(status: :active) }
    scope :by_priority, -> { order(priority: :desc) }
    scope :createdafter, ->(date) { where('createdat > ?', date) }

    # ✅ Chainable
    # Task.active.bypriority.createdafter(1.week.ago)
    end
    `

  2. Validations in models, not controllers:
    `ruby
    class Task < ApplicationRecord
    validates :title, presence: true, length: { maximum: 200 }
    validates :priority, inclusion: { in: %w[low medium high] }

    # ✅ Custom validation
    validate :deadlinemustbeinfuture, on: :create

    private

    def deadlinemustbeinfuture
    return unless deadline.present? && deadline < Time.current
    errors.add(:deadline, 'must be in the future')
    end
    end
    `

  3. includes/preload for eager loading — avoid N+1:
    `ruby
    # ❌ 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
    `

  4. Callbacks — use sparingly, prefer service objects:
    `ruby
    # ❌ Complex callback chains — hard to test and debug
    beforesave :normalizetitle, :setdefaults, :notifyassignee

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

  1. RESTful actions — only standard 7 actions per controller. Custom actions = new controller:
    `ruby
    # ❌ 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
    `

  2. Strong parameters — never mass-assign without permit:
    `ruby
    private

    def task_params
    params.require(:task).permit(:title, :priority, :deadline, :description)
    end
    `

  3. Service objects for complex business logic:
    `ruby
    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.

  1. rescue_from for controller-level error handling:
    `ruby
    class ApplicationController < ActionController::Base
    rescuefrom ActiveRecord::RecordNotFound, with: :notfound
    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
    `

  2. Custom domain errors:
    `ruby
    module TaskErrors
    class NotAssignable < StandardError; end
    class DeadlinePassed < StandardError; end
    end
    `

  3. Never rescue Exception — always rescue StandardError or specific subclasses.

Hotwire (7+)

  1. Turbo Frames for partial page updates:
    `erb
    <!-- Wraps content that can be independently loaded/replaced -->
    <%= turboframetag "task_#{task.id}" do %>
    <%= render task %>
    <% end %>
    `

  2. Turbo Streams for real-time updates:
    `ruby
    # In controller — auto-broadcasts updates
    def create
    @task = Task.create!(task_params)
    respond_to do |format|
    format.turbo_stream
    format.html { redirectto taskspath }
    end
    end
    `

  3. 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) — use update!
  • ❌ rescue Exception — catches everything including SystemExit, Interrupt
  • ❌ String interpolation in SQL — SQL injection vector

Testing

For universal testing principles, see .agents/rules/testing-strategy.md. Below: language-specific patterns only.

  1. RSpec (preferred):
    `ruby
    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 havehttpstatus(: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 havehttpstatus(:unprocessable_entity)
    end
    end
    end
    `

  2. FactoryBot for test data:
    `ruby
    FactoryBot.define do
    factory :task do
    title { Faker::Lorem.sentence(word_count: 3) }
    priority { %w[low medium high].sample }
    association :user
    end
    end
    `

  3. Database Cleaner for test isolation.
  4. Shoulda Matchers for model spec shortcuts.

Formatting and Static Analysis

ToolPurposeCommand
RuboCop + rubocop-railsLintingrubocop --autocorrect
BrakemanSecuritybrakeman --no-pager
bundle auditCVE scanningbundle audit check --update
rails_best_practicesCode qualityrails_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