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.
100
100%
Does it follow best practices?
Impact
Pending
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!)| 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 |
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 Demetercb03f92
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.