Advertisement

Ruby for Newbies: Iterators and Blocks

by

Ruby is a one of the most popular languages used on the web. We've started a new screencast series here on Nettuts+ that will introduce you to Ruby, as well as the great frameworks and tools that go along with Ruby development. In this chapter, we’ll talk about in blocks and iterators.


Catch Up


View Screencast

Click the HD button for a clearer picture.

Subscribe to our YouTube page to watch all of the video tutorials!


Blocks

In the last lesson, we talked about loops. You actually won’t use loops too often in Ruby, because of a feature called blocks (and—as a result of blocks—iterators). To refresh your memory, look at the two following method calls (you can try this in IRB):

name = "Joe"

name.reverse  # => "eoJ"

name.concat(" the Plumber") # => "Joe the Plumber"

As you know, the parentheses after the method call are usually optional. We’ll learn today when they are required.

So, here are the parts of a method call:

  • The object receiving the method; name above.
  • The dot.
  • The method name; reverse or concat above.
  • The arguments; " the Plumber" in the second example above.
  • The code block; stay tuned!

The first three parts are required, obviously. The arguments and the code block are optional. What is this code block? Look at this example, and then we’ll discuss it:

sites = ["net", "psd", "mobile"]

sites.map! do |site|
    site += ".tutsplus.com"
end

sites # => ["net.tutsplus.com", "psd.tutsplus.com", "mobile.tutsplus.com"]

In this case, the array sites is the receiver; the method is map!. Next we have the block. If the block is on multiple lines, you can use the keywords do and end to delimit it. If you’re putting it on a single line, you can use curly braces (these work for multi-line blocks, too).

After the opening of the block, we have the block parameters, within pipes ( | ). What this is really depends on the method you’re executing. The most common use of blocks are in iterator methods, so the block parameter will be the current item in the looping. If this sounds rather abstract, we’ll do a few examples.


Iterators

We’ll start by looking at the iterators of arrays, because they’re the most commonly looped over thing.

Instead of using a for loop, you’ll probably use each:

sites = ["net", "psd", "mobile"]

sites.each { |site|
    puts "#{site}.tutsplus.com"
}
# net.tutsplus.com
# psd.tutsplus.com
# mobile.tutsplus.com

This is just like doing a for loop; one by one, each item in sites will be assigned to the block parameter site; then, the code inside the block will be executed.

In case you’re curious, the each method returns the original array.

Sometimes, you’ll want to return a value from the block. That’s not hard to do, if you use the right method.

# assume sites above

sites = sites.map do |s|
    "#{s}.tutsplus.com"
end

The map method collects whatever values are returned from each iteration of the block. Then, an array of those values is returned from the method. In this case, we’re reassigning the sites variable to the new array.

There’s a better way to do this, though. Several Ruby methods have duplicates with the exclamation mark (or bang); this means they are destructive: they replace the value they are working on. So the above could be done this way:

sites.map! { |site_prefix| "#{site_prefix}.tutsplus.com" }

Now, sites will be the array of values returned from the block.

More than just arrays have iterator methods, though. Numbers have a pretty cool times method:

5.times do |i|
    puts "Loop number #{i}"
end
   
# Loop number 0
# Loop number 1
# Loop number 2
# Loop number 3
# Loop number 4

As you continue coding Ruby, you’ll find a lot of useful methods that use blocks. Now, let’s see how to create our own blocks.


Building Blocks

Now that you’re familiar with using blocks, let’s see how to write methods that take advantage of them. Here are two other block tidbits that you haven’t learned yet:

  • Block parameters are not required; you can write a block that doesn’t use them.
  • Blocks themselves can be optional. You can write a function that works with or without blocks.

Here’s what happens you call a method that takes a block. Behind the scenes, Ruby is executing some method code, then, yielding to the block code. After that, control is returned to the method. Let’s check it out.

Since most of the simple iterator functions are built into Ruby, we’ll “rewrite” one of those. Let’s do the each method on arrays:

class Array
    def each2 
        i = 0;
        while self[i]
            yield self[i]
            i += 1
        end
        self
    end
end

As you can see, this is just a normal method. Remember that within an instance method, the self keyword refers to the instance of the class, in this case, Array. Inside the method, we’re using a while loop to loop over the items in the array. Then, inside the loop, we use the yield keyword. We’re passing it self[i]; that will end up being the block parameter. After that, we increment i for the loop and continue.

If we wanted this method to return the array of values that the block returned, we could just capture the returned value of yield and return that, instead of self, we return that array.

class Array
    def each2_returning_new_values
        i = 0;
        new_vals = [];
        while self[i]
            new_vals[i] = yield self[i]
            i += 1
        end
        new_vals
    end
end

Methods, revisited

Let’s finish up by talking about methods. We know that using parentheses is optional … most of the time. Here’s when you need to use the parenthesis when method calling.

Sometimes, you’ll have both method parameters and a block.

obj.some_method "param" { |x|
    #block code here
}

What I’ve just done won’t work; we need to use parentheses in this case, because otherwise the block is associated with the last parameter. This is only the case if you use the curly braces to delimit the block; if you use do - end, the parentheses aren’t required.

The other time parenthesis are required is when you’re passing a literal hash (not a variable that points to a hash) as the first parameter of the method. Ruby will think it’s a block, because of the curly brace

arr.push { :name => "Andrew" } # Fails!
arr.push({ :name => "Andrew" }) # Passes
Advertisement