Ruby Reference
Ruby Reference
The Ruby patterns you actually reach for: blocks and iterators, string manipulation, hashes and arrays, modules and mixins, error handling, file I/O, and the standard library gems worth knowing.
Blocks, Procs, Lambdas
# Block — inline code passed to a method
[1, 2, 3].each { |n| puts n }
[1, 2, 3].each do |n|
puts n * 2
end
# yield — call the block from inside a method
def measure
start = Time.now
result = yield # executes the block
puts "Took #{Time.now - start}s"
result
end
measure { sleep 0.1 }
# block_given? — check if block was passed
def greet(name)
if block_given?
yield(name)
else
"Hello, #{name}"
end
end
# Explicit block capture with &
def capture(&block)
block.call(42) # call the Proc
end
# Proc — saved block, lenient with args, returns from enclosing method
double = Proc.new { |n| n * 2 }
square = proc { |n| n ** 2 }
double.call(5) # 10
double.(5) # 10 — shorthand
double[5] # 10 — shorthand
# Lambda — strict arg checking, returns from lambda only
triple = lambda { |n| n * 3 }
triple = ->(n) { n * 3 } # stabby lambda (preferred)
triple.call(5) # 15
triple.lambda? # true
# Key difference: return behaviour
def test_proc
p = proc { return "from proc" }
p.call
"after proc" # never reached — proc return exits the method
end
def test_lambda
l = lambda { return "from lambda" }
l.call
"after lambda" # reached — lambda return stays inside lambda
end
# &method — convert method to block
[1, -2, 3].select(&method(:positive?)) # pass method as block
["1", "2"].map(&method(:Integer)) # convert strings to ints
[nil, false, 1, ""].reject(&:nil?) # Symbol#to_proc shorthand
Enumerable — the most useful Ruby methods
# map / collect — transform each element
[1, 2, 3].map { |n| n * 2 } # [2, 4, 6]
["a", "b"].map(&:upcase) # ["A", "B"]
# select / filter — keep matching
[1, 2, 3, 4].select { |n| n.even? } # [2, 4]
[1, 2, nil, false].select(&:itself) # [1, 2] (truthy only)
[1, 2, nil].compact # [1, 2] (remove nil)
# reject — remove matching
[1, 2, 3, 4].reject(&:odd?) # [2, 4]
# reduce / inject — fold to single value
[1, 2, 3, 4].reduce(:+) # 10 (sum)
[1, 2, 3, 4].reduce(10, :+) # 20 (starting value)
[1, 2, 3].reduce { |acc, n| acc * n } # 6 (product)
# each_with_object — build a result while iterating
[1, 2, 3].each_with_object([]) { |n, arr| arr << n * 2 }
# vs inject — each_with_object returns the object, inject returns last value
# group_by — partition into a hash
words = ["ant", "bear", "cat", "bee"]
words.group_by(&:length) # {3=>["ant", "cat"], 4=>["bear", "bee"]}
words.group_by { |w| w[0] } # {"a"=>["ant"], "b"=>["bear","bee"], "c"=>["cat"]}
# flat_map — map then flatten one level
[[1, 2], [3, 4]].flat_map { |a| a.map { |n| n * 2 } } # [2, 4, 6, 8]
# each_slice / each_cons
(1..10).each_slice(3).to_a # [[1,2,3],[4,5,6],[7,8,9],[10]]
(1..5).each_cons(3).to_a # [[1,2,3],[2,3,4],[3,4,5]]
# zip — pair arrays together
[1, 2, 3].zip([4, 5, 6]) # [[1,4],[2,5],[3,6]]
[1, 2, 3].zip([4, 5, 6]).map { |a, b| a + b } # [5, 7, 9]
# count / tally
[1, 2, 1, 3, 2, 1].tally # {1=>3, 2=>2, 3=>1}
[1, 2, 3, 4].count(&:even?) # 2
# min_by / max_by / minmax_by / sort_by
words.min_by(&:length) # "ant"
words.sort_by(&:length) # ascending by length
words.sort_by { |w| [-w.length, w] } # length desc, alpha asc
# any? / all? / none? / one?
[1, 2, 3].any? { |n| n > 2 } # true
[1, 2, 3].all?(&:positive?) # true
[1, 2, 3].none?(&:negative?) # true
# find / detect — first match
[1, 2, 3, 4].find { |n| n.even? } # 2
# chunk — consecutive elements
[1, 1, 2, 2, 3].chunk_while { |a, b| a == b }.to_a # [[1,1],[2,2],[3]]
# flat_map over nested data
users.flat_map { |u| u.tags } # all tags from all users, flattened
Hash patterns
# Create and access
h = { name: "Alice", age: 30 } # symbol keys (most common)
h[:name] # "Alice"
h.fetch(:name) # "Alice" — raises KeyError if missing
h.fetch(:email, "n/a") # "n/a" — default value
h.fetch(:email) { |k| "No #{k}" } # block for dynamic default
# Merge — second hash wins on conflict
h.merge({ age: 31, city: "London" }) # new hash
h.merge!(age: 31) # in-place
# merge with block — resolve conflicts
defaults.merge(overrides) { |key, old, new_val| old || new_val }
# Transform
h.transform_values { |v| v.to_s }
h.transform_keys(&:to_s) # symbol → string keys
h.map { |k, v| [k, v * 2] }.to_h # equivalent to transform_values
# Filter
h.select { |k, v| v.is_a?(Integer) }
h.reject { |k, v| v.nil? }
h.filter_map { |k, v| [k, v * 2] if v.is_a?(Integer) }
# dig — safe nested access (no KeyError)
data = { user: { address: { city: "London" } } }
data.dig(:user, :address, :city) # "London"
data.dig(:user, :phone, :number) # nil (not raise)
# each_with_object to build hash
[[:a, 1], [:b, 2]].each_with_object({}) { |(k, v), h| h[k] = v }
# Group and count
words.tally # {"ant"=>1, "bear"=>1, ...}
# Default values
counts = Hash.new(0) # default 0 for missing keys
words.each { |w| counts[w] += 1 }
# to_a, from array
[[:a, 1], [:b, 2]].to_h # {a: 1, b: 2}
h.to_a # [[:name, "Alice"], [:age, 30]]
# Destructuring in block
h.each { |key, value| puts "#{key}: #{value}" }
h.each { |(k, v)| puts "#{k}: #{v}" } # equivalent
String methods
# Basic
"hello world".upcase # "HELLO WORLD"
"Hello World".downcase # "hello world"
"hello world".capitalize # "Hello world"
"Hello World".swapcase # "hELLO wORLD"
# Strip whitespace
" hello ".strip # "hello"
" hello ".lstrip # "hello "
" hello ".rstrip # " hello"
"\nhello\n".chomp # "\nhello" — remove trailing \n/\r\n
"hello\n".chop # "hell" — unconditionally remove last char
# Split and join
"a,b,c".split(",") # ["a", "b", "c"]
"hello world".split # ["hello", "world"] (on whitespace)
"hello world".split(" ", 2) # ["hello", "world"] (max 2 parts)
["a", "b", "c"].join(", ") # "a, b, c"
# Include / start / end
"hello world".include?("world") # true
"hello".start_with?("he") # true
"hello".end_with?("lo") # true
# Find and replace
"hello world".gsub("o", "0") # "hell0 w0rld" (all)
"hello world".sub("o", "0") # "hell0 world" (first)
"hello world".gsub(/\w+/) { |w| w.capitalize } # "Hello World"
"price: $10".gsub(/\$(\d+)/) { "$#{$1.to_i * 2}" } # "price: $20"
# Slice / index
"hello"[0] # "h"
"hello"[1..3] # "ell"
"hello"[-2..] # "lo"
"hello"[1, 3] # "ell" (start, length)
# Format
"Hello, %s! You are %d." % ["Alice", 30] # positional
"%-10s | %5d" % ["item", 42] # padding
# Encoding and conversion
"hello".bytes # [104, 101, 108, 108, 111]
"hello".chars # ["h", "e", "l", "l", "o"]
42.to_s # "42"
"42".to_i # 42
"3.14".to_f # 3.14
"0xff".to_i(16) # 255
# String multiplication
"ha" * 3 # "hahaha"
"-" * 40 # "----------------------------------------"
# Heredoc
text = <<~HEREDOC
Line one
Line two
HEREDOC
Classes, Modules, Mixins
# Class basics
class Animal
attr_accessor :name, :age # getter and setter
attr_reader :species # getter only
attr_writer :nickname # setter only
@@count = 0 # class variable (shared across all instances)
def initialize(name, species)
@name = name # instance variable
@species = species
@@count += 1
end
def self.count # class method
@@count
end
def to_s # string representation
"#{@name} (#{@species})"
end
def <=>(other) # comparison for Comparable
@name <=> other.name
end
end
# Inheritance
class Dog < Animal
def initialize(name)
super(name, "dog") # call parent initialize
end
def speak
"Woof!"
end
end
# Modules — namespace OR mixin
module Greetable
def greet
"Hello, I'm #{name}" # expects name method on including class
end
end
module Trackable
def track
"Tracking #{name} at #{Time.now}"
end
end
class Person
include Greetable # adds instance methods
include Trackable
extend Greetable # adds class methods instead
prepend LoggingModule # inserted BEFORE the class in lookup chain
attr_reader :name
def initialize(name)
@name = name
end
end
# Comparable mixin — define <=> once, get <, >, <=, >=, between?, clamp
class Temperature
include Comparable
attr_reader :degrees
def initialize(degrees)
@degrees = degrees
end
def <=>(other)
degrees <=> other.degrees
end
end
temps = [Temperature.new(30), Temperature.new(20), Temperature.new(25)]
temps.min # 20°
temps.sort # [20, 25, 30]
# Struct — quick data class
Point = Struct.new(:x, :y)
p = Point.new(1, 2)
p.x # 1
# Data — immutable value object (Ruby 3.2+)
Measurement = Data.define(:value, :unit)
m = Measurement.new(value: 100, unit: :kg)
m.value # 100
# Method visibility
class Example
def public_method; end # public by default
protected
def protected_method; end # accessible to instances of same class/subclass
private
def private_method; end # only accessible within the instance
private :existing_method # make existing method private
end
Error handling
# begin / rescue / ensure / else
begin
result = risky_operation
rescue ArgumentError => e
puts "Bad argument: #{e.message}"
rescue TypeError, ValueError => e
puts "Type or value error: #{e.message}"
rescue StandardError => e # catch-all for most errors
puts "Error: #{e.message}"
puts e.backtrace.first(5)
else
puts "Success: #{result}" # runs ONLY if no exception
ensure
cleanup() # ALWAYS runs (like finally)
end
# Retry with counter
attempts = 0
begin
attempts += 1
connect_to_database
rescue ConnectionError => e
retry if attempts < 3
raise # re-raise after 3 attempts
end
# Custom exceptions
class AppError < StandardError; end
class ValidationError < AppError
attr_reader :field
def initialize(msg = "Validation failed", field: nil)
@field = field
super(msg)
end
end
raise ValidationError.new("Invalid email", field: :email)
# rescue in method body (no begin/end needed)
def safe_divide(a, b)
a / b
rescue ZeroDivisionError
nil
end
# raise vs raise without args
begin
do_something
rescue StandardError
log_error
raise # re-raise the same exception with same backtrace
end
# Kernel#raise shortcuts
raise ArgumentError, "message" # same as raise ArgumentError.new("message")
raise "something went wrong" # RuntimeError
fail "something went wrong" # alias for raise
# Exception hierarchy worth knowing:
# Exception
# └── StandardError (rescue catches this and below)
# ├── RuntimeError (raise "msg" default)
# ├── ArgumentError
# ├── TypeError
# ├── NameError → NoMethodError
# ├── IOError → Errno::* (ENOENT, EACCES...)
# └── ...
# ├── ScriptError (SyntaxError, LoadError)
# └── SignalException (Interrupt from Ctrl+C)
File I/O and common stdlib
# File reading
content = File.read("file.txt")
lines = File.readlines("file.txt", chomp: true) # no trailing \n
File.open("file.txt") do |f|
f.each_line { |line| process(line) }
end # auto-closes on block exit
# File writing
File.write("out.txt", "content") # overwrite
File.write("out.txt", "more\n", mode: "a") # append
File.open("out.txt", "w") do |f|
f.puts "line one"
f.puts "line two"
end
# Path operations (use Pathname for convenience)
require "pathname"
path = Pathname.new("/var/log/app.log")
path.exist?
path.dirname # Pathname "/var/log"
path.basename # Pathname "app.log"
path.extname # ".log"
path.stem # "app" (basename without extension)
path.read # file contents
# File utilities
require "fileutils"
FileUtils.mkdir_p("path/to/dir") # mkdir -p
FileUtils.cp("src", "dst")
FileUtils.cp_r("src_dir", "dst_dir") # recursive copy
FileUtils.mv("old", "new")
FileUtils.rm_rf("dir") # rm -rf — CAREFUL
# Temporary file
require "tempfile"
Tempfile.create("prefix") do |f|
f.write("data")
f.rewind
f.read
end # auto-deleted on block exit
# JSON
require "json"
JSON.parse('{"name":"Alice","age":30}') # → Hash
JSON.generate({ name: "Alice", age: 30 }) # → String
JSON.pretty_generate(data) # formatted
# CSV
require "csv"
CSV.foreach("data.csv", headers: true) do |row|
puts row["name"]
end
CSV.open("out.csv", "w") do |csv|
csv << ["name", "age"]
csv << ["Alice", 30]
end
# HTTP (net/http)
require "net/http"
require "uri"
uri = URI("https://api.example.com/users")
response = Net::HTTP.get_response(uri)
data = JSON.parse(response.body)
# Open3 — run shell commands
require "open3"
stdout, stderr, status = Open3.capture3("ls", "-la")
raise "Failed: #{stderr}" unless status.success?
# Time
Time.now # current time
Time.now.utc # UTC
Time.now.strftime("%Y-%m-%d") # "2026-03-14"
Time.parse("2026-01-01") # parse string (require 'time')
(Time.now - 3600).iso8601 # 1 hour ago, ISO 8601
Bundler and Gems
# Gemfile
source "https://rubygems.org"
ruby "3.3.0"
gem "rails", "~> 7.1" # >= 7.1.0, < 7.2.0
gem "pg", ">= 1.5"
gem "puma", "~> 6.0"
group :development, :test do
gem "rspec-rails"
gem "factory_bot_rails"
gem "faker"
end
group :development do
gem "rubocop", require: false
gem "solargraph"
end
group :test do
gem "capybara"
gem "shoulda-matchers"
end
# Bundle commands
bundle install # install all gems
bundle install --without production # skip group
bundle update rails # update single gem
bundle update # update all (careful!)
bundle exec rspec # run command in gem context
bundle exec rails server
# Gemfile.lock — always commit this for apps, .gitignore for libraries
bundle lock --update BUNDLER # update bundler itself
# Version operators
# = 1.0.0 exactly
# != 1.0.0 not this version
# > 1.0.0 greater than
# >= 1.0.0 at least
# < 2.0.0 less than
# ~> 1.0.0 pessimistic constraint (>= 1.0.0, < 1.1.0)
# ~> 1.0 pessimistic constraint (>= 1.0, < 2.0)
# rubocop — Ruby linter
bundle exec rubocop # check
bundle exec rubocop -a # auto-correct safe offenses
bundle exec rubocop -A # auto-correct all (including unsafe)
# gem build / publish
gem build mylib.gemspec
gem push mylib-1.0.0.gem # requires rubygems.org account
gem yank mylib -v 1.0.0 # retract a version
Track Ruby EOL dates and releases at ReleaseRun.
🔍 Free tool: Ruby Gems Health Checker — check any Ruby gem for latest version and active maintenance before adding to your Gemfile.
🔍 Free tool: Gemfile Batch Health Checker — paste your Gemfile.lock and get health grades for all gems at once.
Founded
2023 in London, UK
Contact
hello@releaserun.com