Ruby project workflow guidelines. Activate when working with Ruby files (.rb), Gemfile, bundler, or Ruby-specific tooling.
location
user
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
Ruby Projects Workflow
Tool Grid
Task
Tool
Command
Lint
StandardRB
bundle exec standardrb
Format
StandardRB
bundle exec standardrb --fix
Type check
Sorbet
bundle exec srb tc
Semantic
Reek
bundle exec reek
Dead code
debride
bundle exec debride .
Complexity
Flog
bundle exec flog lib/
Test
RSpec
bundle exec rspec
Test
Minitest
bundle exec rake test
Ruby Version
Projects SHOULD target Ruby 3.x+
.ruby-version file MUST be present in project root
YJIT SHOULD be enabled in production (--yjit flag or RUBY_YJIT_ENABLE=1)
Ruby 3.x+ Features
Pattern Matching
Pattern matching SHOULD be used for complex conditionals:
# Preferredcase response
in { status:200, body: }
process(body)
in { status:404 }
handle_not_found
in { status:500.. }
handle_server_error
end# Also valid for single patterns
response => { data: { users: } }
Data Class
Data.define SHOULD be used for immutable value objects (Ruby 3.2+):
# Preferred over Struct for immutable dataPoint = Data.define(:x, :y) dodefdistance_from_originMath.sqrt(x**2 + y**2)
endend
point = Point.new(3, 4)
point.x = 5# => FrozenError (immutable by default)
The it Keyword
The it keyword (Ruby 3.4+) SHOULD be used for single-parameter blocks:
# Preferred - pessimistic version constraint
gem "rails", "~> 7.1"# Acceptable - exact version for critical deps
gem "pg", "1.5.4"# Avoid - no version constraint
gem "nokogiri"# May break unexpectedly
Bundle Commands
Command
Use Case
bundle install
Install dependencies
bundle update GEM
Update specific gem
bundle exec CMD
Run command with bundled gems
bundle outdated
Check for updates
bundle audit
Security vulnerability check
All Ruby commands MUST use bundle exec prefix to ensure correct gem versions.
Naming Conventions
Element
Convention
Example
Files
snake_case
user_service.rb
Classes/Modules
PascalCase
UserService
Methods
snake_case
find_by_email
Variables
snake_case
current_user
Constants
SCREAMING_SNAKE
MAX_RETRIES
Predicates
trailing ?
valid?, empty?
Dangerous
trailing !
save!, destroy!
Setters
trailing =
name=
Method Naming
Predicate methods MUST return boolean and end with ?
Bang methods SHOULD indicate danger (mutation, exceptions) with !
Private methods SHOULD NOT use underscore prefix (use private keyword)
Code Style
StandardRB
StandardRB SHOULD be used over RuboCop for simplicity:
# String literals - prefer double quotes
name = "Ruby"# Symbol arrays
%i[foo bar baz]
# String arrays%w[apple banana cherry]# Heredocs for multiline strings
query = <<~SQL
SELECT * FROM users
WHERE active = true
SQL# Safe navigation operator
user&.profile&.avatar_url
# Endless methods (Ruby 3.0+) for simple one-linersdeffull_name = "#{first_name}#{last_name}"
Method Definitions
# Keyword arguments SHOULD be preferred for optional paramsdefcreate_user(name:, email:, role::member)
# ...end# Avoid positional arguments beyond 2-3 parameters# Baddefcreate_user(name, email, role, active, verified)
# Gooddefcreate_user(name:, email:, role:, active:, verified:)
Testing
RSpec (Preferred)
RSpec SHOULD be the default testing framework:
# spec/services/user_service_spec.rbRSpec.describe UserServicedo
describe "#create"do
context "with valid attributes"do
it "creates a new user"do
result = described_class.new.create(name:"Test")
expect(result).to be_success
endend
context "with invalid attributes"do
it "returns failure"do
result = described_class.new.create(name:"")
expect(result).to be_failure
endendendend
Test Structure
describe for classes/methods
context for conditions/scenarios
it for specific behaviors
let for lazy-loaded test data
let! for eager-loaded test data
before for setup (use sparingly)
Minitest (Alternative)
# test/services/user_service_test.rbclassUserServiceTest < Minitest::Testdeftest_create_with_valid_attributes
result = UserService.new.create(name:"Test")
assert result.success?
endend
Blocks, Procs, and Lambdas
Preference Order
Blocks - SHOULD be preferred for most cases
Lambdas - MAY be used when storing/passing callable
Procs - SHOULD be avoided unless specific behavior needed
# Preferred: blocks
users.each { |user| notify(user) }
# Acceptable: lambda for callbacks
validator = ->(value) { value.present? }
# Lambda with arguments
process = ->(x, y) { x + y }
# Avoid: Proc.new unless needed
callback = Proc.new { |x| x * 2 } # Different arity handling
Block Conversion
# Symbol to proc (preferred for simple cases)
names = users.map(&:name)
# Method referencedefprocess(item)
item.upcase
end
items.map(&method(:process))
Metaprogramming
Guidelines
Metaprogramming SHOULD be used sparingly
All metaprogrammed methods MUST be documented
Prefer explicit over implicit magic
define_method over method_missing when possible
# Acceptable: documented DSLclassValidator# Defines validation methods for each attribute# @param attrs [Array<Symbol>] attribute names to validatedefself.validates(*attrs)
attrs.each do |attr|
define_method("validate_#{attr}") do# validation logicendendendend# Document what methods are generated# Generated methods: validate_name, validate_email
validates :name, :email
Avoid
# Avoid: unbounded method_missingdefmethod_missing(name, *args)
# Hard to debug, no autocompleteend# Prefer: explicit delegation or define_method
Error Handling
# Custom errors SHOULD inherit from StandardErrorclassServiceError < StandardError; endclassValidationError < ServiceError; end# Rescue specific exceptionsbegin
risky_operation
rescueValidationError => e
handle_validation(e)
rescueServiceError => e
handle_service_error(e)
rescueStandardError => e
handle_unexpected(e)
end# Result objects SHOULD be used for expected failuresResult = Data.define(:success, :value, :error) dodefsuccess? = success
deffailure? = !success
defself.success(value) = new(true, value, nil)
defself.failure(error) = new(false, nil, error)
end