Using blocks in Ruby
With this excerpt from Head First Ruby, you’ll learn about Ruby blocks by looking at each concept from different angles.
A block is a chunk of code that you associate with a method call. While the method runs, it can invoke (execute) the block one or more times. Methods and blocks work in tandem to process your data. Blocks are a way of encapsulating or packaging statements up and using them wherever you need them. They turn up all over Ruby code.
Blocks are mind-bending stuff. But stick with it!
Even if you’ve programmed in other languages, you’ve probably never seen anything like blocks. But stick with it, because the payoff is big.
Imagine if, for all the methods you have to write for the rest of your career, someone else wrote half of the code for you. For free. They’d write all the tedious stuff at the beginning and end, and just leave a little blank space in the middle for you to insert your code, the clever code, the code that runs your business.
If we told you that blocks can give you that, you’d be willing to do whatever it takes to learn them, right?
Well, here’s what you’ll have to do: be patient, and persistent. We’re here to help. We’ll look at each concept repeatedly, from different angles. We’ll provide exercises for practice. Make sure to do them, because they’ll help you understand and remember how blocks work.
A few hours of hard work now are going to pay dividends for the rest of your Ruby career, we promise. Let’s get to it!
Defining a method that takes blocks
Blocks and methods work in tandem. In fact, you can’t have a block without also having a method to accept it. So, to start, let’s define a method that works with blocks.
(On this page, we’re going to show you how to use an ampersand,
&, to accept a block, and the
call method to call that block. This isn’t the quickest way to work with blocks, but it does make it more obvious what’s going on. We’ll show you
yield, which is more commonly used, in a few pages!)
Since we’re just starting off, we’ll keep it simple. The method will print a message, invoke the block it received, and print another message.
If you place an ampersand before the last parameter in a method definition, Ruby will expect a block to be attached to any call to that method. It will take the block, convert it to an object, and store it in that parameter.
Remember, a block is just a chunk of code that you pass into a method. To execute that code, stored blocks have a
call instance method that you can call on them. The
call method invokes the block’s code.
Okay, we know, you still haven’t seen an actual block, and you’re going crazy wondering what they look like. Now that the setup’s out of the way, we can show you…
Your first block
Are you ready? Here it comes: your first glimpse of a Ruby block.
There it is! Like we said, a block is just a chunk of code that you pass to a method. We invoke
my_method, which we just defined, and then place a block immediately following it. The method will receive the block in its
The start of the block is marked with the keyword
do, and the end is marked by the keyword
The block body consists of one or more lines of Ruby code between
end. You can place any code you like here.
When the block is called from the method, the code in the block body will be executed.
After the block runs, control returns to the method that invoked it.
So we can call my_method and pass it the above block. We pass my_method a single argument, @my_block, so we can refer to the block inside the method.
…and here’s the output we’d see:
Flow of control between a method and block
We declared a method named
my_method, called it with a block, and got this output:
Let’s break down what happened in the method and block, step by step.
my_method’s body runs.
def my_method(&my_block) puts "We're in the method, about to invoke your block!" my_block.call puts "We're back in the method!" end
my_block.callexpression runs, and control is passed to the block. The
putsexpression in the block’s body runs.
When the statements within the block body have all run, control returns to the method. The second call to
my_method’s body runs, and then the method returns.
Calling the same method with different blocks
You can pass many different blocks to a single method.
We can pass different blocks to the method we just defined, and do different things:
The code in the method is always the same, but you can change the code you provide in the block.
Calling a block multiple times
A method can invoke a block as many times as it wants.
This method is just like our previous one, except that it has two
The method name is appropriate: as you can see from the output, the method does indeed call our block twice!
Statements in the method body run until the first
my_block.callexpression is encountered. The block is then run. When it completes, control returns to the method.
The method body resumes running. When the second
my_block.callexpression is encountered, the block is run again. When it completes, control returns to the method so that any remaining statements there can run.
We learned back in Chapter 2 that when defining a Ruby method, you can specify that it will accept one or more parameters:
def print_parameters(p1, p2) puts p1, p2 end
You’re probably also aware that you can pass arguments when calling the method that will determine the value of those parameters.
In a similar vein, a method can pass one or more arguments to a block. Block parameters are similar to method parameters; they’re values that are passed in when the block is run, and that can be accessed within the block body.
call get forwarded on to the block:
You can have a block accept one or more parameters from the method by defining them between vertical bar (
|) characters at the start of the block:
So, when we call our method and provide a block, the arguments to
call are passed into the block as parameters, which then get printed. When the block completes, control returns to the method, as normal.
Using the “yield” keyword
So far, we’ve been treating blocks like an argument to our methods. We’ve been declaring an extra method parameter that takes a block as an object, then using the
call method on that object.
def twice(&my_block) my_block.call my_block.call end
We mentioned that this wasn’t the easiest way to accept blocks, though. Now, let’s learn the less obvious but more concise way: the
yield keyword will find and invoke the block a method was called with—there’s no need to declare a parameter to accept the block.
This method is functionally equivalent to the one above:
def twice yield yield end
Just like with
call, we can also give one or more arguments to
yield, which will be passed to the block as parameters. Again, these methods are functionally equivalent:
def give(&my_block) my_block.call("2 turtle doves", "1 partridge") end def give yield "2 turtle doves", "1 partridge" end
So far, we’ve been using the
do...end format for blocks. Ruby has a second block format, though: “curly brace” style. You’ll see both formats being used “in the wild,” so you should learn to recognize both.
end being replaced with curly braces, the syntax and functionality are identical.
And just as
do...end blocks can accept parameters, so can curly-brace blocks:
By the way, you’ve probably noticed that all our
do...end blocks span multiple lines, but our curly-brace blocks all appear on a single line. This follows another convention that much of the Ruby community has adopted. It’s valid syntax to do it the other way:
But not only is that out of line with the convention, it’s really ugly.
The “each” method
We had a lot to learn in order to get here: how to write a block, how a method calls a block, how a method can pass parameters to a block. And now, it’s finally time to take a good, long look at the method that will let us get rid of that repeated loop code in our
show_discounts methods. It’s an instance method that appears on every
Array object, and it’s called
You’ve seen that a method can yield to a block more than once, with different values each time:
each method uses this feature of Ruby to loop through each of the items in an array, yielding them to a block, one at a time.
If we were to write our own method that works like
each, it would look very similar to the code we’ve been writing all along:
We loop through each element in the array, just like in our
show_discounts methods. The key difference is that instead of putting code to process the current array element in the middle of the loop, we use the
yield keyword to pass the element to a block.
The “each” method, step-by-step
We’re using the
each method and a block to process each of the items in an array:
Let’s go step-by-step through each of the calls to the block and see what it’s doing.
For the first pass through the
indexis set to
0, so the first element of the array gets yielded to the block as a parameter. In the block body, the parameter gets printed. Then control returns to the method,
indexgets incremented, and the
Now, on the second pass through the
indexis set to
1, so the second element in the array will be yielded to the block as a parameter. As before, the block body prints the parameter, control then returns to the method, and the loop continues.
After the third array element gets yielded to the block for printing and control returns to the method, the
whileloop ends, because we’ve reached the end of the array. No more loop iterations means no more calls to the block; we’re done!
That’s it! We’ve found a method that can handle the repeated looping code, and yet allows us to run our own code in the middle of the loop (using a block). Let’s put it to use!
DRYing up our code with “each” and blocks
Our invoicing system requires us to implement these three methods. All three of them have nearly identical code for looping through the contents of an array.
It’s been difficult to get rid of that duplication, though, because all three methods have different code in the middle of that loop.
But now we’ve finally mastered the
each method, which loops over the elements in an array and passes them to a block for processing.
Let’s see if we can use
each to refactor our three methods and eliminate the duplication.
First up for refactoring is the
total method. Just like the others, it contains code for looping over prices stored in an array. In the middle of that looping code,
total adds the current price to a total amount.
each method looks like it will be perfect for getting rid of the repeated looping code! We can just take the code in the middle that adds to the total, and place it in a block that’s passed to
Let’s redefine our
total method to utilize
each, then try it out.
Perfect! There’s our total amount. The
each method worked!
For each element in the array,
each passes it as a parameter to the block. The code in the block adds the current array element to the
amount variable, and then control returns back to
We’ve successfully refactored the
But before we move on to the other two methods, let’s take a closer look at how that
amount variable interacts with the block.
Blocks and variable scope
We should point something out about our new
total method. Did you notice that we use the
amount variable both inside and outside the block?
def total(prices) amount = 0 prices.each do |price| amount += price end amount end
As you may remember from Chapter 2, the scope of local variables defined within a method is limited to the body of that method. You can’t access variables that are local to the method from outside the method.
The same is true of blocks, if you define the variable for the first time inside the block.
But, if you define a variable before a block, you can access it inside the block body. You can also continue to access it after the block ends!
Since Ruby blocks can access variables declared outside the block body, our
total method is able to use
each with a block to update the
def total(prices) amount = 0 prices.each do |price| amount += price end amount end
We can call
total like this:
total([3.99, 25.00, 8.99])
amount variable is set to
0, and then
each is called on the array. Each of the values in the array is passed to the block. Each time the block is called,
amount is updated:
each method completes,
amount is still set to that final value,
37.98. It’s that value that gets returned from the method.
Using “each” with the “refund” method
We’ve revised the
total method to get rid of the repeated loop code. We need to do the same with the
show_discounts methods, and then we’ll be done!
The process of updating the
refund method is very similar to the process we used for
total. We simply take the specialized code from the middle of the generic loop code, and move it to a block that’s passed to
Much cleaner, and calls to the method still work just the same as before!
Within the call to
each and the block, the flow of control looks very similar to what we saw in the
Using “each” with our last method
One more method, and we’re done! Again, with
show_discounts, it’s a matter of taking the code out of the middle of the loop and moving it into a block that’s passed to
Again, as far as users of your method are concerned, no one will notice you’ve changed a thing!
Here’s what the calls to the block look like:
Our complete invoicing methods
Save this code in a file named prices.rb. Then try running it from the terminal!
We’ve gotten rid of the repetitive loop code!
We’ve done it! We’ve refactored the repetitive loop code out of our methods! We were able to move the portion of the code that differed into blocks, and rely on a method,
each, to replace the code that remained the same!
Utilities and appliances, blocks and methods
Imagine two electric appliances: a mixer and a drill. They have very different jobs: one is used for baking, the other for carpentry. And yet they have a very similar need: electricity.
Now, imagine a world where, any time you wanted to use an electric mixer or drill, you had to wire your appliance into the power grid yourself. Sounds tedious (and fairly dangerous), right?
That’s why, when your house was built, an electrician came and installed power outlets in every room. The outlets provide the same utility (electricity) through the same interface (an electric plug) to very different appliances.
The electrician doesn’t know the details of how your mixer or drill works, and he doesn’t care. He just uses his skills and training to get the current safely from the electric grid to the outlet.
Likewise, the designers of your appliances don’t have to know how to wire a home for electricity. They only need to know how to take power from an outlet and use it to make their devices operate.
You can think of the author of a method that takes a block as being kind of like an electrician. They don’t know how the block works, and they don’t care. They just use their knowledge of a problem (say, looping through an array’s elements) to get the necessary data to the block.
def wire yield "current" end
You can think of calling a method with a block as being kind of like plugging an appliance into an outlet. Like the outlet supplying power, the block parameters offer a safe, consistent interface for the method to supply data to your block. Your block doesn’t have to worry about how the data got there, it just has to process the parameters it’s been handed.
Not every appliance uses electricity, of course; some require other utilities. There are stoves and furnaces that require gas. There are automatic sprinklers and spray nozzles that use water.
Just as there are many kinds of utilities to supply many kinds of appliances, there are many methods in Ruby that supply data to blocks. The
each method was just the beginning. Blocks, also sometimes known as lambdas, are crucial components of Ruby. They are used in loops, in functions that have to run code at some future time (known as callbacks), and other contexts.