Introduction
Blocks are one of Ruby's most distinctive features. They let you pass a chunk of code to a method, which can then execute that code with yield. Blocks power iteration methods like each and map, configuration DSLs, and resource management patterns. Understanding blocks is essential because they appear in virtually every Ruby program.
Key Concepts
- Block: A chunk of code enclosed in
do...endor{...}that you pass to a method. Blocks are not objects themselves, but can be captured as Procs. yield: A keyword that executes the block passed to the current method. It can pass arguments to the block and receive a return value.block_given?: A method that returnstrueif a block was passed to the current method, allowing you to write methods with optional block behavior.
Real World Context
In a Rails application, blocks are everywhere. Database transactions wrap operations in a block (ActiveRecord::Base.transaction { ... }), file handling ensures cleanup (File.open('log.txt') { |f| f.read }), and testing frameworks use blocks to define test cases (it 'works' { expect(result).to eq(42) }). Every time you use each, map, or select, you are passing a block.
Deep Dive
Blocks are chunks of code you can pass to methods. They are enclosed in do...end or {...}.
Here is the basic syntax for passing a block to a method:
ruby[1, 2, 3].each do |n| puts n end # Single-line style [1, 2, 3].each { |n| puts n }
By convention, do...end is used for multi-line blocks and {...} for single-line blocks.
Writing Methods That Accept Blocks
Use yield to execute the block passed to your method:
rubydef wrap puts "Before" yield # Execute the block puts "After" end wrap { puts "Inside" } # Output: # Before # Inside # After
The yield keyword transfers control to the block, runs it, and then returns control to the method. This pattern is the foundation of Ruby's iterator design.
Passing Arguments to Blocks
You can pass values from your method into the block via yield:
rubydef repeat(times) times.times do |i| yield i end end repeat(3) { |n| puts "Iteration #{n}" }
The value passed to yield becomes the block parameter (the variable between the pipes).
The it Keyword (Ruby 3.4+)
Ruby 3.4 introduced it as a default block parameter for single-argument blocks, providing a concise alternative to numbered parameters (_1) or explicit names:
ruby# Traditional [1, 2, 3].map { |n| n * 2 } # => [2, 4, 6] # With numbered parameter (Ruby 2.7+) [1, 2, 3].map { _1 * 2 } # => [2, 4, 6] # With it (Ruby 3.4+) [1, 2, 3].map { it * 2 } # => [2, 4, 6]
The it keyword reads naturally and is ideal for simple one-argument blocks. It can only be used in blocks with no explicit parameters defined — you cannot mix it with named or numbered parameters.
Checking if a Block Was Given
Use block_given? to make block usage optional in your methods:
rubydef maybe_yield if block_given? yield else puts "No block provided" end end
This pattern lets you write methods that behave differently depending on whether the caller provided a block, which is common in Ruby's standard library.
Common Pitfalls
- Calling
yieldwithout a block — If you callyieldand no block was given, Ruby raises aLocalJumpError. Always guard withblock_given?if the block is optional. - Confusing
do...endand{...}precedence —method arg { ... }binds the block toarg, whilemethod arg do ... endbinds it tomethod. This matters when passing blocks to methods with arguments.
Best Practices
- Use
{...}for single-line blocks anddo...endfor multi-line — This convention is nearly universal in the Ruby community and makes code immediately scannable. - Guard with
block_given?for optional blocks — If your method should work both with and without a block, always checkblock_given?before callingyield.
Summary
- Blocks are chunks of code passed to methods, enclosed in
do...endor{...}. - The
yieldkeyword executes the block from within the method, optionally passing arguments. - Use
block_given?to make block usage optional and avoidLocalJumpError.
Code Examples
# Writing a method that accepts a block
def wrap
puts "Before"
yield # Execute the block
puts "After"
end
wrap { puts "Inside" }
# Output:
# Before
# Inside
# After
# Checking if a block was given
def maybe_yield
if block_given?
yield
else
puts "No block provided"
end
end