Why Ruby blocks exist

Exploring Ruby's "each" method

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]

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)

Output:

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)

Output:

-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)

Output:

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 totalrefund, 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

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

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.

block

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}"

Output:

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

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)

Output:

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']

Output:

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]

Output:

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 totalfix_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

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]

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.

tags: , ,