Building Blocks: A Fundamental Part of Ruby
After disappearing into the world of JavaScript for a quite a spell, I found I needed to go back and review how Ruby blocks work. At this point I hadn’t seen a good, freely accessible explanation for how blocks worked so I thought it would be nice to do a bit of a write up.1,
Here are the two points I’m going to try and explain:
- How blocks work at a basic level.
- How to create methods that use blocks.
Block basics
Below is a common use case for blocks - calling a block on a Ruby standard library methods such as each
.
>[1, 2, 3].each do |num|
puts num
end
1,2,3
There are three parts to this method call:
[1,2,3]
- a collection of elements that implements Enumerable, such as an array or hash.each
- a method that can utilize a blockdo...end
- everything withindo ... end
is the block that is passed to the method
do |num|
puts num
end
So that’s the anatomy of a block in action, but the simple syntax of Ruby seems to be hiding some details from us. How exactly are these three parts of a block call working together?
Methods, blocks, and yields
All Ruby methods can technically take a bloc. Here’s the simplest example of a method taking a block.
def hello_world
puts "Hello world"
end
hello_world { puts "World replies: Hey!" }
> "Hello world"
Hmm? So while our hello_world
method can take a block, it’s not exactly doing anything with it. Let’s start changing that:
def hello_world_conversation
puts "Hello world"
yield
end
> hello_world_conversation { puts "World replies: Hey!" }
"Hello world"
"World replies: Hey!"
If you aren’t familiar with the Ruby yield
keyword, it’s in effect saying this: “Hey, at this point in my method I want a block to run. I don’t really know what block, blocks are like a parameter so I can’t know the exact contents ahead of time, but if someone wants to pass me a block when they call the method hello_world_conversation
, that’s cool.”
Passing parameters to a block
Another important point to using method and blocks is that methods can pass parameters to blocks through yield()
.
def hello_world_conversation(reply)
puts "Hello world"
yield(reply)
end
> hello_world_conversation('Goodbye!') do |reply|
puts "World replies: #{reply}"
end
"Hello world"
"World replies: Goodbye!"
To summarize, our method takes a parameter, yields to the block, the block prints out a statement using that argument string. Now that we’re familiar with some of the building “blocks” (pardon the pun), let’s move on to using iteration with blocks.
Iteration is the name of the game
What if we wanted the our “World” entity to reply a couple of times? The basics are already there - we just need to figure out how to do iteration inside our method.
> replies = [ 'Hey!',
'How\'s it going?',
'Code here often?',
'Goodbye!' ];
> hello_world_conversation(replies) do |reply|
replies.length.times do |index|
puts "World replies: #{replies[index]}"
end
end
"Hello world"
"World replies: Hey!"
"World replies: How's it going?!"
"World replies: Code here often?"
"World replies: Goodbye!"
This is pretty cool, but we should probably think about what the relationship should be between our method and our block. Right now our method takes in parameters and the block iterates over them and decides how to print. Since each time this method is called it will iterate through the conversation, let’s move that logic into the method and out of the block.
def hello_world_conversation(replies)
puts "Hello world"
replies.length.times do |index|
yield(replies[index])
end
end
> hello_world_conversation(replies) do |reply|
puts "World replies: #{reply}"
end
Ok, believe it or not while trying to add some simple bits of additional functionality to our hello_world_conversation
method, we more or less reimplemented how each
works!
> replies.each { |reply| puts "World replies #{reply}"}
"World replies: Hey!"
"World replies: How's it going?!"
"World replies: Code here often?"
"World replies: Goodbye!"
=> ['Hey!', 'How\'s it going?', 'Code here often?', 'Goodbye!' ]
Note that the code above does not print out "Hello World"
.
If the Enumerable#each
method was ever confusing to you, I think what we learned so far gives us a good framework for describing how each
works internally. Let’s go over the high level steps below:
each
starts iterating through the of the items in the collection (replies
)- it yields the current element to the block
- returns the original collection
If you’ve never tried before, I would go ahead and try and code up your own version of Enumerable#each
. For simplicity’s sake, I would recommend just passing in the collection to iterate over like this: each(collection)
. If you need some inspiration, I have another post goes over more complicated Enumerable
methods here.
There’s a lot more you can do with blocks, but hopefully this above covered some of the basics. I’ve found it’s often not a bad place to start. :)