| 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:
- Developer Productivity - Write less code to accomplish more
- Readability - Code should read like natural language
- Flexibility - Multiple ways to accomplish tasks (TMTOWTDI - There's More Than One Way To Do It)
- Object-Oriented Everything - Everything is an object, including primitives
- Duck Typing - "If it walks like a duck and quacks like a duck, it's a duck"
Ruby's Core Characteristics
5.times { puts "Hello" }
"hello".upcase
nil.class
[1, 2, 3].map { |n| n * 2 }
class String
def shout
"#{upcase}!"
end
end
"hello".shout
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 Animal
def speak
"Some sound"
end
end
class Dog < Animal
def speak
"Woof!"
end
end
Dog.class
Dog.superclass
Animal.superclass
Object.superclass
dog = Dog.new
def dog.name
"Buddy"
end
dog.name
Dog.new.name
Composition Over Inheritance
Prefer composition and modules over deep inheritance hierarchies:
class Vehicle
end
class LandVehicle < Vehicle
end
class Car < LandVehicle
end
class SportsCar < Car
end
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
end
Single Responsibility Principle
Each class should have one reason to change:
class User
def save
end
def send_email
end
def generate_report
end
end
class User
def save
UserRepository.new.save(self)
end
end
class UserMailer
def send_welcome_email(user)
end
end
class UserReportGenerator
def generate(user)
end
end
Dependency Injection
Inject dependencies rather than hardcoding them:
class OrderProcessor
def process(order)
PaymentGateway.new.charge(order.amount)
EmailService.new.send_confirmation(order)
end
end
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:
customer.orders.last.line_items.first.price
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:
def find_user(id)
user = User.find(id)
rescue ActiveRecord::RecordNotFound
nil
end
def find_user(id)
User.find_by(id: id)
end
2. Rescue Specific Exceptions
Always rescue specific exceptions, never bare rescue:
begin
dangerous_operation
rescue
end
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:
def process_data(data)
result = parse(data)
rescue => e
nil
end
def process_data(data)
parse(data)
rescue ParseError => e
logger.error("Failed to parse data: #{e.message}")
raise
end
4. Use ensure for Cleanup
Always use ensure for cleanup code:
def process_file(filename)
file = File.open(filename)
process(file)
ensure
file&.close
end
def process_file(filename)
File.open(filename) do |file|
process(file)
end
end
5. Custom Exceptions for Domain Logic
Create custom exceptions for your domain:
class PaymentError < StandardError; end
class InsufficientFundsError < PaymentError; end
class InvalidCardError < PaymentError; end
def charge_card(card, amount)
raise InvalidCardError, "Card expired" if card.expired?
raise InsufficientFundsError if balance < amount
process_charge(card, amount)
end
begin
charge_card(card, 100)
rescue InsufficientFundsError => e
notify_user("Insufficient funds")
rescue InvalidCardError => e
notify_user("Please update your card")
rescue PaymentError => e
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
end
end
7. Provide Context in Exceptions
Include helpful information in exception messages:
raise "Invalid input"
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
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:
puts "JIT enabled: #{defined?(RubyVM::YJIT)}"
RubyVM::YJIT.runtime_stats if defined?(RubyVM::YJIT)
Memory Management and Garbage Collection
Ruby uses generational garbage collection:
GC.stat
GC.disable
GC.enable
GC.start
before = GC.stat(:total_allocated_objects)
after = GC.stat(:total_allocated_objects)
puts "Allocated: #{after - before} objects"
Performance Best Practices
1. Avoid Creating Unnecessary Objects
1000.times do |i|
"User #{i}"
end
template = "User %d"
1000.times do |i|
template % i
end
MESSAGE = "Processing".freeze
2. Use Symbols for Repeated Strings
hash = { "name" => "John", "age" => 30 }
hash = { name: "John", age: 30 }
3. Prefer Enumerable Methods Over Loops
result = []
array.each do |item|
result << item * 2 if item > 0
end
result = array.select { |item| item > 0 }
.map { |item| item * 2 }
result = array.each_with_object([]) do |item, acc|
acc << item * 2 if item > 0
end
4. Use Lazy Enumerables for Large Collections
(1..1_000_000).select { |n| n.even? }
.map { |n| n * 2 }
.first(10)
(1..1_000_000).lazy
.select { |n| n.even? }
.map { |n| n * 2 }
.first(10)
5. Cache Expensive Computations
class User
def full_name
"#{first_name} #{last_name}".strip
end
end
class User
def full_name
@full_name ||= "#{first_name} #{last_name}".strip
end
end
def expensive_check
return @result if defined?(@result)
@result = compute_result
end
Modern Ruby Features (3.x+)
Pattern Matching (Ruby 2.7+)
case [1, 2, 3]
in [a, b, c]
puts "#{a}, #{b}, #{c}"
end
case { name: "John", age: 30 }
in { name: "John", age: age }
puts "John is #{age}"
in { name:, age: }
puts "#{name} is #{age}"
end
case [1, 2, 3, 4, 5]
in [first, *rest, last]
puts "First: #{first}, Last: #{last}, Rest: #{rest}"
end
{ name: "John", age: 30 } => { name:, age: }
puts name
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+)
def square(x)
x * x
end
def square(x) = x * x
def full_name = "#{first_name} #{last_name}"
def admin? = role == "admin"
Numbered Parameters (Ruby 2.7+)
[1, 2, 3].map { |n| n * 2 }
[1, 2, 3].map { _1 * 2 }
hash.map { [_1, _2 * 2] }
Rightward Assignment (Ruby 3.0+)
result = compute_value()
puts result
compute_value() => result
puts result
calculate_price.tap { p _1 } => price
Ractors (Ruby 3.0+) - True Parallelism
r = Ractor.new do
received = Ractor.receive
received * 2
end
r.send(21)
r.take
results = 4.times.map do |i|
Ractor.new(i) do |n|
(1..1000000).reduce(:+) + n
end
end
results.map(&:take)
Typed Ruby with RBS (Ruby 3.0+)
class User
attr_reader name: String
attr_reader age: Integer
def initialize: (name: String, age: Integer) -> void
def adult?: () -> bool
end
Fiber Scheduler (Ruby 3.0+) - Non-blocking I/O
require 'async'
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
Ruby Standard Library Essentials
Working with Collections
arr = [1, 2, 3, 4, 5]
arr.first(2)
arr.last(2)
arr.sample
arr.shuffle
arr.rotate(2)
arr.combination(2).to_a
arr.permutation(2).to_a
hash = { a: 1, b: 2, c: 3 }
hash.fetch(:d, 0)
hash.dig(:nested, :key)
hash.transform_values(&:to_s)
hash.slice(:a, :b)
hash.merge(d: 4)
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
str = " Hello, World! "
str.strip
str.split(", ")
str.gsub("World", "Ruby")
str.scan(/\w+/)
str.start_with?("Hello")
str.include?("World")
name = "John"
age = 30
"#{name} is #{age}"
"2 + 2 = #{2 + 2}"
text = <<~TEXT
This is a heredoc.
Indentation is removed.
Very useful for multi-line strings.
TEXT
CONSTANT = "immutable".freeze
File I/O
content = File.read("file.txt")
lines = File.readlines("file.txt")
File.open("file.txt") do |file|
file.each_line do |line|
puts line
end
end
File.write("output.txt", "Hello, World!")
File.open("output.txt", "w") do |file|
file.puts "Line 1"
file.puts "Line 2"
end
File.exist?("file.txt")
File.directory?("path")
File.size("file.txt")
File.mtime("file.txt")
Dir.glob("**/*.rb")
Dir.foreach("path") { |file| puts file }
Dir.mkdir("new_dir")
Regular Expressions
text = "Hello, my email is john@example.com"
text =~ /\w+@\w+\.\w+/
match = text.match(/(\w+)@(\w+)\.(\w+)/)
match[0]
match[1]
match[2]
match = text.match(/(?<user>\w+)@(?<domain>\w+)\.(?<tld>\w+)/)
match[:user]
match[:domain]
emails = text.scan(/\w+@\w+\.\w+/)
text.gsub(/\b\w{4}\b/, "****")
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
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
def test_user_is_adult_when_age_is_over_18
end
def test_order_total
order = Order.new
order.add_item(item: "Book", price: 10)
order.add_item(item: "Pen", price: 2)
total = order.total
assert_equal 12, total
end
def test_user
assert user.valid?
assert_equal "John", user.name
assert_equal 30, user.age
end
def test_user_is_valid
assert user.valid?
end
def test_user_name
assert_equal "John", user.name
end
FactoryBot.define do
factory :user do
name { "John" }
age { 30 }
email { "john@example.com" }
end
end
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
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
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
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
session = UserSession.new
puts session.current_user.name
Strategy Pattern
class CreditCardPayment
def process(amount)
end
end
class PayPalPayment
def process(amount)
end
end
class Order
def initialize(payment_strategy)
@payment_strategy = payment_strategy
end
def checkout(amount)
@payment_strategy.process(amount)
end
end
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
order = Order.new
order.add_observer(Logger.new)
order.add_observer(Emailer.new)
order.status = "shipped"
Ruby Code Style and Conventions
Naming Conventions
class UserAccount
end
module PaymentProcessing
end
def calculate_total_price
total_amount = 0
end
MAX_RETRIES = 3
DEFAULT_TIMEOUT = 30
def valid?
errors.empty?
end
def admin?
role == 'admin'
end
def save!
raise "Invalid" unless valid?
persist
end
def downcase!
@value = @value.downcase
end
Code Organization
class User
extend SomeModule
include AnotherModule
MAX_NAME_LENGTH = 100
attr_reader :id
attr_accessor :name
def self.find(id)
end
def initialize(name)
@name = name
end
def full_name
"#{first_name} #{last_name}"
end
protected
def internal_helper
end
private
def calculate_something
end
end
Ruby Style Guidelines
def method_name
if condition
do_something
end
end
result = some_long_condition ?
long_true_value :
long_false_value
result = if some_long_condition
long_true_value
else
long_false_value
end
STATES = ['draft', 'published', 'archived']
STATES = %w[draft published archived]
{ 'name' => 'John', 'age' => 30 }
{ name: 'John', age: 30 }
def process(value)
if value
if value.valid?
end
end
end
def process(value)
return unless value
return unless value.valid?
end
def bad_example
return 42
ensure
return 0
end
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
result * 2
end
Using ruby/debug (Ruby 3.1+)
require 'debug'
def calculate(x, y)
debugger
result = x + y
result
end
Logging Best Practices
require 'logger'
logger = Logger.new(STDOUT)
logger.level = Logger::INFO
logger.debug("Detailed debug information")
logger.info("Informational messages")
logger.warn("Warning messages")
logger.error("Error messages")
logger.fatal("Fatal errors")
logger.info("User logged in") do
{ user_id: 123, ip: "192.168.1.1" }
end
Concurrency and Threading
Thread Basics
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
threads.each(&:join)
Thread.current[:user_id] = 123
Thread.current[:user_id]
Thread Safety
class Counter
def initialize
@count = 0
end
def increment
@count += 1
end
end
class Counter
def initialize
@count = 0
@mutex = Mutex.new
end
def increment
@mutex.synchronize do
@count += 1
end
end
end
require 'concurrent'
counter = Concurrent::AtomicFixnum.new(0)
counter.increment
Ractors for Parallelism (Ruby 3.0+)
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
results = parallel_map([1, 2, 3, 4]) { |n| n * 2 }
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
obj = DynamicAccessor.new(name: "John", age: 30)
obj.name
obj.age
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
class_eval and instance_eval
String.class_eval do
def shout
upcase + "!"
end
end
"hello".shout
str = "hello"
str.instance_eval do
def custom_method
"Custom: #{self}"
end
end
str.custom_method
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
1000.times { "string" + "concatenation" }
end
report.pretty_print
Ruby Profiler
require 'ruby-prof'
result = RubyProf.profile do
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
array = [1, 2, 3, 4, 5]
array.each do |item|
array.delete(item) if item.even?
end
array.reject! { |item| item.even? }
array.delete_if { |item| item.even? }
2. Unintended Global Variable Modification
$user_count = 0
class UserCounter
@count = 0
class << self
attr_accessor :count
end
end
3. String Concatenation in Loops
result = ""
1000.times { |i| result += "#{i} " }
result = 1000.times.map { |i| "#{i} " }.join
result = String.new
1000.times { |i| result << "#{i} " }
4. Forgetting to Return Values
def calculate
total = items.sum
end
def calculate
total = items.sum
return total
end
def calculate
items.sum
end
Framework-Specific Guidance
Rails-Specific Best Practices
class User < ApplicationRecord
scope :active, -> { where(active: true) }
scope :recent, -> { where('created_at > ?', 1.week.ago) }
end
module Timestampable
extend ActiveSupport::Concern
included do
before_save :update_timestamp
end
def update_timestamp
self.updated_at = Time.current
end
end
class UsersController < ApplicationController
def create
@user = User.new(user_params)
end
private
def user_params
params.require(:user).permit(:name, :email, :age)
end
end
users = User.all
users.each { |user| puts user.posts.count }
users = User.includes(:posts).all
users.each { |user| puts user.posts.count }
Quick Reference Commands
ruby -v
ruby script.rb
irb
ruby -e "puts 'Hello, World!'"
ruby -c script.rb
ruby -w script.rb
gem install gem_name
gem list
gem update
bundle install
ruby test/my_test.rb
rake test
rspec spec/
ri String#upcase
ri Array
rdoc
yard doc
Resources and Further Learning
Summary
Ruby is designed for developer happiness and productivity. When writing Ruby code:
- Write readable code - Code is read more than it's written
- Follow conventions - Consistency helps teams collaborate
- Test thoroughly - Tests give confidence in refactoring
- Handle errors explicitly - Fail fast and provide context
- Optimize when necessary - Profile before optimizing
- Embrace Ruby's features - Use blocks, modules, and metaprogramming appropriately
- Stay current - Ruby 3.x brings significant improvements
Remember: Ruby rewards simple, expressive code that clearly communicates intent.