DEV Community

Yago Santos
Yago Santos

Posted on • Edited on

Yet Another Post About Ruby Blocks

If you have been programming in Ruby for awhile now, you have likely already used built-in methods such as #each, #select, and #map. Despite of what everyone says about Ruby magic, there's nothing special with the way those methods work.

Those methods are usually called on collections, such as Arrays and Hashes, either using an inline block (code wrapped by curly braces) or a multiline block (code wrapped between do and end).

How do Blocks Work?

We frequently associate blocks of code with methods that are available to us through the Enumerable module. Please take a look at the example below:

  1. Inline Block:
[1, 2, 3, 4, 5].select { |number| number.even? }

> [2, 4]
Enter fullscreen mode Exit fullscreen mode
  1. Multiline Block:
[1, 2, 3, 4, 5].select do |number|
  number.odd?
end

> [1, 3, 5]
Enter fullscreen mode Exit fullscreen mode

In order to fully understand how blocks work, it's necessary that we learn how to create our own custom blocks to grasp the nitty-gritty details!

Yielding Control to a Block

Any method in Ruby can be associated to a block, but what determines if that block is going to be invoked or not during the method's execution is the special keyword yield.

roll_die { |number| puts "You rolled #{number}" }
Enter fullscreen mode Exit fullscreen mode

How could we implement that method?

def roll_die
  puts "Method is executing..."
  random_number = rand(1..6)
  yield(random_number)
  puts "Method is done!"
end
Enter fullscreen mode Exit fullscreen mode

Therefore, once we invoke the roll_die method with the associated block it'll print the following lines to the console:

> "Method is executing..."
> "You rolled 3"
> "Method is done!"
Enter fullscreen mode Exit fullscreen mode

What yield is doing is "pausing" the roll_die method's excution and passing the control over to the block that is associated with it. Plus, it's also passing the random_number parameter as a block parameter to the block!

A curiosity about blocks is that once its done executing it returns the last computed line of code as the final result! Wait a second...what does that mean, though?

It means that we could have done something like this instead:

  • Rearranging the code in the roll_die method:
def roll_die
  puts "Method is executing..."
  random_number = rand(1..6)
  result = yield(random_number)
  puts "You rolled #{result}"
  puts "Method is done!"
end
Enter fullscreen mode Exit fullscreen mode
  • Rearranging the code block associated to the roll_die method:
roll_die do |number|
  puts "Rolling the die..."
  number
end
Enter fullscreen mode Exit fullscreen mode

In that new arrangement above, the random number is being returned by yield and assigned to the variable result in the roll_die method!

> "Method is executing..."
> "Rolling the die..."
> "You rolled 1"
> "Method is done!"
Enter fullscreen mode Exit fullscreen mode

Caveats About Ruby Blocks

If you have read this far, you are likely wondering what happens if we try to yield but there isn't an associated block to pass over control to. Well, for those scenarios we can use the block_given? method. Check out the example below:

  • Defining the greeting(name) method:
def greeting(name)
  if block_given?
    yield(name)
  else
    "No block was given."
  end
end
Enter fullscreen mode Exit fullscreen mode
  • Associating the greeting(name) method with a block:
greeting(name) { |name| "Hello,  #{name}!" }
Enter fullscreen mode Exit fullscreen mode

Now, there are 2 different ways to invoke the greeting(name) method:

# First way
greeting("Mary") { |name| "Hello,  #{name}!" }

> "Hello,  #{name}!"

# Second way
greeting("John")

> "No block was given."
Enter fullscreen mode Exit fullscreen mode

Another curiosity is around the number of parameters passed to yield, which in turn are sent as block parameters to your block. Are they enforced/requested by Ruby or not? Give it a try using irb - you'll be surprised with the result!

Please do let me know if you have any questions and/or suggestions in the comments below!

Top comments (4)

Collapse
 
pinotattari profile image
Riccardo Bernardini

Another curiosity is around the number of parameters passed to yield, which in turn are sent as block parameters to your block. Are they enforced/requested by Ruby or not? Give it a try using irb - you'll be surprised with the result!

Interesting, I never tried before (although I used Ruby fairly a lot). This also is interesting

a = [1, 2, 3]
a.each {|*x| p x}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
yagosansz profile image
Yago Santos • Edited

Hi Riccardo,

Thanks for sharing! I couldn't understand how it works though. Would you mind explaining what is happening behind the scenes? =)

I thought the * (splat operator) was only used to "spread" elements of a collection when passing it as an argument to a function.

Collapse
 
pinotattari profile image
Riccardo Bernardini

It works also the other way around, as the varargin in Matlab. For example, suppose I define

def foo(a, b, *c)
   p c;
end
Enter fullscreen mode Exit fullscreen mode

Here foo is a function that expects a number of arguments larger or equal than two. The first two arguments are assigned to a and b and all the remaining arguments (zero or more) are collected in an Array assigned to c. For example, if I call

foo(42, 33, 'x', nil 12)
Enter fullscreen mode Exit fullscreen mode

the function prints ["x", nil, 12], if I call

foo(42, 33)
Enter fullscreen mode Exit fullscreen mode

the function prints [ ], if I call

foo(42)
Enter fullscreen mode Exit fullscreen mode

I get an error

Thread Thread
 
yagosansz profile image
Yago Santos

Hi Riccardo,

Thank you for taking to explain it - it is more clear to me now how it works!