Apr 29, 2015
Table of contents:
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.
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.
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.
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
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.