programming-ruby
SkillIdiomatic Ruby and Rails architecture: objects, services, ActiveRecord persistence, transactions, and Active Job boundaries. Auto-activates in Ruby projects.
Instructions
Overview
Write idiomatic Ruby with explicit data contracts and predictable persistence. Research baseline: 2026-09-05, Ruby 4.0 stable; Rails guidance below targets supported Rails 8 APIs. Check .ruby-version, the Gemfile/lockfile, Ruby implementation, and Rails version before using newer features. Preserve existing application conventions and dependencies; a small change does not need a new service layer or gem extraction.
Mental model
Ruby's flexibility makes names and failure contracts important. Keep domain behavior near the object that owns it; extract orchestration only when it has independent responsibilities. Rails conventions reduce boilerplate, but callbacks, transactions, and asynchronous jobs have distinct timing and failure semantics.
Current language features
- Ruby 3.2+
Data.definedescribes fixed value records; members cannot be reassigned, but referenced strings/arrays/hashes are not deeply frozen.Structis mutable unless frozen. Copy/freeze nested values only when the contract needs it. - Ruby 3.4's implicit block parameter
itsuits a short, obvious block; use named parameters for nested or nontrivial logic. New syntax should improve readability rather than demonstrate novelty. - Ruby 4 makes
Seta core class and addsArray#rfindfor reverse search. Keep older-versionrequire "set"compatibility when the repository supports older Rubies. - Ruby 4's Ruby Box and Ractor remain experimental; ZJIT is also experimental. Do not introduce them or switch JITs as routine cleanup.
- Keep frozen-string policy explicit and consistent with the project.
# frozen_string_literal: truefreezes literals, not all strings; use+""or an owned duplicate when mutation is intended.
Values and control flow
- Use keyword arguments where positional arguments hide intent. Forward positional arguments, keywords, and blocks accurately; Ruby 3 distinguishes a positional hash from keyword arguments.
- Prefer
fetchfor required hash/configuration keys; use[],dig, and&.only where absence is valid. A chain of nil-tolerant operations must not conceal a missing invariant. - Ruby treats only
nilandfalseas falsey.||=does not cache a valid false/nil result; use an explicit initialization check when those results matter. - Use explicit parsing such as
Integer(text, 10)for validated integer input;to_ican silently accept malformed data. Keep absence distinct from invalid syntax. - Use
mapfor transformation,eachfor effects, andfilter_maponly when discarding both nil and false is intended. Keep long chains readable with named intermediate values. - A
returninside a normal block exits its enclosing method; an escaped proc can raiseLocalJumpErrorwhen that method has returned. A lambda'sreturnexits the lambda. Usenextfor the current block invocation. - Predicate methods conventionally end in
?;!identifies a more dangerous counterpart, not a universal promise to mutate or raise. Follow the specific method contract.
Errors and resources
- Rescue the specific error where recovery or translation is meaningful. Ordinary custom errors derive from
StandardError; do not broadly rescueException, which includes shutdown and interrupt conditions. - Preserve causes when wrapping failures. Avoid rescue modifiers, empty rescues, and fallback empty collections that convert broken I/O or invalid data into success.
- Use block forms of resource APIs, or
ensurewhen cleanup must span custom control flow. Do not return fromensure, which can replace results or suppress errors. - Bound retries to identified transient failures and account for side effects already performed. Prefer an explicit not-found result only where the caller expects one.
Rails persistence and jobs
- Keep entity behavior on the model when cohesive; extract a query, form, or orchestration object only when it clarifies a real boundary. Controllers should adapt HTTP and authorize operations, not dictate arbitrary class counts.
- Rails 8's
params.expectcan require and permit an expected parameter shape together. Strong Parameters filter assignment; they do not authorize access or validate all domain constraints. - Back uniqueness and cross-record invariants with database constraints and appropriate locking. A model validation alone cannot prevent races. Choose eager loading based on accessed relationships.
- Transactions cover database work on their connection; they do not roll back HTTP calls or restore in-memory objects. Use raising persistence methods when failure must abort; ordinary
savereturning false does not trigger rollback automatically. ActiveRecord::Rollbackrolls back but is swallowed by the transaction boundary. Do not rescue database statement errors inside a transaction and continue using a potentially aborted transaction.- Schedule external effects after commit when appropriate; use durable coordination when delivery must survive a process crash. Make jobs safe under duplicate execution and bounded retries.
- Active Job supports records via GlobalID, which reloads them later. Choose IDs, records, or explicit snapshots according to desired semantics and handle deletion before execution. Verify enqueue-after-commit behavior for the actual adapter/database setup.
Example
Preserve a legitimate false value and reject a malformed required value:
# frozen_string_literal: true
def read_settings(input)
enabled = input.fetch("enabled")
unless enabled == true || enabled == false
raise ArgumentError, "enabled must be boolean"
end
attempts = Integer(input.fetch("attempts"), 10)
raise ArgumentError, "attempts must be positive" unless attempts.positive?
{ enabled: enabled, attempts: attempts }
endThis boundary expects string input for attempts, such as form/config text. Missing keys and malformed values remain explicit failures.
Checklist
- Check Ruby/Rails version gates and retain the project's formatter and testing conventions.
- Distinguish nil/false/missing from invalid data; check mutation and block-return semantics.
- Verify transaction failure, database constraints, job duplication, and commit timing.
- Use installed project commands through
bundle execor binstubs when authorized; test observable behavior with the existing Minitest/RSpec setup. Do not replace fixtures or test libraries by preference.