Ruby Blocks - How to pass and Execute Code
Ruby blocks are one of my favorite Ruby features. When I was new to Ruby, understanding blocks was a bit confusing since they didn’t seem to work like the rest of the language, but they’re actually quite straightforward once you grok them.
What Are Ruby Blocks?
“A code block is a set of Ruby statements and expressions inside braces or a do/end pair.” – Pickaxe (aka Programming Ruby 3.3)
Simply put, blocks are chunks of code you can pass around and execute. Basic Block Syntax
Blocks can be written in two primary ways:
- Using braces { } for single-line blocks
- Using do…end for multi-line blocks
Here’s a simple single line example:
[1, 2, 3, 4, 5].each { |n| puts n.even? }
#=> [false, true, false, true, false]
Or a multiline example with do…end:
[1, 2, 3, 4, 5].each do |n|
puts n.even?
end
#=>
false
true
false
true
false
How Blocks Work
Blocks are commonly used with methods that enumerate over collections. Ruby’s standard library is full of methods that leverage blocks:
.map transforms each element in a collection and returns and array
y = [1, 2, 3, 4, 5].map { |num| num.odd? }
y # [true, false, true, false, true]
.times repeats an action the requested number of times.
2.times { puts "." }
.each iterates over elements one by one and then executes whatever code is in the associated block without returning the original ennumerable.
[1, 2, 3, 4, 5].each { |n| puts "The number is #{n}" }
The number is 1
The number is 2
The number is 3
The number is 4
The number is 5
Passing Arguments to Blocks
Blocks can receive arguments from the method that yields to them:
def example_method(value)
yield(value)
end
example_method(12) { |x| puts "Received: #{x}" }
# Prints: Received: 12
Block to Proc Conversion
Ruby allows you to convert blocks to Proc objects and vice versa using the & operator. This happens when you accept a block as a method argument.
# Convert a block to a Proc
def method_with_block(a, b, &block)
block.call(a, b)
puts block.class
end
method_with_block(1, 2){|x, y| puts x + y}
#=>
3
Proc
As you can see the 1 and 2 are added to give us 3 which is returned then the “puts block.class” line reveals that the block has actually been converted to a Proc object. That happened upon passing the block into the method at “&block”.
You can also create a Proc object directly if you want like this:
proc_object = Proc.new { puts "Hello world" }
proc_object.call
#=>
Hello world
So what I have show is that you can convert a block to a Proc object by putting an & in front of it. Any method that accept as block as an argument ends it’s arguments with a &block exactly for this reason. It needs to convert the block to a Proc object so that the Proc (formerly a block) can be assigned to a variable name. Example:
def some_method(a, b, &c)
puts a
c.call
puts "c is a #{c.class}"
puts b
end
some_method(1, 2){ puts "block in the middle" }
#=>
1
block in the middle
c is a Proc
2
We know that a block is converted into a Proc when it is passed into a method since that is the way that it is stored on instance variables. But that doesn’t work that way with yield. For example:
def yield_method
puts "yield is the class: #{yield.class}"
end
yield_method{ puts "Something here" }
#=>
Something here
yield is the class: NilClass
See? We get NilClass from yield.class in the above example because the .class method is being called on the returned value of puts (which is nil).
Another example of yield not being a Proc:
def yield_method
puts "yield is the class: #{yield.class}"
end
yield_method{ "Something here" }
#=>
yield is the class: String
In this case we get a class of String because a string is returned from the block.
Tap
The tap method is a utility method in Ruby meant for you to check on the values of an object without disrupting a method chain and while not altering the return value. It’s meant for debugging or logging while working with a object with lots of chained methods.
Some methods like tap use blocks in unique ways:
[1, 2, 3].tap { |arr| puts "Inspecting: #{arr}" }
# Prints: Inspecting: [1, 2, 3]
# Still returns [1, 2, 3]
Key Takeaways
- Blocks are not objects, but can be converted to Proc objects
- Blocks are great for iteration, transformation, and creating flexible, reusable code
Ruby’s block system allows for incredibly expressive and concise code. By understanding blocks, you unlock a powerful programming paradigm that makes Ruby both elegant and efficient.