| name | ruby-workflow |
| description | 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:
case response
in { status: 200, body: }
process(body)
in { status: 404 }
handle_not_found
in { status: 500.. }
handle_server_error
end
response => { data: { users: } }
Data Class
Data.define SHOULD be used for immutable value objects (Ruby 3.2+):
Point = Data.define(:x, :y) do
def distance_from_origin
Math.sqrt(x**2 + y**2)
end
end
point = Point.new(3, 4)
point.x = 5
The it Keyword
The it keyword (Ruby 3.4+) SHOULD be used for single-parameter blocks:
users.map { it.name.upcase }
users.map { _1.name.upcase }
users.map { |user| user.name.upcase }
Bundler & Dependencies
Gemfile Rules
Gemfile MUST be present for all projects
Gemfile.lock MUST be committed to version control
- Gems SHOULD specify version constraints:
gem "rails", "~> 7.1"
gem "pg", "1.5.4"
gem "nokogiri"
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:
- Zero configuration required
- Consistent style across projects
- Auto-fix available
ruby_version: 3.3
ignore:
- "db/schema.rb"
- "vendor/**/*"
Style Guidelines
name = "Ruby"
%i[foo bar baz]
%w[apple banana cherry]
query = <<~SQL
SELECT * FROM users
WHERE active = true
SQL
user&.profile&.avatar_url
def full_name = "#{first_name} #{last_name}"
Method Definitions
def create_user(name:, email:, role: :member)
end
def create_user(name, email, role, active, verified)
def create_user(name:, email:, role:, active:, verified:)
Testing
RSpec (Preferred)
RSpec SHOULD be the default testing framework:
RSpec.describe UserService do
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
end
end
context "with invalid attributes" do
it "returns failure" do
result = described_class.new.create(name: "")
expect(result).to be_failure
end
end
end
end
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)
class UserServiceTest < Minitest::Test
def test_create_with_valid_attributes
result = UserService.new.create(name: "Test")
assert result.success?
end
end
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
users.each { |user| notify(user) }
validator = ->(value) { value.present? }
process = ->(x, y) { x + y }
callback = Proc.new { |x| x * 2 }
Block Conversion
names = users.map(&:name)
def process(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
class Validator
def self.validates(*attrs)
attrs.each do |attr|
define_method("validate_#{attr}") do
end
end
end
end
validates :name, :email
Avoid
def method_missing(name, *args)
end
Error Handling
class ServiceError < StandardError; end
class ValidationError < ServiceError; end
begin
risky_operation
rescue ValidationError => e
handle_validation(e)
rescue ServiceError => e
handle_service_error(e)
rescue StandardError => e
handle_unexpected(e)
end
Result = Data.define(:success, :value, :error) do
def success? = success
def failure? = !success
def self.success(value) = new(true, value, nil)
def self.failure(error) = new(false, nil, error)
end
File Structure
project/
lib/ # Application code
spec/ # RSpec tests
test/ # Minitest tests
bin/ # Executables
Gemfile # Dependencies
Gemfile.lock # Locked versions
.ruby-version # Ruby version
.standard.yml # StandardRB config (optional)
Out of Scope
- Rails specifics -> see
rails-workflow
- Hanami specifics -> see
hanami-workflow
- Gem publishing -> see
gem-publishing