home / skills / phrazzld / claude-config / 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-conventionsReview the files below or copy the command above to add this skill to your agents.
---
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
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.
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 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.