| name | ruby-idioms |
| description | Plain Ruby idioms: value objects, error handling, pattern matching, ractors, YJIT. Triggers: "frozen string literal", "plain Ruby code", "endless method", "value object". Do NOT use for: Rails patterns. |
| user-invocable | false |
| effort | medium |
Ruby Idioms
Iron Laws
- Prefer simple objects over metaprogramming
- Raise exceptions for failures; return values for expected branches
- Keep mutation narrow and obvious
- Wrap third-party APIs behind project-owned adapters
- Avoid global state and hidden thread-local coupling
- Consider
it for simple single-argument blocks when clarity permits
- Prefer pattern matching for complex data destructuring
- Enable YJIT in production (Ruby 3.2+; Rails 7.2+ enables by default on Ruby 3.3+)
Ruby 3.4+ Features
The it Keyword
users.map { |user| user.name }
orders.select { |o| o.completed? }
users.map { it.name }
orders.select { it.completed? }
Use when: Single argument, 1-2 method calls, no nesting
Avoid when: Multiple args, complex logic, nested blocks
Pattern Matching
case response
in { status: 200, body: { data: items } }
items
in { status: 404 }
raise NotFoundError
in { status: code, body: { error: msg } }
raise ApiError.new("#{code}: #{msg}")
end
Use for: Complex data structures, state machines, event handling
Avoid for: Simple cases (use if/when instead)
YJIT (Ruby 3.2+) — Current Recommendation
YJIT is the production-ready JIT compiler for Ruby 3.x:
RubyVM::YJIT.enabled?
Benefits: 15-30% performance improvement for Rails apps
Status:
- Bare Ruby: Not enabled by default; enable via
RUBY_YJIT_ENABLE=1 or RubyVM::YJIT.enable
- Rails 7.2+: Enabled by default when running on Ruby 3.3+
ZJIT (Ruby 4.0+) — Experimental
ZJIT is the next-generation JIT compiler in Ruby 4.0:
puts RubyVM::ZJIT.enabled?
Status: Experimental/not recommended for production; YJIT remains the recommended JIT for Ruby 3.2-4.0
Core Patterns
Value Objects
class Money
include Comparable
attr_reader :cents, :currency
def initialize(cents, currency = "USD")
@cents = cents
@currency = currency
freeze
end
def <=>(other)
return nil unless currency == other.currency
cents <=> other.cents
end
def hash = [cents, currency].hash
alias_method :eql?, :==
def +(other)
raise CurrencyMismatch unless currency == other.currency
Money.new(cents + other.cents, currency)
end
end
Result Objects
class Result
def self.success(value) = new(value: value)
def self.failure(error) = new(error: error)
attr_reader :value, :error
def initialize(value: nil, error: nil)
@value = value
@error = error
freeze
end
def success? = error.nil?
def failure? = !success?
def bind = success? ? yield(value) : self
end
result = process_payment
result.success? ? render(json: result.value) : render_error(result.error)
Service Objects
class CreateOrder
def initialize(
inventory_checker: InventoryChecker.new,
payment_processor: PaymentProcessor.new
)
@inventory_checker = inventory_checker
@payment_processor = payment_processor
end
def call(user:, items:, payment_method:)
check_inventory!(items)
order = Order.create!(user: user, items: items)
process_payment!(order, payment_method)
Result.success(order)
rescue InsufficientInventory => e
Result.failure("Out of stock: #{e.item_name}")
rescue PaymentError => e
order.cancel!
Result.failure("Payment failed: #{e.message}")
end
private
attr_reader :inventory_checker, :payment_processor
def check_inventory!(items) = items.each { inventory_checker.check!(it) }
def process_payment!(order, pm) = payment_processor.charge!(order.total, pm)
end
Adapter Pattern
class StripeAdapter
def initialize(api_key: ENV['STRIPE_API_KEY'])
@client = Stripe::StripeClient.new(api_key)
end
def create_charge(amount:, currency:, source:)
result = client.request do
Stripe::Charge.create(
amount: amount.cents,
currency: currency.downcase,
source: source
)
end
ChargeResult.new(
id: result.id,
amount: Money.new(result.amount, result.currency.upcase),
status: result.status
)
rescue Stripe::CardError => e
raise PaymentError, e.message
end
private
attr_reader :client
end
Error Handling
module MyApp
class Error < StandardError; end
class ValidationError < Error
attr_reader :field
def initialize(message, field: nil)
super(message)
@field = field
end
end
class NotFoundError < Error; end
class ExternalServiceError < Error; end
class TimeoutError < ExternalServiceError; end
end
def with_retry(max_attempts: 3, base_delay: 1)
attempts = 0
begin
attempts += 1
yield
rescue TimeoutError => e
raise if attempts >= max_attempts
sleep(base_delay * (2 ** (attempts - 1)))
retry
end
end
Performance Tips
Anti-patterns
Don't:
- Use class variables (
@@count) — use instance variables on class instead
- Monkey patch core classes — use refinements
- Rescue Exception — rescue StandardError or specific types
- Use eval with user input — use JSON.parse or YAML.safe_load
Plain-Ruby Boundaries
- One object should have one reason to change
- Boundary objects should make side effects obvious
- Integration points should be wrapped behind project-owned APIs
- Avoid framework leakage in plain Ruby business code where practical
References
| Need | Reference |
|---|
| advanced pattern matching | ${CLAUDE_SKILL_DIR}/references/pattern-matching.md |
functional pipelines + .then/it chaining | ${CLAUDE_SKILL_DIR}/references/data-transformations.md |
| lazy enumeration + batching | ${CLAUDE_SKILL_DIR}/references/enumerable-patterns.md |
| RSpec patterns with modern Ruby | ${CLAUDE_SKILL_DIR}/references/testing-patterns.md |
| Ruby 4.0 upgrade path | ${CLAUDE_SKILL_DIR}/references/ruby-4-migration.md |
| fluent interfaces, Result objects, dry-monads, error handling | ${CLAUDE_SKILL_DIR}/references/method-chaining.md |
| Rake task + Thor patterns | ${CLAUDE_SKILL_DIR}/references/rake-tasks.md |
Ruby 3.4+ language features (it, endless methods, Data classes, Ractor, YJIT, ZJIT) | ${CLAUDE_SKILL_DIR}/references/ruby-34-features.md |
| anti-patterns / troubleshooting | ${CLAUDE_SKILL_DIR}/references/anti-patterns.md, ${CLAUDE_SKILL_DIR}/references/troubleshooting.md |
Related — invoke manually if needed
- Fiber concurrency / async-gem work →
/rb:async-patterns (fiber concurrency)