| name | ruby |
| description | Ruby language conventions, idioms, and modern features (3.x+) for writing idiomatic Ruby code. Covers error handling patterns, performance optimization, and Ruby-specific idioms. Use when writing or reviewing pure Ruby code, using modern Ruby features (pattern matching, ractors, RBS), optimizing Ruby performance, or establishing Ruby conventions. For Rails-specific guidance use the rails skill. For design patterns use design-patterns-ruby. For testing use rspec. For code style use rubocop. |
Ruby Language Skill
Error Handling Conventions
Weirich raise/fail Convention
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
end
end
Custom Exception Hierarchies
Group domain exceptions under a base error:
module MyApp
class Error < StandardError; end
class PaymentError < Error; end
class InsufficientFundsError < PaymentError; end
end
rescue MyApp::InsufficientFundsError
rescue MyApp::PaymentError
rescue MyApp::Error
Result Objects for Expected Failures
Use 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?
end
Caller-Supplied Fallback
Let 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.
Modern Ruby (3.x+)
Pattern Matching
case response
in { status: 200, body: { users: [{ name: }, *] } }
"First user: #{name}"
in { status: (400..), error: message }
"Error: #{message}"
end
case array
in [*, String => str, *]
"Found string: #{str}"
end
expected = 200
case response
in { status: ^expected, body: }
process(body)
end
Other 3.x+ Features
def square(x) = x * x
def admin? = role == "admin"
[1, 2, 3].map { _1 * 2 }
Point = Data.define(:x, :y)
p = Point.new(x: 1, y: 2)
p.with(x: 3)
params.except(:password, :admin)
users.filter_map { |u| u.email if u.active? }
%w[a b a c b a].tally
See references/modern_ruby.md for ractors, fiber scheduler, RBS types, and advanced pattern matching.
Performance Quick Wins
Frozen String Literals
Efficient Enumeration
totals = items.each_with_object(Hash.new(0)) do |item, hash|
hash[item.category] += item.amount
end
(1..Float::INFINITY).lazy.select(&:odd?).map { _1 ** 2 }.first(10)
Memoization with nil/false Caveat
def users = @users ||= User.all.to_a
def feature_enabled?
return @feature_enabled if defined?(@feature_enabled)
@feature_enabled = expensive_check
end
String Building
result = ""; items.each { |i| result += i.to_s }
result = String.new; items.each { |i| result << i.to_s }
items.map(&:to_s).join
See references/performance.md for YJIT, GC tuning, benchmarking, and profiling tools.
Ruby Idioms to Prefer
Guard Clauses
def process(value)
return unless value
return unless value.valid?
end
Literal Array Constructors
STATES = %w[draft published archived]
FIELDS = %i[name email created_at]
Hash#fetch for Required Keys
config.fetch(:api_key)
config.fetch(:timeout, 30)
config.fetch(:handler) { build_handler }
Safe Navigation
user&.profile&.avatar_url
Predicate and Bang Conventions
? suffix: returns boolean (empty?, valid?, admin?)
! suffix: dangerous version - mutates receiver or raises on failure (save!, sort!)
- Always provide a non-bang alternative when defining bang methods
References
references/modern_ruby.md - Pattern matching, ractors, fiber scheduler, RBS types
references/error_handling.md - Exception hierarchies, result objects, retry patterns
references/performance.md - YJIT, GC tuning, benchmarking, profiling