ruby-guide

Applies to: Ruby 3.2+, Gems, APIs, CLIs, Web Applications

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "ruby-guide" with this command: npx skills add ar4mirez/samuel/ar4mirez-samuel-ruby-guide

Ruby Guide

Applies to: Ruby 3.2+, Gems, APIs, CLIs, Web Applications

Core Principles

  • Least Surprise: Code should behave as readers expect; prefer clarity over cleverness

  • Everything is an Object: Leverage Ruby's object model; primitives are objects with methods

  • Convention Over Configuration: Follow established naming and structure conventions

  • Duck Typing with Confidence: Rely on behavior, not class checks; validate at boundaries

  • Blocks Everywhere: Use blocks for resource management, iteration, and DSLs

Guardrails

Version & Dependencies

  • Use Ruby 3.2+ with # frozen_string_literal: true in every .rb file

  • Manage dependencies with Bundler (Gemfile

  • Gemfile.lock )
  • Pin gem versions with pessimistic operator: gem "rails", "~> 7.1"

  • Run bundle audit before merging to check for vulnerable gems

  • Commit Gemfile.lock for applications; omit for gems

  • Specify required_ruby_version in .gemspec files

Code Style

  • Run rubocop before every commit (no exceptions)

  • snake_case for methods/variables/files, PascalCase for classes/modules, SCREAMING_SNAKE_CASE for constants

  • Predicate methods end with ? , dangerous methods end with !

  • Two-space indentation, no tabs

  • Prefer guard clauses over nested conditionals

frozen_string_literal: true

Bad: deeply nested

def process(user) if user if user.active? do_something(user) if user.verified? end end end

Good: guard clauses

def process(user) return unless user return unless user.active? return unless user.verified?

do_something(user) end

Blocks & Procs

  • Use {} for single-line blocks, do...end for multi-line

  • Prefer block_given?

  • yield over explicit &block parameter
  • Use lambdas for strict arity checking; procs for flexible arity

Block for resource management

File.open("data.txt", "r") do |file| file.each_line { |line| process(line) } end

Lambda vs Proc

validator = ->(x) { x.positive? } # strict arity, returns from lambda transformer = proc { |x| x.to_s } # flexible arity, returns from enclosing

Point-free style

names = users.map(&:name)

Error Handling

  • Rescue specific exceptions, never bare rescue

  • Define custom errors inheriting from StandardError

  • Use ensure for cleanup (not rescue for flow control)

  • Provide #message with actionable information in custom errors

class PaymentError < StandardError; end class InsufficientFundsError < PaymentError; end

def charge(account, amount) raise InsufficientFundsError, "account #{account.id} needs #{amount}" if account.balance < amount

account.debit(amount) rescue Stripe::CardError => e Rails.logger.error("Payment failed for account=#{account.id}: #{e.message}") raise PaymentError, "card declined: #{e.message}" end

Metaprogramming

  • Use define_method sparingly; prefer explicit method definitions

  • Always pair method_missing with respond_to_missing?

  • Prefer Module#prepend over alias_method chains

  • Avoid eval with string arguments (use block form of class_eval )

  • Never use metaprogramming in hot paths

class DynamicFinder def method_missing(method_name, *args) if method_name.to_s.start_with?("find_by_") attribute = method_name.to_s.delete_prefix("find_by_") find_by_attribute(attribute, args.first) else super end end

def respond_to_missing?(method_name, include_private = false) method_name.to_s.start_with?("find_by_") || super end end

Project Structure

Gem Layout

mygem/ ├── lib/ │ ├── mygem.rb # Entry point, require dependencies │ └── mygem/ │ ├── version.rb # VERSION constant │ ├── client.rb # Core logic │ └── errors.rb # Custom error classes ├── spec/ │ ├── spec_helper.rb │ └── mygem/ │ └── client_spec.rb ├── Gemfile ├── mygem.gemspec └── Rakefile

Application Layout

myapp/ ├── app/ │ ├── models/ # Domain objects │ ├── services/ # Business logic (POROs) │ └── validators/ # Input validation ├── config/ ├── db/migrate/ # Database migrations ├── lib/tasks/ # Rake tasks ├── spec/ │ ├── spec_helper.rb │ ├── models/ │ └── services/ ├── Gemfile ├── Gemfile.lock └── Rakefile

  • Service objects for business logic (single public method: #call )

  • One class per file, file name matches class name in snake_case

  • No global mutable state; use dependency injection or configuration objects

Key Patterns

Enumerable & Lazy Evaluation

Chain Enumerable methods over manual loops

users.select(&:active?).map(&:email).sort

Lazy evaluation for large collections

File.open("huge.log").each_line.lazy .select { |line| line.include?("ERROR") } .map { |line| parse_error(line) } .first(10)

each_with_object for building hashes

totals = orders.each_with_object(Hash.new(0)) do |order, sums| sums[order.category] += order.amount end

Pattern Matching (Ruby 3.x)

case response in { status: 200, body: { data: Array => items } } process_items(items) in { status: 404 } handle_not_found in { status: (500..) } handle_server_error end

Pin operator for variable binding

expected_id = 42 case record in { id: ^expected_id, name: String => name } puts "Found: #{name}" end

Frozen String Literals & Immutability

frozen_string_literal: true

name = "hello" name << " world" # => FrozenError

Use +"" or .dup when mutation is needed

mutable = +"hello" mutable << " world" # => "hello world"

VALID_STATUSES = %w[pending active suspended].freeze CONFIG_DEFAULTS = { timeout: 30, retries: 3 }.freeze

Ractor (Ruby 3.x Parallelism)

workers = 4.times.map do Ractor.new do loop do task = Ractor.receive Ractor.yield expensive_computation(task) end end end

tasks.each_with_index { |task, i| workers[i % workers.size].send(task) } results = workers.map(&:take)

Testing

RSpec

RSpec.describe PaymentService do subject(:service) { described_class.new(gateway: gateway) }

let(:gateway) { instance_double(PaymentGateway) } let(:account) { build(:account, balance: 100.0) }

describe "#charge" do context "when account has sufficient funds" do before { allow(gateway).to receive(:process).and_return(true) }

  it "debits the account" do
    expect { service.charge(account, 50.0) }
      .to change { account.balance }.from(100.0).to(50.0)
  end
end

context "when account has insufficient funds" do
  it "raises InsufficientFundsError" do
    expect { service.charge(account, 200.0) }
      .to raise_error(InsufficientFundsError, /needs 200/)
  end
end

end end

RSpec Conventions

  • describe for class/method, context for scenario (prefix with "when" or "with")

  • let for lazy data, let! only when eager evaluation is needed

  • subject for the object under test, described_class over hardcoded class names

  • Prefer instance_double over generic double for type safety

  • Use shared examples for behavior shared across classes

FactoryBot

FactoryBot.define do factory :user do sequence(:email) { |n| "user#{n}@example.com" } name { "Test User" } active { true } trait(:admin) { role { "admin" } } trait(:inactive) { active { false } } end end

Testing Standards

  • Test files mirror source: lib/foo/bar.rb -> spec/foo/bar_spec.rb

  • Coverage target: >80% for business logic, >60% overall (use simplecov )

  • All bug fixes include a regression test

  • Test edge cases: nil, empty string, empty array, boundary values

  • Use webmock or vcr for HTTP stubbing (no real network in unit tests)

Tooling

Essential Commands

bundle install # Install dependencies bundle exec rspec # Run tests through Bundler bundle exec rubocop # Lint bundle exec rubocop -a # Auto-fix safe cops bundle exec rake # Default task (usually tests) bundle audit # Check for vulnerable gems bundle outdated # Show outdated gems

RuboCop Configuration

.rubocop.yml

require:

  • rubocop-rspec
  • rubocop-performance AllCops: NewCops: enable TargetRubyVersion: 3.2 Style/FrozenStringLiteralComment: Enabled: true EnforcedStyle: always Metrics/MethodLength: Max: 20 Metrics/CyclomaticComplexity: Max: 10

Bundler Best Practices

Gemfile

source "https://rubygems.org" ruby "~> 3.2"

gem "dry-struct", "> 1.6" gem "zeitwerk", "> 2.6"

group :development, :test do gem "rspec", "> 3.13" gem "rubocop", "> 1.60", require: false gem "factory_bot", "~> 6.4" end

group :test do gem "simplecov", require: false gem "webmock", "~> 3.19" end

Advanced Topics

For detailed patterns and examples, see:

  • references/patterns.md -- Enumerable patterns, metaprogramming examples, RSpec matchers

External References

  • Ruby Style Guide

  • RSpec Documentation

  • RuboCop Documentation

  • Ruby 3.x Pattern Matching

  • Bundler Best Practices

  • RBS Type Signatures

  • Zeitwerk Autoloading

  • Ruby on Rails Guides (for Rails projects)

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

code-review

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

python-guide

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

typescript-guide

No summary provided by upstream source.

Repository SourceNeeds Review
General

actix-web

No summary provided by upstream source.

Repository SourceNeeds Review