cult3

Understanding and using blocks in Ruby

Apr 29, 2015

Table of contents:

  1. What is a Block?
  2. Using a Block in a method
  3. Why would you use a block?
  4. Conclusion

If you have been following along with this series, you might be confused by the use of blocks.

So far we’ve seen blocks being passed to iterator methods such as each, map and select whilst looking at Arrays, and Hashes.

A block is simply a chunk of code that can be passed as an argument to a function. You can think of it as a list of instructions that can be passed around and acted upon.

In today’s tutorial we will be looking at understanding and using blocks in Ruby.

What is a Block?

If you are already familiar with other programming languages, you might already be familiar with the concept of passing a chunk of code to a function.

In Ruby, a block is a chunk of code that sits between curly braces or a do..end statement.

For example, imagine we have the following Array:

colours = %w[red blue green]

We could print each colour to the screen by passing a block wrapped in curly braces like this:

colours.each { |colour| puts colour }

Or alternatively we could pass a block wrapped in do..end like this:

colours.each { |colour| puts colour }

Usually you will see curly braces used if the block fits on one line, and do..end when it spans multiple lines.

So a block is a chunk of code that you pass as an argument to a method.

In both cases above, each element of the Array will be passed to the block. The block accepts each element and then puts it to the screen.

This makes methods like each very flexible because we can pass a list of actions that should be executed upon for each item as a block of instructions.

Using a Block in a method

So hopefully the last section makes sense. A block is simply a chunk of code that can be passed to a method.

To better understand what’s going on under the hood, we’ll now look at implementing a method that accepts a block:

def do_something; end

do_something { puts 'hello world' }

Here we have a simple method called do_something. After we have declared the method we call it and pass it a block that will print "hello world" to the screen.

Notice how we don’t have to tell the method that it should be expecting a block.

However, if you save this as a Ruby file and run it from Terminal you will notice that nothing is printed to the screen!

In order for the block to be executed we need to yield the control flow. We do that by using the yield key word:

def do_something
  yield
end

do_something { puts 'hello world' }

When the yield key word is encountered we jump into the block and run the chunk of code and then jump back out and continue with the rest of the method.

You can see this in action with the following code:

def do_something
  puts 'before the block'
  yield
  puts 'after the block'
end

do_something { puts 'Inside the block' }

If a method is expecting a block but you don’t pass one an Exception will be thrown:

def do_something
  yield
end

do_something

# => no block given (yield) (LocalJumpError)

You can conditionally yield to a block if a block is passed by calling block_given?:

def do_something
  yield if block_given?
end

do_something

Finally, you can also pass arguments to a block. This is what is happening when you pass a block to the each method of an Array.

To understand this better we can implement our own iterator method on the Array Class:

class Array
  def reverse_each
    reverse.each { |item| yield item }
  end
end

[1, 2, 3].reverse_each { |number| puts number }

In the example above we’ve added a new method to the Array class called reverse_each.

This method will reverse the Array of items and then call the each method and pass the block. Each item of the Array will be passed to the block.

If you run this code in Terminal you will see that the numbers are printed to the screen in reverse order.

Why would you use a block?

So why would you want to use a block and why are they so important to Ruby? Well blocks are useful for a number of different reasons.

Firstly, using blocks makes it possible to implement generic methods like each or map that can be used in all sorts of different ways.

For example, the each method will call a block for each element, whereas the map method will return a new Array containing the values returned by the block.

By providing the raw tools of the each and map methods and the ability to write blocks, Ruby is giving us with what we need to define the behaviour we need to implement. This means we can reuse these methods with any kind of behaviour without having to duplicate the logic of iterating over the Array.

Secondly, blocks allow you to DRY up your code to prevent repetition. For example, take a look at this:

class SnapChat
  def take_picture
    puts 'You have authenticated!'
    puts 'Take a picture'
  end

  def record_video
    puts 'You have authenticated!'
    puts 'Record a video'
  end
end

client = SnapChat.new
client.take_picture
client.record_video

In both of these methods we first need to authenticate before we can take the action. We can DRY this code up by using a block:

class SnapChat
  def authenticate
    puts 'You have authenticated!'
    yield
  end

  def take_picture
    authenticate { puts 'Take a picture' }
  end

  def record_video
    authenticate { puts puts 'Record a video' }
  end
end

client = SnapChat.new
client.take_picture
client.record_video

So once again we are using the flexibility of blocks to reduce repetition.

And finally, blocks make it really easy to encapsulate a series of tasks using the Ruby classes like File:

File.open('shopping_list.txt', 'w') { |f| f << 'eggs' }

In this example we open up a new file called shopping_list.txt and then pass the file to a block where we can work with it, in this case simply adding a new item to the list.

Ruby will automatically deal with closing the file at the end of the block.

Using a block in this example is also much better than writing the following series of statements:

f = File.open('shopping_list.txt', 'w')
f << 'eggs'
f.close

Conclusion

Blocks are a very important and very useful part of the Ruby language. You will see heavy use of blocks in almost all Ruby code you encounter, so it’s important to understand what they are for and what is exactly going on under the hood.

Block are useful for all sorts of reasons really and you will see them used in all sorts of different scenarios.

If you’re new to programming in general, blocks can seem a bit daunting or confusing. However once you get into the swing of writing Ruby, you will see that blocks are really an invaluable component of your Ruby tool belt.

Philip Brown

@philipbrown

© Yellow Flag Ltd 2024.