Ruby blocks are chunks of code you pass to methods. They're everywhere:
[1, 2, 3].map { |n| n * 2 } # [2, 4, 6]
The { |n| n * 2 } part is a block. It receives each element (n) and returns n * 2. The map method calls the block for each element, collecting the results.
Block Syntax
Blocks come in two forms:
Curly braces (single-line):
{ |param| expression }
do/end (multi-line):
do |param|
expression
end
By convention, use curly braces for one-liners and do/end for multi-line blocks:
# Single line
[1, 2, 3].each { |n| puts n }
# Multi-line
[1, 2, 3].each do |n|
squared = n * n
puts squared
end
How Blocks Work
Methods receive blocks implicitly. Inside a method, yield calls the block:
def my_method
puts "Before block"
yield
puts "After block"
end
my_method { puts "Inside block" }
# Output:
# Before block
# Inside block
# After block
You can pass arguments to blocks through yield:
def twice
yield(1)
yield(2)
end
twice { |n| puts n * 2 }
# Output:
# 2
# 4
Block Parameters
Blocks can take multiple parameters:
{ "a" => 1, "b" => 2 }.each { |key, value| puts "#{key}: #{value}" }
# Output:
# a: 1
# b: 2
The number of parameters depends on what the method yields.
Implicit Return
Blocks return the value of the last expression:
[1, 2, 3].map { |n| n * 2 } # [2, 4, 6]
# The block returns n * 2 implicitly
Explicit return exits the enclosing method, not just the block:
def example
[1, 2, 3].each do |n|
return "Found 2" if n == 2
end
"Not found"
end
example # "Found 2"
The return exits example, not just the block. To exit only the block, use next:
def example
[1, 2, 3].each do |n|
next if n == 2 # Skip to next iteration
puts n
end
end
example
# Output:
# 1
# 3
Common Iterator Methods
each - iterates without transforming:
[1, 2, 3].each { |n| puts n }
map - transforms each element:
[1, 2, 3].map { |n| n * 2 } # [2, 4, 6]
select - filters by condition:
[1, 2, 3, 4].select { |n| n.even? } # [2, 4]
reject - inverse of select:
[1, 2, 3, 4].reject { |n| n.even? } # [1, 3]
reduce - accumulates a result:
[1, 2, 3].reduce(0) { |sum, n| sum + n } # 6
Blocks vs Procs vs Lambdas
Ruby has three ways to create reusable code chunks:
Blocks - not objects, passed implicitly:
[1, 2, 3].map { |n| n * 2 }
Procs - objects, stored in variables:
doubler = Proc.new { |n| n * 2 }
[1, 2, 3].map(&doubler) # [2, 4, 6]
Lambdas - procs with stricter argument checking:
doubler = ->(n) { n * 2 }
[1, 2, 3].map(&doubler) # [2, 4, 6]
Key differences:
-
Return behavior:
returnin a lambda exits the lambda.returnin a proc exits the enclosing method. -
Argument checking: Lambdas raise errors for wrong argument counts. Procs ignore extra arguments or assign
nilto missing ones.
proc = Proc.new { |a, b| puts "#{a}, #{b}" }
proc.call(1) # "1, " (b is nil)
lam = ->(a, b) { puts "#{a}, #{b}" }
lam.call(1) # ArgumentError: wrong number of arguments
Converting Blocks to Procs
Methods can capture blocks as procs using &:
def my_method(&block)
block.call
end
my_method { puts "Hello" }
This converts the block to a proc, making it an object you can store or pass around.
Yielding with block_given?
Check if a block was passed before yielding:
def my_method
if block_given?
yield
else
puts "No block provided"
end
end
my_method # "No block provided"
my_method { puts "Block!" } # "Block!"
Practical Examples
Custom iteration:
def times_three
3.times { |i| yield(i) }
end
times_three { |n| puts n }
# Output: 0, 1, 2
Resource management:
def with_file(path)
file = File.open(path)
yield(file)
ensure
file.close if file
end
with_file("data.txt") do |f|
puts f.read
end
The file is automatically closed, even if the block raises an error.
Configuration DSL:
class Config
def initialize
yield(self) if block_given?
end
attr_accessor :name, :value
end
config = Config.new do |c|
c.name = "app"
c.value = 42
end
Stabby Lambda Syntax
Ruby 1.9 introduced -> syntax for lambdas:
# Traditional
add = lambda { |a, b| a + b }
# Stabby lambda
add = ->(a, b) { a + b }
Parameters go in parentheses before the block. Multi-line works too:
greet = ->(name) do
puts "Hello, #{name}!"
end
greet.call("Alice")
When to Use Each
Blocks - for passing behavior to methods:
[1, 2, 3].each { |n| puts n }
Procs - when you need to store code for later or pass it to multiple methods:
validator = Proc.new { |n| n > 0 }
positives = numbers.select(&validator)
all_positive = numbers.all?(&validator)
Lambdas - when you want strict argument checking and lambda-style returns:
operation = ->(a, b) { a + b }
result = operation.call(2, 3)
Performance
Blocks are slightly faster than procs/lambdas because they're not objects. For tight loops, this might matter:
# Faster
1000000.times { |i| i * 2 }
# Slightly slower
operation = ->(i) { i * 2 }
1000000.times(&operation)
But the difference is negligible in most code.
Further Reading
The Ruby documentation on Proc covers procs and lambdas in detail.
Paolo Perrotta's Metaprogramming Ruby has excellent chapters on blocks, closures, and the different callable objects in Ruby.
The Ruby Style Guide's section on blocks provides style conventions.
Blocks are central to Ruby's expressiveness.
0 comments