Ruby Blocks Simplified

Image for post
Image for post

One of the most unique and often misunderstood features of Ruby is blocks. Blocks are Ruby’s version of closures and can be used to make code more reusable and less verbose. But keywords such as yield can be hard to grok at first and make this functionality a bit intimidating to work with. This article aims to go over block essentials and build up your knowledge piece by piece.

What is a block?

Blocks consist of code between a set of curly braces or a do/end pair. The former is the single-line definition and the latter is the multiline definition.

method { |i| ... }method do |i|
...
end

The single-line definition is primarily used for one-liners. For the sake of consistency I’ve used the do/end syntax in all of the examples.

If you’ve worked with Ruby before, I almost guarantee you’ve seen a block. The each and map methods are two of the most commonly used iterators that invoke block usage.

["⭐️", "🌟"].each do |star|
puts star
end
# Output
⭐️
🌟

How do you create a block?

def star_wrap(el)
puts "⭐️" + el + "⭐️"
end
star_wrap("💙")# Output
⭐️💙⭐

If we wanted to rewrite this function using block notation, we could do it like so…

def star_wrap
puts "⭐️" + yield + "⭐️"
end
star_wrap do
"💜"
end
# Output
⭐️💜⭐

As shown above, the return value of the block contents are what is passed to the yield keyword in the function.

We can also make the block more customizable by passing a parameter to the function we created.

def wrap_with(el)
puts el + yield + el
end
wrap_with("⭐️") do
"💚"
end
# Output
⭐️💚⭐️

If we want to refer to values from our function in the attached block, then we can pass arguments to yield and reference it in the block parameters...

def wrap_with(el)
puts el * 5
puts yield(el * 2)
puts el * 5
end
wrap_with("⭐️") do |els|
els + "🖤" + els
end
# Output
⭐️⭐️⭐️⭐️⭐️
⭐️⭐️🖤⭐️⭐️
⭐️⭐️⭐️⭐️⭐️

But why use blocks over a regular function?

For example, let’s say we knew that we always wanted to print the output of a series of commands between a set of “⭐⭐⭐”. We can use blocks to apply the wrapping logic to different contexts without having to make auxiliary functions.

def star_wrap
puts "⭐⭐⭐"
puts yield
puts "⭐⭐⭐"
end
star_wrap do
server = ServerInstance.new
data = server.get("orange/heart/endpoint")
data.to_s
end
star_wrap do
fetcher = DatabaseFetcher.new
data = fetcher.load("purple_heart_data")
data.exists? data : "no heart data"
end
# Output (hypothetical)
⭐⭐⭐
🧡
⭐⭐⭐

⭐⭐⭐
💜
⭐⭐⭐

As shown above, stars are always printed before and after the code executed in a block. Even though “🧡” was fetched quite differently than “💜”️, the star_wrap method allows us to apply the star wrapping logic to both contexts in a contained manner.

Block error handling

def stars
puts "⭐⭐⭐"
end
stars do
puts "💙"
end
# Output
⭐⭐⭐

We were able to invoke all of the blocks in the examples above because we used the keyword yield. So, what if we called yield and did not supply a block? An error will be raised.

def star_wrap
puts "⭐️" + yield + "⭐️"
end
star_wrap# Output
LocalJumpError: no block given (yield)

We can amend this issue by using the block_given? expression to check for block usage.

def star_wrap
if block_given?
puts "⭐️" + yield + "⭐️"
else
puts "⭐️⭐️⭐️"
end
end
star_wrap# Output
⭐️⭐️⭐️

Passing a block as a parameter

def star_wrap(&block)
puts "⭐️" + block.call + "⭐️"
end
star_wrap do
puts "💛"
end
# Output
⭐💛⭐

In this instance, the block is turned into a Proc object which we can invoke with .call. Using blocks in this manner comes in handy when you want to pass blocks across functions. We specify the block parameter by passing it as the last argument and prepending it with &.

Below, the methods star_wrap_a and star_wrap_b do the exact same thing ...

def star_wrap_a(&block)
puts "⭐" + block.call("✨") + "⭐"
end
def star_wrap_b
puts "⭐" + yield("✨") + "⭐"
end
star_wrap_a do |el|
el + "💙" + el
end
star_wrap_b do |el|
el + "💚" + el
end
# Output
⭐✨💙✨⭐
⭐✨💚✨⭐

Blocks in the real world

<!DOCTYPE html>
<html>
<head>
<title>Block Investigation</title>
</head>
<body>
<%= yield %>
</body>
</html>

For example, in Gusto’s Partner Directory, the top blue bar resides in a base view file which yields to a different layout for each route.

Image for post
Image for post

And those are the essentials!

Originally published at https://engineering.gusto.com on July 30, 2019.

Gusto Engineering

Reengineering Payroll, Benefits, and HR for modern…

Julianna Seiki Roen

Written by

She / her • Software engineer @ Gusto, explaining technical concepts in visual and approachable ways • juliannaroen.com

Gusto Engineering

Reengineering Payroll, Benefits, and HR for modern business. Hiring empathetic engineers in San Francisco, Denver and NYC! https://gusto.com/about/careers

Julianna Seiki Roen

Written by

She / her • Software engineer @ Gusto, explaining technical concepts in visual and approachable ways • juliannaroen.com

Gusto Engineering

Reengineering Payroll, Benefits, and HR for modern business. Hiring empathetic engineers in San Francisco, Denver and NYC! https://gusto.com/about/careers

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store