Use when writing, reviewing, or debugging pure Ruby code — idiomatic patterns, modern 3.x+ features (pattern matching, Data.define, endless methods), error handling conventions (raise vs fail, result objects), memoization, and performance idioms. For Rails use rails-guides. For testing use minitest. For code style use sandi-metz-rules.
77
96%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Passed
No known issues
Opinionated Ruby conventions and idioms for writing idiomatic Ruby 3.x+ code. Focuses on patterns agents miss by default — the Weirich raise/fail distinction, safe nil-aware memoization, result objects over exceptions for expected failures, and performance-conscious enumeration.
Use fail for first-time exceptions, raise only for re-raising:
def process(order)
fail ArgumentError, "Order cannot be nil" if order.nil?
begin
gateway.charge(order)
rescue PaymentError => e
logger.error("Payment failed: #{e.message}")
raise # re-raise with raise
end
endGroup domain exceptions under a base error:
module MyApp
class Error < StandardError; end
class PaymentError < Error; end
class InsufficientFundsError < PaymentError; end
end
# Rescue at any granularity:
rescue MyApp::InsufficientFundsError # specific
rescue MyApp::PaymentError # category
rescue MyApp::Error # all app errorsUse result objects instead of exceptions for expected failure paths:
class Result
attr_reader :value, :error
def self.success(value) = new(value: value)
def self.failure(error) = new(error: error)
def initialize(value: nil, error: nil) = (@value, @error = value, error)
def success? = error.nil?
def failure? = !success?
endLet callers define error handling via blocks:
def fetch_user(id, &fallback)
User.find(id)
rescue ActiveRecord::RecordNotFound => e
fallback ? fallback.call(e) : raise
end
user = fetch_user(999) { |_| User.new(name: "Guest") }See references/error_handling.md for full patterns and retry strategies.
case response
in { status: 200, body: { users: [{ name: }, *] } }
"First user: #{name}"
in { status: (400..), error: message }
"Error: #{message}"
end
# Find pattern
case array
in [*, String => str, *]
"Found string: #{str}"
end
# Pin operator
expected = 200
case response
in { status: ^expected, body: }
process(body)
end# Endless methods (3.0+)
def square(x) = x * x
def admin? = role == "admin"
# Numbered block parameters (2.7+)
[1, 2, 3].map { _1 * 2 }
# Data class - immutable value objects (3.2+)
Point = Data.define(:x, :y)
p = Point.new(x: 1, y: 2)
p.with(x: 3) # => Point(x: 3, y: 2)
# Hash#except (3.0+)
params.except(:password, :admin)
# filter_map (2.7+) - select + map in one pass
users.filter_map { |u| u.email if u.active? }
# tally (2.7+)
%w[a b a c b a].tally # => {"a"=>3, "b"=>2, "c"=>1}See references/modern_ruby.md for ractors, fiber scheduler, RBS types, and advanced pattern matching.
# frozen_string_literal: true
# Add to top of every file. Prevents mutation, reduces allocations.
# When you need mutable: String.new("hello") or +"hello"# each_with_object for building results (avoids intermediate arrays)
totals = items.each_with_object(Hash.new(0)) do |item, hash|
hash[item.category] += item.amount
end
# Lazy enumerables for large/infinite sequences
(1..Float::INFINITY).lazy.select(&:odd?).map { _1 ** 2 }.first(10)# Simple (only works if result is truthy)
def users = @users ||= User.all.to_a
# Safe (handles nil/false results)
def feature_enabled?
return @feature_enabled if defined?(@feature_enabled)
@feature_enabled = expensive_check
end# Bad: O(n^2) with +=
result = ""; items.each { |i| result += i.to_s }
# Good: O(n) with <<
result = String.new; items.each { |i| result << i.to_s }
# Best: join
items.map(&:to_s).joinSee references/performance.md for YJIT, GC tuning, benchmarking, and profiling tools.
def process(value)
return unless value
return unless value.valid?
# main logic here
endSTATES = %w[draft published archived] # word array
FIELDS = %i[name email created_at] # symbol arrayconfig.fetch(:api_key) # raises KeyError if missing
config.fetch(:timeout, 30) # default value
config.fetch(:handler) { build_handler } # lazy defaultuser&.profile&.avatar_url # returns nil if any link is nil? suffix: returns boolean (empty?, valid?, admin?)! suffix: dangerous version - mutates receiver or raises on failure (save!, sort!)Lay a class out top-to-bottom so a reader descends one abstraction level at a time — the "stepdown" / newspaper layout. Element order:
extend / include / prependattr_* readers/writers and class macros (has_many, validates, scope)def initializeprotectedprivate helpers — grouped by abstraction altitudeWithin the public block and within the private block, order breadth-first by abstraction altitude, not depth-first by caller. Keep every method at one altitude next to its siblings, then the helpers they call, then the helpers those call. Defining a helper directly beneath its lone caller scatters the high-level steps and buries them under detail.
# Good — breadth-first by altitude: stages cluster, then their helpers
def process; validate; charge; notify; end # altitude 1 (public entry)
private
def validate; end # altitude 2: stages together
def charge; end
def notify; end
def verify_customer; end # altitude 3: helpers together
def submit_payment; end
def write_audit_log; end
# Bad — depth-first by caller: altitudes 2 and 3 interleave, stages scatter
def validate; end
def verify_customer; end
def charge; end
def submit_payment; end
def notify; end
def write_audit_log; endSee references/method_ordering.md for worked examples, the stepdown/SLAP background, and where shared helpers go.
| Mistake | Fix |
|---|---|
raise for new exceptions | Use fail; reserve raise for re-raising (Weirich convention) |
@var ||= compute when result can be nil/false | Use defined?(@var) check instead |
String concatenation with += in loops | Use << or .join — += is O(n²) |
rescue Exception | Rescue StandardError — Exception catches SignalException, NoMemoryError |
Deep &. chains (3+ links) | Extract to a method or use explicit nil check |
Missing # frozen_string_literal: true | Add to top of every file |
| Helper defined right under its single caller (depth-first) | Group methods by abstraction altitude; breadth-first layout (stepdown rule) |
references/modern_ruby.md - Pattern matching, ractors, fiber scheduler, RBS typesreferences/error_handling.md - Exception hierarchies, result objects, retry patternsreferences/performance.md - YJIT, GC tuning, benchmarking, profilingreferences/ood-philosophy.md - OOD principles, naming, SOLID, TRUE heuristic, Law of Demeterreferences/method_ordering.md - File layout order, stepdown rule, breadth-first vs depth-first method ordering712d734
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.