Ruby Block - what is that, and what does it do?
It has long been said that "Everying is an object in Ruby". Well, it is almost true.
Despite such classes as Class, Module, as well as many others eventually being objects, there's an exception to the rule.
As you probably guessed, block is not an object. We cannot assign it to a variable or call a method class on it.
It is a chunk of code that we want to execute dynamically by putting it between do...end
or between curly braces {...}
. The benefit of blocks is that we don't need to store some statements into a method. Blocks serve as part of a method signature (method signature includes method name, list of arguments and a block) and must start on the same line after method arguments.
We often use blocks when we read or write into files, or when we want to perform certain operations while iterating via a collection, such as an Array or a Hash.
As an example let's take a look into times method of the Integer class.
# Block written in one line
5.times { puts "Some code within a block" }
# Block written in multiple lines
5.times do
puts "Some code within a block"
end
# Output in both cases
Some code within a block
Some code within a block
Some code within a block
Some code within a block
Some code within a block
=> 5
As per the implementation of the times
method, an output Some code within a block
will be printed out five times and integer 5
will be returned after that.
A block has to start on the same line as the method name and its parameters if any.
# This is wrong
5.times
{ puts "Some code within a block" }
Difference between do..end
and curly braces {}
.
- According to the Ruby Style Guide it is recommended to use
do...end
for multi-line blocks and{...}
for single-line blocks. - Curly braces
{...}
syntax has higher precedence thando...end
syntax. We can observe when a block is passed to a method without using parentheses()
:
# Using {...}
puts [1,2,3].select{ |number| number.even? }
2
=> nil # method "puts" returns nil
# Using do...end
puts [1, 2, 3].select do |number| number.even? end
#<Enumerator:0x00007fefbebca498>
=> nil # method "puts" returns nil
In the first case, we see 2
is printed back because the method puts
accepts everything till the end of the block as one parameter.
In the second case, enumerator is returned instead because the method puts
does not see do..end
as a part of a parameter.
Block Parameters
A block can have parameters that are stored between vertical lines called pipes ||
.
2.times do |number|
puts number
end
# Output printed
0
1
=> 2 # returned value of the "times" method
Block Variable Scope
Block variables (parameters between the pipes ||
) can behave differently from variables that are coming from the method scope. Let's review some examples:
- A variable is declared in the method and the same variable is used inside a block. We can see that block has access to the
x
:
def block_variable_example
x = 1
3.times do
puts "x inside the block: #{x}"
end
puts "x outside the block: #{x}"
end
# Call method
block_variable_example
# Output
x inside the block: 1
x inside the block: 1
x inside the block: 1
x outside the block: 1
=> nil
- A variable is declared in the method and the same variable is being re-assigned inside the block. Same as in the first example, variable inside a block is the same as the one outside:
def block_variable_example
x = 1
3.times do
x = 2
puts "x inside the block: #{x}"
end
puts "x outside the block: #{x}"
end
# Call method
block_variable_example
# Output
x inside the block: 2
x inside the block: 2
x inside the block: 2
x outside the block: 2
=> nil
- A variable
x
is declared in the method and the block parameter has the same namex
. It appears that variable inside the block is different from the one that was declared in the method:
def block_variable_example
x = 1
3.times do |x|
puts "x inside the block: #{x}"
end
puts "x outside the block: #{x}"
end
# Call method
block_variable_example
# Output
x inside the block: 0
x inside the block: 1
x inside the block: 2
x outside the block: 1
=> nil
What is yield
keyword?
If you want to execute a block inside a method yield
is your friend. In many countries, there's a traffic sign yield
which means you need to give right of way to another car.
- Similar logic applies here. During a method execution when Ruby parser sees
yield
it gives the right of way to the code within a block and executes it. After that, it comes back to the method scope and executes the remaining code. - In case there are situations when a block is going to be optional when calling your method it is better to use the
Kernel#block_given?
method so that no exception is thrown. Here are some examples of usingyield
keyword:
def my_yield_method
puts "Code before yield"
if block_given?
yield
else
puts "No block was given"
end
puts "Code after yield"
end
# =================== Call method with a block
my_yield_method { puts "Code inside a block" }
# Output
Code before yield
Code inside a block
Code after yield
=>nil
# =================== Call method without a block
my_yield_method
# Output
Code before yield
No block was given
Code after yield
=> nil
-
yield
keyword can have arguments that are going to be passed to a block
def my_yield_method_with_params
str = "Awesome parameter"
puts "Code before yield"
yield str if block_given?
puts "Code after yield"
end
# =================== Call method with a block
my_yield_method_with_params do |param|
puts "This is what we got: #{param}"
end
# Output:
Code before yield
This is what we got: Awesome parameter
Code after yield
=> nil
I hope you found this information helpful 🙂
Top comments (3)
Hi Maryna, nice explanation here.
I've been coding in Ruby for many years and until last year found myself that I don't use blocks in the code I write in a daily basis xD
I don't feel bad for that but I do wonder what's the real use case of blocks when we create our own functions? I mean, I know how to use the ones in the Ruby API but I don't see cases where a function I wrote would benefit from a block.
Example, I know I can do:
but when I write something like this
I've never added a block to the functions I write xD
I'd like to extent the question to you to know your opinion :)
Hi Francisco,
Thank you for your feedback and an interesting question 🙂
I also do not write custom methods that yield blocks too often. One example that currently comes to my mind is when I was working with Net::SFTP to perform various operations for sftp files: download, upload, rename, get all files.
We also had different ways to connect to SFTP server - via user/password or via ssh/no password.
It could result into something like this
One way to make it less redundant would be to create a method that accepts a block (with syntactic sugar parameter
&block
):Of course, it might be possible to avoid using a custom method here and instead pass some sort of argument hash or object into the method. But the code above is one way to solve the problem.
Another time I used
yield
is when I used to run performance metrics with RubyProfAwesome. Thanks for your answer.
I've been asking this question to every one I can (last's year RubyConfColombia I even asked Aaron Patterson 😅) and the answers is almost the same: "not [too] often".
Your examples are really good and this was precisely why I asked. Getting to see blocks being used outside the Ruby API is a way to experience their benefit or possibilities.
Thanks again!