ruby

SKILL.md

Ruby Development Skill

Purpose

This skill provides comprehensive guidance for Ruby development, covering language fundamentals, object-oriented design, error handling, performance optimization, and modern Ruby (3.x+) features. It synthesizes knowledge from Ruby internals, best practices, and official documentation to help Claude write idiomatic, maintainable, and performant Ruby code.

When to Use This Skill

Use this skill when:

  • Writing or reviewing Ruby code
  • Debugging Ruby applications
  • Optimizing Ruby performance
  • Implementing object-oriented designs
  • Handling errors and exceptions
  • Working with Ruby's standard library
  • Using modern Ruby features (pattern matching, types, fibers, ractors)
  • Building Rails applications or Ruby gems

Ruby Philosophy and Core Principles

Matz's Design Philosophy

Ruby is designed to make programmers happy. It prioritizes:

  1. Developer Productivity - Write less code to accomplish more
  2. Readability - Code should read like natural language
  3. Flexibility - Multiple ways to accomplish tasks (TMTOWTDI - There's More Than One Way To Do It)
  4. Object-Oriented Everything - Everything is an object, including primitives
  5. Duck Typing - "If it walks like a duck and quacks like a duck, it's a duck"

Ruby's Core Characteristics

# Everything is an object
5.times { puts "Hello" }          # Integer is an object
"hello".upcase                    # String is an object
nil.class                         # => NilClass

# Blocks are first-class citizens
[1, 2, 3].map { |n| n * 2 }      # => [2, 4, 6]

# Open classes - can modify any class
class String
  def shout
    "#{upcase}!"
  end
end

"hello".shout                     # => "HELLO!"

# Duck typing - focus on behavior, not type
def process(thing)
  thing.call if thing.respond_to?(:call)
end

Object-Oriented Design in Ruby

The Ruby Object Model

Understanding Ruby's object model is crucial for effective programming:

# Class hierarchy
class Animal
  def speak
    "Some sound"
  end
end

class Dog < Animal
  def speak
    "Woof!"
  end
end

# Every class is an instance of Class
Dog.class                         # => Class
Dog.superclass                    # => Animal
Animal.superclass                 # => Object
Object.superclass                 # => BasicObject

# Singleton methods (eigenclass/metaclass)
dog = Dog.new
def dog.name
  "Buddy"
end

dog.name                          # => "Buddy"
Dog.new.name                      # NoMethodError

Composition Over Inheritance

Prefer composition and modules over deep inheritance hierarchies:

# ❌ Bad: Deep inheritance
class Vehicle
end

class LandVehicle < Vehicle
end

class Car < LandVehicle
end

class SportsCar < Car
end

# ✅ Good: Composition with modules
module Drivable
  def drive
    "Driving..."
  end
end

module Flyable
  def fly
    "Flying..."
  end
end

class Car
  include Drivable
end

class Plane
  include Flyable
  include Drivable  # Can taxi on ground
end

Single Responsibility Principle

Each class should have one reason to change:

# ❌ Bad: Multiple responsibilities
class User
  def save
    # Database logic
  end

  def send_email
    # Email logic
  end

  def generate_report
    # Report logic
  end
end

# ✅ Good: Separate concerns
class User
  def save
    UserRepository.new.save(self)
  end
end

class UserMailer
  def send_welcome_email(user)
    # Email logic
  end
end

class UserReportGenerator
  def generate(user)
    # Report logic
  end
end

Dependency Injection

Inject dependencies rather than hardcoding them:

# ❌ Bad: Hard dependency
class OrderProcessor
  def process(order)
    PaymentGateway.new.charge(order.amount)
    EmailService.new.send_confirmation(order)
  end
end

# ✅ Good: Dependency injection
class OrderProcessor
  def initialize(payment_gateway: PaymentGateway.new,
                 email_service: EmailService.new)
    @payment_gateway = payment_gateway
    @email_service = email_service
  end

  def process(order)
    @payment_gateway.charge(order.amount)
    @email_service.send_confirmation(order)
  end
end

Law of Demeter (Principle of Least Knowledge)

Avoid reaching through multiple objects:

# ❌ Bad: Train wreck
customer.orders.last.line_items.first.price

# ✅ Good: Delegate or encapsulate
class Customer
  def last_order_first_item_price
    orders.last&.first_item_price
  end
end

class Order
  def first_item_price
    line_items.first&.price
  end
end

customer.last_order_first_item_price

Error Handling and Exceptions

The Exception Hierarchy

Exception
├── NoMemoryError
├── ScriptError
│   ├── LoadError
│   ├── NotImplementedError
│   └── SyntaxError
├── SignalException
│   └── Interrupt
├── StandardError (Default rescue catches this)
│   ├── ArgumentError
│   ├── IOError
│   │   └── EOFError
│   ├── IndexError
│   ├── LocalJumpError
│   ├── NameError
│   │   └── NoMethodError
│   ├── RangeError
│   ├── RegexpError
│   ├── RuntimeError (Default raise creates this)
│   ├── SecurityError
│   ├── SystemCallError
│   ├── ThreadError
│   ├── TypeError
│   └── ZeroDivisionError
├── SystemExit
└── SystemStackError

Exception Handling Best Practices

1. Exceptions Should Be Exceptional

Use exceptions for exceptional cases, not control flow:

# ❌ Bad: Using exceptions for control flow
def find_user(id)
  user = User.find(id)
rescue ActiveRecord::RecordNotFound
  nil
end

# ✅ Good: Use explicit checks
def find_user(id)
  User.find_by(id: id)
end

2. Rescue Specific Exceptions

Always rescue specific exceptions, never bare rescue:

# ❌ Bad: Catches everything, including SystemExit
begin
  dangerous_operation
rescue
  # Too broad!
end

# ✅ Good: Rescue specific exceptions
begin
  dangerous_operation
rescue NetworkError, TimeoutError => e
  logger.error("Network issue: #{e.message}")
  retry_operation
end

3. Fail Fast, Fail Loudly

Let errors propagate unless you can handle them meaningfully:

# ❌ Bad: Swallowing exceptions
def process_data(data)
  result = parse(data)
rescue => e
  nil  # Silent failure!
end

# ✅ Good: Let it fail or handle meaningfully
def process_data(data)
  parse(data)
rescue ParseError => e
  logger.error("Failed to parse data: #{e.message}")
  raise  # Re-raise to propagate
end

4. Use ensure for Cleanup

Always use ensure for cleanup code:

# ✅ Proper resource management
def process_file(filename)
  file = File.open(filename)
  process(file)
ensure
  file&.close
end

# Better: Use blocks that auto-close
def process_file(filename)
  File.open(filename) do |file|
    process(file)
  end  # Automatically closed
end

5. Custom Exceptions for Domain Logic

Create custom exceptions for your domain:

# Define custom exceptions
class PaymentError < StandardError; end
class InsufficientFundsError < PaymentError; end
class InvalidCardError < PaymentError; end

# Use them meaningfully
def charge_card(card, amount)
  raise InvalidCardError, "Card expired" if card.expired?
  raise InsufficientFundsError if balance < amount

  process_charge(card, amount)
end

# Caller can handle appropriately
begin
  charge_card(card, 100)
rescue InsufficientFundsError => e
  notify_user("Insufficient funds")
rescue InvalidCardError => e
  notify_user("Please update your card")
rescue PaymentError => e
  # Catch all payment errors
  logger.error("Payment failed: #{e.message}")
end

6. The Weirich raise/fail Convention

Use fail for exceptions you expect to be rescued, raise for re-raising:

def process_order(order)
  fail ArgumentError, "Order cannot be nil" if order.nil?

  begin
    payment_gateway.charge(order)
  rescue PaymentError => e
    logger.error("Payment failed: #{e.message}")
    raise  # Re-raise with raise
  end
end

7. Provide Context in Exceptions

Include helpful information in exception messages:

# ❌ Bad: Vague message
raise "Invalid input"

# ✅ Good: Descriptive message with context
raise ArgumentError, "Expected positive integer for age, got: #{age.inspect}"

Alternative Error Handling Patterns

Result Objects

Return result objects instead of raising exceptions:

class Result
  attr_reader :value, :error

  def initialize(value: nil, error: nil)
    @value = value
    @error = error
  end

  def success?
    error.nil?
  end

  def failure?
    !success?
  end
end

def divide(a, b)
  return Result.new(error: "Division by zero") if b.zero?
  Result.new(value: a / b)
end

result = divide(10, 2)
if result.success?
  puts result.value
else
  puts "Error: #{result.error}"
end

Caller-Supplied Fallback Strategy

Let callers define error handling:

def fetch_user(id, &fallback)
  User.find(id)
rescue ActiveRecord::RecordNotFound => e
  fallback ? fallback.call(e) : raise
end

# Usage
user = fetch_user(999) { |e| User.new(name: "Guest") }

Ruby Performance and Optimization

Understanding Ruby's VM (YARV)

Ruby 3.x uses YARV (Yet Another Ruby VM) with JIT compilation:

# Enable JIT (YJIT in Ruby 3.1+)
# Run with: ruby --yjit your_script.rb

# Check JIT status
puts "JIT enabled: #{defined?(RubyVM::YJIT)}"

# Profile JIT compilation
RubyVM::YJIT.runtime_stats if defined?(RubyVM::YJIT)

Memory Management and Garbage Collection

Ruby uses generational garbage collection:

# Check GC stats
GC.stat
# => {:count=>23, :heap_allocated_pages=>145, ...}

# Manual GC control (rarely needed)
GC.disable  # Disable GC temporarily
# ... do intensive work
GC.enable
GC.start    # Force GC

# Monitor object allocations
before = GC.stat(:total_allocated_objects)
# ... your code
after = GC.stat(:total_allocated_objects)
puts "Allocated: #{after - before} objects"

Performance Best Practices

1. Avoid Creating Unnecessary Objects

# ❌ Bad: Creates many string objects
1000.times do |i|
  "User #{i}"  # New string each time
end

# ✅ Good: Reuse strings with interpolation
template = "User %d"
1000.times do |i|
  template % i
end

# ✅ Even better: Use frozen strings
MESSAGE = "Processing".freeze

2. Use Symbols for Repeated Strings

# ❌ Bad: Creates new string objects
hash = { "name" => "John", "age" => 30 }

# ✅ Good: Symbols are immutable and reused
hash = { name: "John", age: 30 }

3. Prefer Enumerable Methods Over Loops

# ❌ Bad: Manual loop
result = []
array.each do |item|
  result << item * 2 if item > 0
end

# ✅ Good: Chained enumerable methods
result = array.select { |item| item > 0 }
              .map { |item| item * 2 }

# ✅ Even better: Single pass with each_with_object
result = array.each_with_object([]) do |item, acc|
  acc << item * 2 if item > 0
end

4. Use Lazy Enumerables for Large Collections

# ❌ Bad: Creates intermediate arrays
(1..1_000_000).select { |n| n.even? }
              .map { |n| n * 2 }
              .first(10)

# ✅ Good: Lazy evaluation
(1..1_000_000).lazy
              .select { |n| n.even? }
              .map { |n| n * 2 }
              .first(10)

5. Cache Expensive Computations

# ❌ Bad: Recomputes every time
class User
  def full_name
    "#{first_name} #{last_name}".strip
  end
end

# ✅ Good: Memoization
class User
  def full_name
    @full_name ||= "#{first_name} #{last_name}".strip
  end
end

# ⚠️ Careful with nil/false values
def expensive_check
  return @result if defined?(@result)
  @result = compute_result
end

Modern Ruby Features (3.x+)

Pattern Matching (Ruby 2.7+)

# Basic pattern matching
case [1, 2, 3]
in [a, b, c]
  puts "#{a}, #{b}, #{c}"
end

# Hash patterns
case { name: "John", age: 30 }
in { name: "John", age: age }
  puts "John is #{age}"
in { name:, age: }  # Variable punning
  puts "#{name} is #{age}"
end

# Array patterns with rest
case [1, 2, 3, 4, 5]
in [first, *rest, last]
  puts "First: #{first}, Last: #{last}, Rest: #{rest}"
end

# Rightward assignment (Ruby 3.0+)
{ name: "John", age: 30 } => { name:, age: }
puts name  # => "John"

# Guard clauses
case value
in String => s if s.length > 10
  puts "Long string: #{s}"
in String => s
  puts "Short string: #{s}"
end

Endless Method Definition (Ruby 3.0+)

# Traditional
def square(x)
  x * x
end

# Endless method (for simple one-liners)
def square(x) = x * x
def full_name = "#{first_name} #{last_name}"
def admin? = role == "admin"

Numbered Parameters (Ruby 2.7+)

# Traditional block parameters
[1, 2, 3].map { |n| n * 2 }

# Numbered parameters
[1, 2, 3].map { _1 * 2 }

# Multiple numbered parameters
hash.map { [_1, _2 * 2] }

Rightward Assignment (Ruby 3.0+)

# Traditional assignment
result = compute_value()
puts result

# Rightward assignment (useful in method chains)
compute_value() => result
puts result

# Useful for debugging
calculate_price.tap { p _1 } => price

Ractors (Ruby 3.0+) - True Parallelism

# Create parallel-safe ractor
r = Ractor.new do
  received = Ractor.receive
  received * 2
end

r.send(21)
r.take  # => 42

# Multiple ractors
results = 4.times.map do |i|
  Ractor.new(i) do |n|
    # Heavy computation
    (1..1000000).reduce(:+) + n
  end
end

results.map(&:take)  # Runs in parallel

Typed Ruby with RBS (Ruby 3.0+)

# Define types in .rbs files
# user.rbs
class User
  attr_reader name: String
  attr_reader age: Integer

  def initialize: (name: String, age: Integer) -> void
  def adult?: () -> bool
end

# Use TypeProf to generate signatures
# $ typeprof user.rb

# Validate with Steep or RBS
# $ steep check

Fiber Scheduler (Ruby 3.0+) - Non-blocking I/O

require 'async'

# Async execution with fibers
Async do
  Async do
    puts "Task 1 start"
    sleep 2
    puts "Task 1 end"
  end

  Async do
    puts "Task 2 start"
    sleep 1
    puts "Task 2 end"
  end
end
# Both tasks run concurrently

Ruby Standard Library Essentials

Working with Collections

# Array operations
arr = [1, 2, 3, 4, 5]

arr.first(2)                # => [1, 2]
arr.last(2)                 # => [4, 5]
arr.sample                  # Random element
arr.shuffle                 # Randomize order
arr.rotate(2)               # => [3, 4, 5, 1, 2]
arr.combination(2).to_a     # All 2-element combinations
arr.permutation(2).to_a     # All 2-element permutations

# Hash operations
hash = { a: 1, b: 2, c: 3 }

hash.fetch(:d, 0)           # => 0 (default value)
hash.dig(:nested, :key)     # Safe nested access
hash.transform_values(&:to_s)  # => { a: "1", b: "2", c: "3" }
hash.slice(:a, :b)          # => { a: 1, b: 2 }
hash.merge(d: 4)            # Non-destructive merge

# Set operations
require 'set'
s1 = Set[1, 2, 3]
s2 = Set[2, 3, 4]

s1 | s2                     # Union => #<Set: {1, 2, 3, 4}>
s1 & s2                     # Intersection => #<Set: {2, 3}>
s1 - s2                     # Difference => #<Set: {1}>

String Manipulation

# String methods
str = "  Hello, World!  "

str.strip                   # => "Hello, World!"
str.split(", ")             # => ["Hello", "World!"]
str.gsub("World", "Ruby")   # => "  Hello, Ruby!  "
str.scan(/\w+/)             # => ["Hello", "World"]
str.start_with?("Hello")    # => false (has spaces)
str.include?("World")       # => true

# String interpolation
name = "John"
age = 30
"#{name} is #{age}"         # => "John is 30"
"2 + 2 = #{2 + 2}"          # => "2 + 2 = 4"

# Heredocs
text = <<~TEXT
  This is a heredoc.
  Indentation is removed.
  Very useful for multi-line strings.
TEXT

# Frozen strings (immutable)
CONSTANT = "immutable".freeze
# Or with magic comment:
# frozen_string_literal: true

File I/O

# Reading files
content = File.read("file.txt")
lines = File.readlines("file.txt")

# Block-based reading (auto-closes)
File.open("file.txt") do |file|
  file.each_line do |line|
    puts line
  end
end

# Writing files
File.write("output.txt", "Hello, World!")

File.open("output.txt", "w") do |file|
  file.puts "Line 1"
  file.puts "Line 2"
end

# File operations
File.exist?("file.txt")
File.directory?("path")
File.size("file.txt")
File.mtime("file.txt")      # Modification time

# Directory operations
Dir.glob("**/*.rb")         # Find all Ruby files recursively
Dir.foreach("path") { |file| puts file }
Dir.mkdir("new_dir")

Regular Expressions

# Pattern matching
text = "Hello, my email is john@example.com"

# Match operator
text =~ /\w+@\w+\.\w+/      # => 18 (match position)

# Match method
match = text.match(/(\w+)@(\w+)\.(\w+)/)
match[0]                     # => "john@example.com"
match[1]                     # => "john"
match[2]                     # => "example"

# Named captures
match = text.match(/(?<user>\w+)@(?<domain>\w+)\.(?<tld>\w+)/)
match[:user]                 # => "john"
match[:domain]               # => "example"

# Scan for all matches
emails = text.scan(/\w+@\w+\.\w+/)

# Replace with regex
text.gsub(/\b\w{4}\b/, "****")  # Mask 4-letter words

Testing Ruby Code

Minitest (Standard Library)

require 'minitest/autorun'

class UserTest < Minitest::Test
  def setup
    @user = User.new(name: "John", age: 30)
  end

  def test_adult_with_age_over_18
    assert @user.adult?
  end

  def test_name_is_capitalized
    assert_equal "John", @user.name
  end

  def test_invalid_age_raises_error
    assert_raises(ArgumentError) do
      User.new(name: "John", age: -5)
    end
  end

  def teardown
    # Cleanup if needed
  end
end

RSpec (Popular Testing Framework)

require 'rspec'

RSpec.describe User do
  let(:user) { User.new(name: "John", age: 30) }

  describe '#adult?' do
    context 'when age is over 18' do
      it 'returns true' do
        expect(user.adult?).to be true
      end
    end

    context 'when age is under 18' do
      let(:user) { User.new(name: "Jane", age: 15) }

      it 'returns false' do
        expect(user.adult?).to be false
      end
    end
  end

  describe '#initialize' do
    it 'raises error for negative age' do
      expect { User.new(name: "John", age: -5) }
        .to raise_error(ArgumentError, /negative age/)
    end
  end

  describe '#name' do
    it 'returns capitalized name' do
      expect(user.name).to eq("John")
    end
  end
end

Testing Best Practices

# 1. Use descriptive test names
def test_user_is_adult_when_age_is_over_18
  # Clear what is being tested
end

# 2. Arrange-Act-Assert pattern
def test_order_total
  # Arrange
  order = Order.new
  order.add_item(item: "Book", price: 10)
  order.add_item(item: "Pen", price: 2)

  # Act
  total = order.total

  # Assert
  assert_equal 12, total
end

# 3. Test one thing per test
# ❌ Bad: Tests multiple things
def test_user
  assert user.valid?
  assert_equal "John", user.name
  assert_equal 30, user.age
end

# ✅ Good: Separate tests
def test_user_is_valid
  assert user.valid?
end

def test_user_name
  assert_equal "John", user.name
end

# 4. Use fixtures/factories for test data
# factories.rb
FactoryBot.define do
  factory :user do
    name { "John" }
    age { 30 }
    email { "john@example.com" }
  end
end

# In tests
user = create(:user)
user_attrs = attributes_for(:user)

Common Ruby Patterns and Idioms

Method Chaining (Fluent Interface)

class QueryBuilder
  def initialize
    @conditions = []
    @order = nil
  end

  def where(condition)
    @conditions << condition
    self  # Return self for chaining
  end

  def order(field)
    @order = field
    self
  end

  def to_sql
    sql = "SELECT * FROM users"
    sql += " WHERE #{@conditions.join(' AND ')}" unless @conditions.empty?
    sql += " ORDER BY #{@order}" if @order
    sql
  end
end

# Usage
query = QueryBuilder.new
         .where("age > 18")
         .where("active = true")
         .order("name")
         .to_sql

Builder Pattern

class UserBuilder
  def initialize
    @user = User.new
  end

  def with_name(name)
    @user.name = name
    self
  end

  def with_email(email)
    @user.email = email
    self
  end

  def build
    @user
  end
end

# Usage
user = UserBuilder.new
        .with_name("John")
        .with_email("john@example.com")
        .build

Null Object Pattern

class NullUser
  def name
    "Guest"
  end

  def admin?
    false
  end

  def logged_in?
    false
  end
end

class UserSession
  def current_user
    @current_user || NullUser.new
  end
end

# Usage - no nil checks needed
session = UserSession.new
puts session.current_user.name  # "Guest" instead of error

Strategy Pattern

# Define strategies
class CreditCardPayment
  def process(amount)
    # Credit card logic
  end
end

class PayPalPayment
  def process(amount)
    # PayPal logic
  end
end

# Use strategy
class Order
  def initialize(payment_strategy)
    @payment_strategy = payment_strategy
  end

  def checkout(amount)
    @payment_strategy.process(amount)
  end
end

# Usage
order = Order.new(CreditCardPayment.new)
order.checkout(100)

Observer Pattern

require 'observer'

class Order
  include Observable

  attr_reader :status

  def status=(new_status)
    @status = new_status
    changed
    notify_observers(self)
  end
end

class Logger
  def update(order)
    puts "Order status changed to: #{order.status}"
  end
end

class Emailer
  def update(order)
    puts "Sending email about: #{order.status}"
  end
end

# Usage
order = Order.new
order.add_observer(Logger.new)
order.add_observer(Emailer.new)
order.status = "shipped"

Ruby Code Style and Conventions

Naming Conventions

# Classes and Modules: PascalCase
class UserAccount
end

module PaymentProcessing
end

# Methods and Variables: snake_case
def calculate_total_price
  total_amount = 0
end

# Constants: SCREAMING_SNAKE_CASE
MAX_RETRIES = 3
DEFAULT_TIMEOUT = 30

# Predicate methods: end with ?
def valid?
  errors.empty?
end

def admin?
  role == 'admin'
end

# Dangerous methods: end with !
def save!  # Raises exception on failure
  raise "Invalid" unless valid?
  persist
end

def downcase!  # Mutates the object
  @value = @value.downcase
end

Code Organization

# Class organization
class User
  # 1. Extend and include statements
  extend SomeModule
  include AnotherModule

  # 2. Constants
  MAX_NAME_LENGTH = 100

  # 3. Attribute macros
  attr_reader :id
  attr_accessor :name

  # 4. Class methods
  def self.find(id)
    # ...
  end

  # 5. Initialization
  def initialize(name)
    @name = name
  end

  # 6. Public instance methods
  def full_name
    "#{first_name} #{last_name}"
  end

  # 7. Protected methods
  protected

  def internal_helper
    # ...
  end

  # 8. Private methods
  private

  def calculate_something
    # ...
  end
end

Ruby Style Guidelines

# Use 2 spaces for indentation
def method_name
  if condition
    do_something
  end
end

# Avoid ternary operators for multi-line
# ❌ Bad
result = some_long_condition ?
         long_true_value :
         long_false_value

# ✅ Good
result = if some_long_condition
          long_true_value
        else
          long_false_value
        end

# Use %w for word arrays
# ❌ Bad
STATES = ['draft', 'published', 'archived']

# ✅ Good
STATES = %w[draft published archived]

# Use symbols for hash keys
# ❌ Bad (when strings aren't needed)
{ 'name' => 'John', 'age' => 30 }

# ✅ Good
{ name: 'John', age: 30 }

# Use guard clauses
# ❌ Bad
def process(value)
  if value
    if value.valid?
      # ... main logic
    end
  end
end

# ✅ Good
def process(value)
  return unless value
  return unless value.valid?

  # ... main logic
end

# Avoid returning from ensure
# ❌ Bad - return value is ignored
def bad_example
  return 42
ensure
  return 0  # This overrides!
end

# ✅ Good
def good_example
  result = 42
ensure
  cleanup
end

Debugging Ruby Code

Using pry for Debugging

require 'pry'

def complex_method(data)
  result = transform(data)
  binding.pry  # Execution pauses here
  result * 2
end

# In pry session:
# - ls: List available methods
# - show-method method_name: Show method source
# - cd object: Enter object context
# - whereami: Show context
# - continue: Resume execution

Using ruby/debug (Ruby 3.1+)

require 'debug'

def calculate(x, y)
  debugger  # Execution pauses here
  result = x + y
  result
end

# Commands:
# - step: Step into
# - next: Step over
# - continue: Resume
# - info: Show information
# - break: Set breakpoint

Logging Best Practices

require 'logger'

logger = Logger.new(STDOUT)
logger.level = Logger::INFO

# Different log levels
logger.debug("Detailed debug information")
logger.info("Informational messages")
logger.warn("Warning messages")
logger.error("Error messages")
logger.fatal("Fatal errors")

# Structured logging
logger.info("User logged in") do
  { user_id: 123, ip: "192.168.1.1" }
end

Concurrency and Threading

Thread Basics

# Create threads
threads = 3.times.map do |i|
  Thread.new(i) do |thread_num|
    puts "Thread #{thread_num} starting"
    sleep 1
    puts "Thread #{thread_num} done"
  end
end

# Wait for all threads
threads.each(&:join)

# Thread-local variables
Thread.current[:user_id] = 123
Thread.current[:user_id]  # => 123

Thread Safety

# ❌ Bad: Race condition
class Counter
  def initialize
    @count = 0
  end

  def increment
    @count += 1  # Not atomic!
  end
end

# ✅ Good: Thread-safe with mutex
class Counter
  def initialize
    @count = 0
    @mutex = Mutex.new
  end

  def increment
    @mutex.synchronize do
      @count += 1
    end
  end
end

# ✅ Better: Use Concurrent::AtomicFixnum
require 'concurrent'

counter = Concurrent::AtomicFixnum.new(0)
counter.increment

Ractors for Parallelism (Ruby 3.0+)

# True parallel execution
def parallel_map(array, &block)
  ractors = array.map do |item|
    Ractor.new(item, block) do |value, transform|
      transform.call(value)
    end
  end

  ractors.map(&:take)
end

# Usage
results = parallel_map([1, 2, 3, 4]) { |n| n * 2 }
# => [2, 4, 6, 8]

Metaprogramming

method_missing

class DynamicAccessor
  def initialize(data)
    @data = data
  end

  def method_missing(method, *args)
    if @data.key?(method)
      @data[method]
    else
      super
    end
  end

  def respond_to_missing?(method, include_private = false)
    @data.key?(method) || super
  end
end

# Usage
obj = DynamicAccessor.new(name: "John", age: 30)
obj.name  # => "John"
obj.age   # => 30

define_method

class Model
  %w[name email age].each do |attr|
    define_method(attr) do
      instance_variable_get("@#{attr}")
    end

    define_method("#{attr}=") do |value|
      instance_variable_set("@#{attr}", value)
    end
  end
end

# Creates name, name=, email, email=, age, age= methods

class_eval and instance_eval

# class_eval: Evaluates in class context
String.class_eval do
  def shout
    upcase + "!"
  end
end

"hello".shout  # => "HELLO!"

# instance_eval: Evaluates in instance context
str = "hello"
str.instance_eval do
  def custom_method
    "Custom: #{self}"
  end
end

str.custom_method  # => "Custom: hello"

Memory and Performance Profiling

Benchmark Module

require 'benchmark'

n = 1_000_000
Benchmark.bm(20) do |x|
  x.report("Array#each:") do
    arr = []
    n.times { |i| arr << i }
  end

  x.report("Array#map:") do
    (0...n).map { |i| i }
  end

  x.report("Array.new:") do
    Array.new(n) { |i| i }
  end
end

Memory Profiler

require 'memory_profiler'

report = MemoryProfiler.report do
  # Code to profile
  1000.times { "string" + "concatenation" }
end

report.pretty_print

Ruby Profiler

require 'ruby-prof'

result = RubyProf.profile do
  # Code to profile
  10_000.times { expensive_operation }
end

printer = RubyProf::FlatPrinter.new(result)
printer.print(STDOUT)

Common Pitfalls and How to Avoid Them

1. Modifying Collections During Iteration

# ❌ Bad: Modifies while iterating
array = [1, 2, 3, 4, 5]
array.each do |item|
  array.delete(item) if item.even?  # Unpredictable!
end

# ✅ Good: Use reject or delete_if
array.reject! { |item| item.even? }
# Or
array.delete_if { |item| item.even? }

2. Unintended Global Variable Modification

# ❌ Bad: Global variable
$user_count = 0

# ✅ Good: Class or instance variable
class UserCounter
  @count = 0

  class << self
    attr_accessor :count
  end
end

3. String Concatenation in Loops

# ❌ Bad: Creates many string objects
result = ""
1000.times { |i| result += "#{i} " }

# ✅ Good: Use array join
result = 1000.times.map { |i| "#{i} " }.join

# ✅ Better: Use string builder
result = String.new
1000.times { |i| result << "#{i} " }

4. Forgetting to Return Values

# ❌ Bad: No explicit return
def calculate
  total = items.sum
  # Implicitly returns total, but unclear
end

# ✅ Good: Explicit return for clarity
def calculate
  total = items.sum
  return total
end

# ✅ Best: Last expression is return value
def calculate
  items.sum
end

Framework-Specific Guidance

Rails-Specific Best Practices

# Use scopes for reusable queries
class User < ApplicationRecord
  scope :active, -> { where(active: true) }
  scope :recent, -> { where('created_at > ?', 1.week.ago) }
end

# Use concerns for shared behavior
module Timestampable
  extend ActiveSupport::Concern

  included do
    before_save :update_timestamp
  end

  def update_timestamp
    self.updated_at = Time.current
  end
end

# Use strong parameters
class UsersController < ApplicationController
  def create
    @user = User.new(user_params)
    # ...
  end

  private

  def user_params
    params.require(:user).permit(:name, :email, :age)
  end
end

# Eager loading to avoid N+1 queries
# ❌ Bad: N+1 query
users = User.all
users.each { |user| puts user.posts.count }

# ✅ Good: Eager load
users = User.includes(:posts).all
users.each { |user| puts user.posts.count }

Quick Reference Commands

# Ruby version
ruby -v

# Run Ruby file
ruby script.rb

# Interactive Ruby (IRB)
irb

# Execute inline Ruby
ruby -e "puts 'Hello, World!'"

# Check syntax without executing
ruby -c script.rb

# Run with warnings
ruby -w script.rb

# Install gem
gem install gem_name

# List installed gems
gem list

# Update gems
gem update

# Bundle install (Rails)
bundle install

# Run tests
ruby test/my_test.rb
rake test
rspec spec/

# Ruby documentation
ri String#upcase
ri Array

# Generate documentation
rdoc
yard doc

Resources and Further Learning

Summary

Ruby is designed for developer happiness and productivity. When writing Ruby code:

  1. Write readable code - Code is read more than it's written
  2. Follow conventions - Consistency helps teams collaborate
  3. Test thoroughly - Tests give confidence in refactoring
  4. Handle errors explicitly - Fail fast and provide context
  5. Optimize when necessary - Profile before optimizing
  6. Embrace Ruby's features - Use blocks, modules, and metaprogramming appropriately
  7. Stay current - Ruby 3.x brings significant improvements

Remember: Ruby rewards simple, expressive code that clearly communicates intent.

Weekly Installs
1
Installed on
windsurf1
opencode1
codex1
claude-code1
antigravity1
gemini-cli1