How to create methods that accept a block

In Ruby there are many methods that accept blocks, one of the first that comes to mind is the times method that allows us to run specific block X times.

Now it's time to learn how to create a method that can accept (and use) a block.

Everything is done by the magic word yield.

Define a method that accepts a block is pretty simple, all you have to do is add yield in the body of the method like so:

def my_method
    puts "This is the start of my method."
    yield # <- I am waiting for a block here
    puts "This is the end of my method."
end

And you're done.

Now you can call your method with a block:

my_method do 
    puts "This text comes from the block"
end

# Prints
# This is the start of my method.
# This text comes from the block
# This is the end of my method.

Basically the method stops what it's doing to let the block take over and run the code we specify in it, but there's more...

First and foremost you can add yield as many times as you want, doing so the code in the block will just get executed as many times as yield is present. So if we change the method in:

def my_method
    puts "This is the start of my method."
    yield
    yield
    yield
    puts "This is the end of my method."
end

We get:

my_method do 
    puts "This text comes from the block"
end

# Prints
# This is the start of my method.
# This text comes from the block
# This text comes from the block
# This text comes from the block
# This is the end of my method.

But obviously, there are some gotchas.

The first one that probably you'll face creating your methods that accept blocks is that you have to pass a block to it otherwise you'll get a LocalJumpError.

In order to make it conditional, you have to use the method block_given?

Pass a parameter to the block

As it happens for times or upto you are able to pass parameters to the blocks that can be used within the block itself, you just need to pass it to yield as follows:

def my_method
    puts "This is the start of my method."
    yield "Andrea"
    puts "This is the end of my method."
end

And when we use it we get the following:

my_method do |name|
    puts "The name you passed is #{name}"
end

# Prints
# This is the start of my method.
# The name you passed is Andrea
# This is the end of my method.

Return values from a block

Probably the method you're creating needs to do something with the value calculated in the block, so it's time to know how to handle it.

The most important thing to notice is that the implicit return, the ability of a Ruby method to return the value of the last expression, is available in the blocks as well.

This means that your blocks are already returning values, you just are not using them.

In order to make our method aware of the returned value we just have to change it by storing the value, you guess, in a variable:

def my_method
    puts "This is the start of my method."
    result = yield "Andrea"
    puts "This is the end of my method and the result from the block is #{result}."
end

Now the result holds the value returned from the block, here's an example:

my_method do |name|
    puts "The name you passed is #{name}"
    rand(1..5)
end

# Prints
# This is the start of my method.
# The name you passed is Andrea
# This is the end of my method and the result from the block is 4.

Resources

Those notes are taken while following the Ruby Blocks course at Pragmatic Studio.