Blocks
Prerequisites: Variable scope
A block is a chunk of code you pass to a method, which the method
can run whenever it likes. You've already used them with each and
times. They're the most distinctive feature of Ruby.
Real-world analogy: a block is an instruction slip you hand to someone along with a job — "for each parcel you deliver, text me the address". The courier (the method) decides when to follow your slip.
Syntax
method_name { |parameter| code }
method_name do |parameter|
code
end
Writing a method that takes a block
Inside a method, yield runs the block that was passed to it:
def twice
yield
yield
end
twice { puts "Hi!" }
Hi!
Hi!
yield can pass values into the block, and receive the block's result
back:
def with_logging(task)
puts "Starting #{task}"
result = yield(task)
puts "Finished #{task}"
result
end
value = with_logging("sum") { |name| [1, 2, 3].sum }
p value
Starting sum
Finished sum
6
This "do something before and after" pattern is how Ruby opens files,
database transactions, and timers safely — you'll see it with
File.open in Level 2.
block_given? checks whether a block was passed, so a method can make
the block optional.
The &:method shortcut
When a block only calls one method on its argument, there's a shorthand:
words = ["ruby", "rails"]
p words.map { |w| w.upcase }
p words.map(&:upcase)
["RUBY", "RAILS"]
["RUBY", "RAILS"]
Ruby 3.4 also lets you refer to a one-argument block's parameter as
it: words.map { it.upcase }.
Common mistakes
- Calling
yieldwhen no block was passed:no block given (yield) (LocalJumpError). Guard it withblock_given?. - Putting a
{ }block on a method call without parentheses around its arguments —puts [1, 2].map { |n| n * 2 }works, but mixing styles in complex calls can bind the block to the wrong method. Add parentheses when in doubt.
Practice
Write a method repeat(n) that runs its block n times, passing the
current pass number starting at 1. Use it to print Pass 1 to
Pass 3.
Show solution
def repeat(n)
1.upto(n) { |i| yield i }
end
repeat(3) { |i| puts "Pass #{i}" }