It seems like more and more languages these days are getting support for closures in one form or another. (Even Java is getting in on the game, finally.) Ruby has had closure-like structures called blocks since its early days, though, and they’re central to the language. Used properly, they can reduce repetition and even make coding less error-prone. Understanding blocks can give you some great ideas to take home to your language of choice. (Or, who knows? Maybe you’ll decide you like coding in Ruby better!)

Today, we’re going to show you just one of the many Ruby methods that use blocks: each . We’ll show you some repetitive code that, in most languages, would be hard to refactor. But using each , we’ll quickly wring that repetition out.

The repeating loop

Let’s say a client has asked us to implement an invoicing system for them. The first requested feature is the ability to take all the prices on an order and total them.

Our client’s code generates an array of item prices for an invoice:

item_prices = [3, 24, 9] 1 item_prices = [ 3 , 24 , 9 ]

We need to define a method that takes such an array, and totals the prices. As in most other languages, we could just loop through the array items, and add them to a variable’s value as we go.

def total(prices) amount = 0 index = 0 while index < prices.length amount += prices[index] index += 1 end return amount end puts total(item_prices) 1 2 3 4 5 6 7 8 9 10 11 def total ( prices ) amount = 0 index = 0 while index < prices . length amount += prices [ index ] index += 1 end return amount end puts total ( item_prices )

Output:

36 1 36

The Ruby syntax you see here shouldn’t seem too strange if you’re coming from another language. The method definition is marked with def ... end . The loop is marked by while ... end ; we break out of it as soon as we pass the last index in the array. The += operator adds a value to the existing amount in a variable.

Now, we’ll need a second method that can process a refund for lost orders. It needs to loop through the invoice prices, and subtract each amount from the customer’s account balance.

def refund(prices) amount = 0 index = 0 while index < prices.length amount -= prices[index] index += 1 end amount end puts refund(item_prices) 1 2 3 4 5 6 7 8 9 10 11 def refund ( prices ) amount = 0 index = 0 while index < prices . length amount -= prices [ index ] index += 1 end amount end puts refund ( item_prices )

Output:

-36 1 - 36

The refund method looks highly similar to total ; we’re just working with negative values instead of positive ones.

We also need a third method that will reduce each item’s price by 1/3 and print the savings.

def show_discounts(prices) index = 0 while index < prices.length amount_off = prices[index] / 3 puts "Your discount: $#{amount_off}" index += 1 end end show_discounts(item_prices) 1 2 3 4 5 6 7 8 9 10 def show_discounts ( prices ) index = 0 while index < prices . length amount_off = prices [ index ] / 3 puts "Your discount: $#{amount_off}" index += 1 end end show_discounts ( item_prices )

Output:

Your discount: $1 Your discount: $8 Your discount: $3 1 2 3 Your discount : $ 1 Your discount : $ 8 Your discount : $ 3

Between these 3 methods, there’s a lot of duplicated code, and it all seems to be related to looping through the array of prices. It’s definitely a violation of the DRY (Don’t Repeat Yourself) principle. It would be nice if we could extract the repeated code out into another method, and have total , refund , and show_discounts call it…

def do_something_with_every_item(array) # Set up total or refund variables here, # IF we need them. index = 0 while index < array.length # Add to the total OR # Add to the refund OR # Show the discount index += 1 end end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def do_something_with_every_item ( array ) # Set up total or refund variables here, # IF we need them. index = 0 while index < array . length # Add to the total OR # Add to the refund OR # Show the discount index += 1 end end

The problem is: how will you set up the variables you need prior to running the loop? And how will you execute the code you need within the loop?

Blocks

If only we could pass a chunk of code into a method, to be executed while that method runs, our problem would be solved. We could rely on do_something_with_every_item to handle the looping for us, and pass it chunks of code that add all the prices, or subtract them, or calculate a discount. Too bad most languages don’t have a simple means to do such a thing.

Ruby has a way, though: blocks! A block is basically a chunk of code that you associate with a method call. While the method runs, it can invoke the block one or more times.

To declare a method that takes a block, include the yield keyword. If you want to pass one or more parameters to the block, include them as arguments to yield .

def calculate_tax(income) tax_rate = 0.2 yield income * tax_rate end 1 2 3 4 def calculate_tax ( income ) tax_rate = 0.2 yield income * tax_rate end

When you call your method, you can provide any code you want within a block. The block sits after the method’s argument list, looking kind of like an additional argument.

Here’s the method call and block in action:

income = 60000 net_income = income calculate_tax(income) do |tax| puts "You owe #{tax}." net_income -= tax end puts "Your net income: #{net_income}" 1 2 3 4 5 6 7 8 9 income = 60000 net_income = income calculate_tax ( income ) do | tax | puts "You owe #{tax}." net_income -= tax end puts "Your net income: #{net_income}"

Output:

You owe 12000.0. Your net income: 48000.0 1 2 You owe 12000.0. Your net income : 48000.0

When Ruby encounters the yield keyword while executing your method, it passes control from the method to the block. The arguments to yield get placed into those block parameters that you specify within the vertical bars. These parameters will live only as long as the block does, and you can act on them in the Ruby statements that make up the block body.

DRYing up our code with blocks

Now that we’ve discovered Ruby blocks, we can actually implement that mythical do_something_with_every_item method we were wishing for, and take the repetition out of our code.

def do_something_with_every_item(array) index = 0 while index < array.length yield array[index] index += 1 end end 1 2 3 4 5 6 7 def do_something_with_every_item ( array ) index = 0 while index < array . length yield array [ index ] index += 1 end end

That’s it! The method will loop through each item in the array. Each time it encounters the yield keyword, the method will pass the current item to the block as a parameter.

Now, we can re-define our other methods to utilize do_something_with_every_item :

def total(prices) amount = 0 do_something_with_every_item(prices) do |price| amount += price end return amount end def refund(prices) amount = 0 do_something_with_every_item(prices) do |price| amount -= price end return amount end def show_discounts(prices) do_something_with_every_item(prices) do |price| amount_off = price / 3 puts "Your discount: $#{amount_off}" end end order = [3, 24, 6] puts total(order) puts refund(order) show_discounts(order) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 def total ( prices ) amount = 0 do_something_with_every_item ( prices ) do | price | amount += price end return amount end def refund ( prices ) amount = 0 do_something_with_every_item ( prices ) do | price | amount -= price end return amount end def show_discounts ( prices ) do_something_with_every_item ( prices ) do | price | amount_off = price / 3 puts "Your discount: $#{amount_off}" end end order = [ 3 , 24 , 6 ] puts total ( order ) puts refund ( order ) show_discounts ( order )

Output:

33 -33 Your discount: $1 Your discount: $8 Your discount: $2 1 2 3 4 5 33 - 33 Your discount : $ 1 Your discount : $ 8 Your discount : $ 2

But here’s the neat part: your new method doesn’t limit you to working with numbers! You can process an array full of strings:

def fix_names(names) do_something_with_every_item(names) do |name| puts name.capitalize end end fix_names ['anna', 'joe', 'zeke'] 1 2 3 4 5 6 7 def fix_names ( names ) do_something_with_every_item ( names ) do | name | puts name . capitalize end end fix _ names [ 'anna' , 'joe' , 'zeke' ]

Output:

Anna Joe Zeke 1 2 3 Anna Joe Zeke

…Or an array full of Time instances:

def get_years(times) do_something_with_every_item(times) do |time| puts time.year end end get_years [Time.at(0), Time.now, Time.now + 31536000] 1 2 3 4 5 6 7 def get_years ( times ) do_something_with_every_item ( times ) do | time | puts time . year end end get _ years [ Time . at ( 0 ) , Time . now , Time . now + 31536000 ]

Output:

1969 2014 2015 1 2 3 1969 2014 2015

…Or anything else you can dream up!

The “each” method

And now, of course, it’s time for the punchline. What we’ve implemented here is already available as a method on Ruby’s Array class: the each method.

Here’s what the total , fix_names , and get_years methods above would look like if they were re-written using each :

def total(prices) amount = 0 prices.each do |price| amount += price end return amount end def fix_names(names) names.each do |name| puts name.capitalize end end def get_years(times) times.each do |time| puts time.year end end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def total ( prices ) amount = 0 prices . each do | price | amount += price end return amount end def fix_names ( names ) names . each do | name | puts name . capitalize end end def get_years ( times ) times . each do | time | puts time . year end end

You can call them the same way as before:

puts total([3, 24, 6]) fix_names ['anna', 'joe', 'zeke'] get_years [Time.at(0), Time.now, Time.now + 31536000] 1 2 3 puts total ( [ 3 , 24 , 6 ] ) fix _ names [ 'anna' , 'joe' , 'zeke' ] get _ years [ Time . at ( 0 ) , Time . now , Time . now + 31536000 ]

The each method lets us remove a lot of duplicate code! And each is just one of many methods that use Ruby blocks in powerful ways. We’ll look at more of the exciting possibilities in a later post.

If you want to play around with blocks yourself, check out this screencast. It will help you get set up with a Ruby environment, and walk you through some experiments with each as well as other methods.

Editor’s note: This post is adapted from Jay’s upcoming book, Head First Ruby.