home / skills / phrazzld / claude-config / ruby-conventions

ruby-conventions skill

/skills/ruby-conventions

This skill enforces Ruby on Rails conventions for service objects, strong params, and testing to improve maintainability and performance.

npx playbooks add skill phrazzld/claude-config --skill ruby-conventions

Review the files below or copy the command above to add this skill to your agents.

Files (2)
SKILL.md
3.3 KB
---
name: ruby-conventions
description: |
  Ruby and Rails best practices for services, testing, and architecture. Use when:
  - Writing or reviewing Ruby/Rails code
  - Designing service objects or organizing business logic
  - Setting up RSpec tests with FactoryBot
  - Building APIs with JSON serialization
  - Optimizing Rails performance (N+1, caching)
  Keywords: Ruby, Rails, RSpec, FactoryBot, service object, strong params,
  N+1 query, Sidekiq, RuboCop, Thor CLI
effort: high
---

# Ruby Conventions

Service objects, strong params, behavior-focused tests.

## Architecture

**Service objects for business logic:**
```ruby
# app/services/user_registration_service.rb
class UserRegistrationService
  def initialize(user_params:, mailer: UserMailer)
    @user_params = user_params
    @mailer = mailer
  end

  def call
    user = User.create!(@user_params)
    @mailer.welcome(user).deliver_later
    user
  end
end
```

**Thin controllers:**
```ruby
def create
  user = UserRegistrationService.new(user_params: user_params).call
  render json: UserSerializer.new(user), status: :created
rescue ActiveRecord::RecordInvalid => e
  render json: { errors: e.record.errors }, status: :unprocessable_entity
end
```

## Strong Parameters

**Always use strong params. Never mass-assign directly:**
```ruby
# Good
def user_params
  params.require(:user).permit(:email, :name, :password)
end

# Never
User.create(params[:user])  # SQL injection risk
```

## Query Safety

**Always parameterize queries:**
```ruby
# Good
User.where(email: email)
User.where("email = ?", email)

# Never
User.where("email = '#{email}'")  # SQL injection
```

**Prevent N+1 queries:**
```ruby
# Good
User.includes(:posts).each { |u| u.posts.count }

# Bad (N+1)
User.all.each { |u| u.posts.count }
```

## Testing with RSpec

```ruby
RSpec.describe UserRegistrationService do
  describe "#call" do
    it "creates active user with welcome email" do
      mailer = instance_double(UserMailer)
      allow(mailer).to receive(:welcome).and_return(double(deliver_later: true))

      service = described_class.new(
        user_params: { email: "[email protected]", name: "Test" },
        mailer: mailer
      )

      user = service.call

      expect(user).to be_persisted
      expect(user.email).to eq("[email protected]")
      expect(mailer).to have_received(:welcome).with(user)
    end
  end
end
```

**Use FactoryBot, not fixtures:**
```ruby
FactoryBot.define do
  factory :user do
    email { Faker::Internet.email }
    name { Faker::Name.name }
  end
end

let(:user) { create(:user) }
```

## API Design

```ruby
# Consistent serialization
class UserSerializer
  include JSONAPI::Serializer
  attributes :id, :email, :name, :created_at
end

# Versioned routes
namespace :api do
  namespace :v1 do
    resources :users
  end
end
```

## Language Patterns

```ruby
# Keyword arguments for 2+ params
def send_email(to:, subject:, body:)
  ...
end

# Safe navigation
user&.profile&.avatar_url

# Frozen strings (file header)
# frozen_string_literal: true
```

## Anti-Patterns

- Fat models (business logic in ActiveRecord)
- Business logic in controllers
- Fixtures instead of factories
- Testing implementation (mocking internals)
- String interpolation in SQL
- N+1 queries without includes
- Synchronous email in request cycle

## References

- [rails-performance.md](references/rails-performance.md) - Caching, background jobs, profiling

Overview

This skill captures pragmatic Ruby and Rails conventions for service design, testing, API serialization, and performance. It focuses on splitting business logic into service objects, keeping controllers thin, safe parameter handling, and reliable tests with RSpec and FactoryBot. The guidance also highlights common anti-patterns and simple performance safeguards like preventing N+1 queries and using background jobs for slow work.

How this skill works

I inspect common areas of a Rails app and recommend patterns: extract business logic into service objects, use strong params in controllers, and serialize consistently for APIs. I validate query safety (parameterized queries, includes for associations) and outline test patterns with RSpec and FactoryBot including how to stub external effects like mailers. I also recommend language-level idioms (keyword args, safe navigation, frozen strings) and operational practices (background jobs for email).

When to use it

  • When writing or reviewing Ruby/Rails controllers and models to move logic into services.
  • When designing service objects that need clear inputs and testable side effects.
  • When setting up RSpec suites with FactoryBot and avoiding brittle tests.
  • When building JSON APIs that require consistent serialization and versioned routes.
  • When diagnosing performance issues like N+1 queries or synchronous external calls.

Best practices

  • Extract business logic into focused service objects with explicit keyword args and dependency injection.
  • Keep controllers thin: parse params, call services, render serializers, and handle known exceptions.
  • Always use strong params and parameterized queries to avoid mass-assignment and SQL injection.
  • Prevent N+1 queries with includes and eager_loading; measure queries in profiling.
  • Use background jobs (Sidekiq) for slow tasks like sending email; stub mailers in tests.
  • Prefer FactoryBot factories over fixtures and test behavior, not implementation details.

Example use cases

  • Implementing a UserRegistrationService that creates users and enqueues a welcome email.
  • Refactoring a fat controller into endpoint + service + serializer with proper error handling.
  • Writing an RSpec spec that uses FactoryBot and an instance_double to assert mailer delivery.
  • Designing a versioned JSON API with JSONAPI::Serializer for consistent responses.
  • Optimizing index endpoints by replacing iterative queries with includes to remove N+1s.

FAQ

When should I create a service object instead of putting logic in a model?

Create a service when logic spans multiple models, involves external services, or doesn't fit a single model's responsibility. Keep models focused on persistence and simple domain behavior.

How do I test mail delivery without sending real emails?

Inject a mailer double into the service and stub deliver_later. In integration specs, use the test adapter or assert enqueued jobs rather than performing delivery.