Ruby Blocks Give You Wings

6 minute read

Introduction

We’re going to start exploring some of the more nuanced aspects of the Ruby programming language, with blocks being the subject of the day.

By the end of this article, we’re going to give you the tools you would need to solve a seemingly simple Ruby interview question, that really probes how well you understand how scoping, bindings and closures are handled in the language:

the_ball = "O"
class Catch 
  # We want to be able to puts the_ball here
  # (At the moment, the_ball is out of scope) 
  def my_method 
    # We want to be able to puts the_ball here 
  end
end

# How could we refactor this code & solve the problem?

Now if you’ve ever written Ruby code before, you’ve probably written hundreds of blocks. But, here we are going to dig a little below the surface, and discuss how they interact with the code they are injected into.

Why Is This Useful?

Some of the details here turn blocks into something you may have overlooked as mundane, into a surprisingly powerful tool, in certain situations. In more complex projects, having the ability to move Ruby objects accross different scopes can be really handy, and this technique is something you’ll learn about in this article.

Let’s get started

Review: The Basics of Ruby Blocks

I assume you’ve encountered one before, but in case it’s been a fair while since the last time you wrote one, let’s quickly run through what a block looks like in Ruby:

[1,2,3].each do |num|
  puts num*num
end
# You either signify the beginning of your block with the keyword "do"
# and end it with "end" OR...

[1,2,3].each { |num| puts num*num }
#You place the code you want to run in curly braces like this.

These two ways of writing a block out are equivalent. You can only define a block when you call a method (here, the method used was each ). A block can take in any number of optional arguments.

How Are Blocks Run?

There are two keywords you need to get to grips with to use blocks in your code.

Firstly, and most importantly, you have the yield keyword, which is the command you give your programme which tells it to “Run the block you’ve been given now”, along with any arguments you might want to pass in.

def do_stuff
  yield 
end

do_stuff do
  puts "hello"
end 
# => "hello"

The block_given? keyword is essential if you are writing a block that requires a block being passed to it in order to do it’s job, but might not receive one. It’s usually used as a guard clause.

The each method is a great example of this, as you can see from the stub below:

def each(*several_variants)
  block_given? ? (yield to_enum.next; self) : to_enum
end

If you don’t pass a method a block and yield gets called, an error will get , using the block_given? command to run a check can help prevent this.

And those are the absolute basics of Ruby Blocks. But how does the code in a block get its variables?

Bindings Form The Environment

Consider the following snippet:

def return_of_goku?	
  over_9000 = false 
  yield
end

over_9000 = true
return_of_goku? do 
  puts "WHAT!? 9000!? THERE'S NO WAY THAT CAN BE RIGHT" if over_9000
end

Pause for a moment: What do you think happens when this code runs?

Here we are passing our method a block of code, but how does the code inside that block know which over_9000 to refer to? If you placed a debugger at different points in this code what would it find?

Code always consists of two parts:

  1. The code itself (the text)
  2. An Environment: the objects and resources that code is trying to instruct.

All the stuff in your environment consists of variables and module/class names bound to objects. This is why they are often referred to as “bindings”. All a “lexical closure” really is, is the set of bindings you can access at a certain point.

The answer to the question above, as you may have guessed, is when block runs, over_9000 is true. This is because blocks grab their bindings from the area where they are called. If we remove the second over_9000 from our code, we will run into an error:

def return_of_goku?	
  over_9000 = false 
  yield
end

return_of_goku? do 
  puts "WHAT!? 9000!? THERE'S NO WAY THAT CAN BE RIGHT!" if over_9000
end

This is what you get:

#undefined local variable or method `over_9000' for main:Object
#(repl):7:in `block in <main>'
#(repl):3:in `return_of_goku?'
#(repl):6:in `<main>'

Ruby’s Scoping Is Strict!

Why is this worth discussing? And why did I say at the beginning that blocks were such a powerful tool?

Well, Different languages handle closures in different ways. For instance, JavaScript’s handling of closures reminds me a little of Russian dolls, with nested scopes having access to variables in their parents scope (for instance a method defined in a class will have access to variables defined there).

Ruby is a little stricter: When you change scope in Ruby, there is no nested visibility. A class and its method definitions have different local variables. Have a look at the example below:

a = 1
class Foo
  b = 2
  def bar
    c = 3
  end
end

If you tried to refer to a inside of the class Foo , your code would fail as with the error

undefined local variable or method 'a' for Foo:Class. Similarly, within the code for the method bar you would run into problems if you tried to refer to either a or b . In other languages, such as JavaScript, you’d be fine- but in Ruby, scopes are sharply separated.

There are two points in Ruby code where you move from one scope to another:

  1. Class/Module definitions
  2. Method definitions

Most of the time, this is totally fine — but there are times when you want a method or a class to share its scope with its parent. This is where blocks (combined with a few special methods) come in really handy. They can help you get over scope gates when you really need to

Blocks Give You Wings!

Let’s return to the code challenge question from earlier:

Using the tools we have in our kit so far, it wouldn’t seem that we would have a way to complete the problem:

the_ball = "O"
class Catch
  # We want to be able to puts the_ball here
  def my_method
    # And here!
  end
end

However, thanks to the way blocks handle scoping, we could accomplish this if, somehow, we could find methods that fulfil the same role as the class and def keywords.

Fortunately such methods do exist. The first of which I’m sure you’ve seen many times before: Class.new .

the_ball = "O"
Class.new Catch do
  # We want to be able to puts the_ball here
  the_ball #=> "O"
  #Great!

  def my_method
    # We  still want to be able to puts the_ball here,
    #but it's blocked by the scope gate
  end
end

Now we just need one more method that would allow us to jump the second scope gate, def. Again, such a function does exist: define_method .

Side Note: Did you ever see define_method in Ruby code before and wonder why it existed? Now you can see why it’s handy.

the_ball = "O"
Catch = Class.new do
  # We want to be able to puts the_ball here
  puts the_ball #=> "O"
  # Great!
  define_method :my_method do
    puts the_ball #=> "O"
    # Nice!
  end
end

Congratulations: We just solved the problem posed at the start of the article. Hopefully you learned something about Ruby in the process

Conclusion

While I have described this method as “jumping over scope gates”, A lot of Rubyists refer to it as “flattening the scope” in your code. The important take away from this problem isn’t the associated slang though, so long as you understand how Ruby handles bindings, and how you can manipulate the language to ensure that the objects you need to reference are available where you need them.

Thanks for reading!

Updated: