This skill guides writing of new Ruby code following modern Ruby 3.x syntax, Sandi Metz's 4 Rules for Developers, and idiomatic Ruby best practices. Use when creating new Ruby files, writing Ruby methods, or refactoring Ruby code to ensure adherence to clarity, simplicity, and maintainability standards.
Limited to specific tools
Additional assets for this skill
This skill is limited to using the following tools:
references/ruby-tips.mdreferences/sandi-metz.mdThis skill provides comprehensive guidance for writing clean, idiomatic Ruby code that follows modern Ruby 3.x syntax, Sandi Metz's 4 Rules for Developers, and established best practices. Apply these standards when creating new Ruby files, implementing features, or refactoring existing code to ensure clarity, maintainability, and adherence to Ruby conventions.
Prioritize:
# snake_case for methods and variables
def calculate_total_price
user_name = "David"
end
# CamelCase for classes and modules
module Vendors
class User
end
end
# SCREAMING_SNAKE_CASE for constants
MAX_RETRY_COUNT = 3
DEFAULT_TIMEOUT = 30
Use the shorthand syntax when hash keys match local variable names (requires exact matching between symbol key and variable name):
# Modern Ruby 3.x shorthand
age = 49
name = "David"
user = { name:, age: }
# Instead of the verbose form
user = { name: name, age: age }
Prefer string interpolation over concatenation:
# Good - interpolation
greeting = "Hello, #{user_name}!"
message = "Total: #{quantity * price}"
# Avoid - concatenation
greeting = "Hello, " + user_name + "!"
Use symbol keys with colon syntax:
# Good - modern syntax
options = { timeout: 30, retry: true, method: :post }
# Avoid - hash rocket syntax (only use for non-symbol keys)
options = { :timeout => 30, :retry => true }
These rules enforce strict limits to maintain code quality. Breaking them requires explicit justification.
Limit: Maximum 100 lines of code per class
Check: When a class exceeds this limit, extract secondary concerns to new classes
# When exceeding 100 lines, extract secondary concerns:
class UserProfilePresenter # Presentation logic
class UserProfileValidator # Validation logic
class UserProfileNotifier # Notification logic
Exceptions: Valid Single Responsibility Principle (SRP) justification required
Limit: Maximum 5 lines per method
Check: Each if/else branch counts as lines; extract helper methods when needed
# Good - 5 lines or fewer
def validate_user
return if anonymous?
check_email_format
check_age_restriction
end
# Good - extraction when logic is complex
def process_order
validate_order
calculate_totals
apply_discounts
finalize_payment
end
private
def validate_order
return false unless items.any?
return false unless valid_address?
true
end
def calculate_totals
self.subtotal = items.sum(&:price)
self.tax = subtotal * tax_rate
self.total = subtotal + tax
end
Exceptions: Pair approval required (Rule 0: break rules with agreement)
Limit: Maximum 4 parameters per method
Check: Use parameter objects or hashes when more data is needed
# Good - 4 parameters or fewer
def create_post(user, title, content, category)
Post.create(
user:,
title:,
content:,
category:
)
end
# Better - use parameter object when data is related
def create_post(post_params)
Post.create(
user: post_params.user,
title: post_params.title,
content: post_params.content,
category: post_params.category,
tags: post_params.tags
)
end
# Good - hash options for flexible parameters
def send_notification(user, message, options = {})
priority = options[:priority] || :normal
delivery = options[:delivery] || :email
# ...
end
Exceptions: Rails view helpers like link_to or form_for are exempt
Limit: Controllers should instantiate only one object; other objects come through that object
Pattern: Use facade pattern to aggregate data and hide collaborator access
# ✅ GOOD - single object via facade
class DashboardController < ApplicationController
def show
@dashboard = DashboardFacade.new(current_user)
end
end
# ❌ AVOID - multiple instance variables
class DashboardController < ApplicationController
def show
@user = current_user
@posts = @user.posts.recent
@notifications = @user.notifications.unread
end
end
Implementation Tips:
@_calculation@user.profile.avatar → use facade methodLeverage Ruby's expressive semantic methods instead of manual checks:
# Good - semantic methods
return unless items.any?
return if email.blank?
return if user.present?
# Avoid - manual checks
return unless items.length > 0
return if email.nil? || email.empty?
return if !user.nil?
Common semantic methods:
any? / empty? - for collectionspresent? / blank? - for presence checks (Rails)nil? - for nil checkszero? / positive? / negative? - for numbersUse symbols for identifiers and hash keys for better performance:
# Good - symbols for identifiers
status = :active
options = { method: :post, format: :json }
# Avoid - strings for identifiers
status = "active"
options = { method: "post", format: "json" }
Use Ruby's rich enumerable methods effectively:
# map - transform collections
prices = items.map(&:price)
names = users.map { |u| u.display_name }
# select/reject - filter collections
active_users = users.select(&:active?)
valid_emails = emails.reject(&:blank?)
# reduce - aggregate values
total = prices.reduce(0, :+)
sum = numbers.reduce { |acc, n| acc + n }
# each_with_object - build new structures
grouped = items.each_with_object({}) do |item, hash|
hash[item.category] ||= []
hash[item.category] << item
end
# any?/all?/none? - boolean checks
has_admin = users.any?(&:admin?)
all_valid = records.all?(&:valid?)
Prefer early returns over nested conditionals:
# Good - guard clauses
def process_payment(order)
return unless order.valid?
return if order.paid?
return unless sufficient_funds?
charge_customer(order)
send_confirmation(order)
end
# Avoid - nested conditionals
def process_payment(order)
if order.valid?
if !order.paid?
if sufficient_funds?
charge_customer(order)
send_confirmation(order)
end
end
end
end
Use blocks to make code more expressive and flexible:
# Good - with blocks
def measure_time
start = Time.now
yield
Time.now - start
end
duration = measure_time { expensive_operation }
# Good - block parameters
users.each do |user|
user.send_welcome_email
end
# Good - using & to convert symbol to proc
names = users.map(&:name)
Prefer composing behavior from smaller objects over deep inheritance hierarchies:
# Good - composition
class Report
def initialize(data_source, formatter)
@data_source = data_source
@formatter = formatter
end
def generate
data = @data_source.fetch
@formatter.format(data)
end
end
# Usage
report = Report.new(DatabaseSource.new, PDFFormatter.new)
# Avoid - deep inheritance
class PDFReport < Report < BaseReport
# Rigid hierarchy
end
Use descriptive, intention-revealing names:
# Good - clear intent
def calculate_shipping_cost(weight, distance)
base_rate = 5.00
weight_charge = weight * 0.50
distance_charge = distance * 0.10
base_rate + weight_charge + distance_charge
end
# Avoid - unclear abbreviations
def calc_ship(w, d)
br = 5.00
wc = w * 0.50
dc = d * 0.10
br + wc + dc
end
Naming conventions:
?: valid?, active?, empty?!: save!, update!, destroy!is_admin, has_permission, can_editWrite thread-safe code when necessary:
# Good - thread-safe singleton
class Configuration
@instance_mutex = Mutex.new
def self.instance
return @instance if @instance
@instance_mutex.synchronize do
@instance ||= new
end
end
end
# Good - avoid shared mutable state
class RequestProcessor
def initialize
@mutex = Mutex.new
end
def process(request)
@mutex.synchronize do
# Critical section
end
end
end
Design operations to be safely repeatable:
# Good - idempotent
def activate_user
return if user.active?
user.update(active: true)
send_activation_email unless email_sent?
end
# Avoid - non-idempotent
def activate_user
user.update(active: true)
send_activation_email # Sends every time!
end
Extract classes when:
Extract methods when:
Use parameter objects when:
Create facades when:
When writing Ruby code, ensure:
any?, present?, blank?)map, select, reduce)Sandi Metz's "Rule 0": Break any of the 4 rules only with pair approval or clear justification.
Valid exceptions:
Document all exceptions with clear reasoning in code comments.
For additional Sandi Metz guidance (code smells, refactoring, testing principles):
references/sandi-metz.mdreferences/ruby-tips.md - Type conversion, hash patterns, proc composition, refinements