Dylan Barnard    About    Archive    Projects

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:

  1. How blocks work at a basic level.
  2. 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. [1,2,3] - a collection of elements that implements Enumerable, such as an array of hash
  2. .each - a method that can utilize a block
  3. do...end - everything within do ... 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

Our block is pretty limited right now, all it can do is ask the block to execute which prints out a hard coded string. Fortunately, we can pass in parameters to our method and then pass those into the block using yield(). Let’s make our method a bit more generic.

def hello_world_conversation(reply)
  puts "Hello world"
  yield(reply)
end

> hello_world_conversation('Goodbye!') do |reply|
    puts "World says: #{reply}"
  end
"Hello world"
"World replies: Goodbye!"

Iteration is the name of the game

What if we wanted the our “World” entity to reply a couple of times? We’ve got all the building blocks there - we should 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 says: #{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 says: #{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 says #{reply}"}
"Hello world"
"World replies: Hey!"
"World replies: How's it going?!"
"World replies: Code here often?"
"World replies: Goodbye!"

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

  1. You never notice what actually exists until you look for it :) I’ve since found several good posts on the subject, including here and here