Claude Code Plugins

Community-maintained marketplace

Feedback

Comprehensive Ruby development skill covering language fundamentals, object-oriented design patterns, error handling strategies, performance optimization, modern Ruby 3.x features (pattern matching, ractors, typed Ruby), testing patterns, metaprogramming, concurrency, and Rails-specific best practices. Use when writing Ruby code, refactoring, implementing design patterns, handling exceptions, optimizing performance, writing tests, or applying Ruby idioms and conventions.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name ruby
description Comprehensive Ruby development skill covering language fundamentals, object-oriented design patterns, error handling strategies, performance optimization, modern Ruby 3.x features (pattern matching, ractors, typed Ruby), testing patterns, metaprogramming, concurrency, and Rails-specific best practices. Use when writing Ruby code, refactoring, implementing design patterns, handling exceptions, optimizing performance, writing tests, or applying Ruby idioms and conventions.

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.