Understanding Ruby Blocks

Starting from the basics:

A single line block:

{ p "example string" } 

Multi-line block:

do
p "example string"
end

A block cannot be run directly, instead the block needs to be associated with or attached to a method call:

# Single line example:
2.times(method_param) { puts "example string" }
# Outputs:
#=> "example string"
#=> "example string"
# Multiple line example:
2.times(method_param) do
puts "example string"
end
# Outputs:
#=> "example string"
#=> "example string"

Adding Ruby Block Parameters:
You might have asked why I bothered showing the method parameters above as (method_param), In this case I wanted to show the distinction between method arguments/parameters and block arguments/parameters.

A block parameter is simply a local variable defined in the block and scoped to the block.

# Single line example:
2.times(method_param) { |num| puts "#{num} example string" }
# Outputs:
#=> "0 example string"
#=> "1 example string"
# Multiple line example:
2.times(method_param) do |num|
puts "#{num} example string"
end
# *Outputs:
#=> "0 example string"
#=> "1 example string"

*The Ruby #times method starts at 0 and counts however many times you specify, in this case 2.

2.times(method_param) do  <-("do" Marks the beginning of block)
p "hello" <-(Inside the block we can run any ruby code)
end <-("end" Marks the end of the block)
# On a single line left curly brace opens a block, and right curly brace closes a block.(opening the block)-> { } <-(closing the block)

When a block is called, the value of the parameter is filled in by the method. A block is like an anonymous method, it can encapsulate a chunk of code and accepts parameters.

Photo by Danny Avila on Unsplash

Block Scope:

As I said above a block parameter is scoped to the block , but if you create a local variable within the block, it will also be scoped to the block.

What this means in practice:

2.times(method_param) do |num|
my_local_variable = "goodbye"

puts "#{num} example string"
end
puts my_local_variable <-(outside of scope)

Calling my_local_variable above will result in an error, because it has disappeared. Any variables defined within the block are used up and then disappear.

However, if you initialize that variable above your method, in the outer scope, it will be available within your method, and outside of your method.

my_local_variable = "hello" 2.times(method_param) do |num|
my_local_variable = "goodbye"

puts "#{num} example string"
end
puts my_local_variable
outputs: #=> "goodbye"

Outer and Inner Scope:

Inner Scope has access to outerscope, but outerscope does not have access to innerscope.

my_local_variable = "hello"          <-(Outer Scope)2.times(method_param) do |num|        
my_local_variable = "goodbye" <-(Inner Scope between do..end)

puts "#{num} example string"
end
puts my_local_variable <-(Outer Scope)

In the case above, when we puts my_local_variable, it will output “goodbye”.
Why?
Because we changed what the variable named “my_local_variable” points to, in this case we changed it from “hello” to “goodbye”.
Again, the above two cases show that inner scoped variables can access outer scoped variables, but not vice versa.

Protecting the variable within the block:
One way to avoid this behavior is to specify to ruby what variable name should be protected by using a semicolon within the block.

my_local_variable = "hello"          2.times(method_param) do |num ; my_local_variable|   

my_local_variable = "goodbye"

puts "#{num} example string"
end
puts my_local_variable
Outputs: #=> "hello"

In the above case, by adding the variable name after the semicolon within the block parameter definition, we have told Ruby that we want to use my_local_variable within the block pointing to “goodbye” and we want to use my_local_variable outside of the block pointing to “hello”. By protecting your variable name with a semicolon, you’re simply stopping Ruby from redefining what my_local_variable points to.

Variables listed after the semicolon aren’t considered block parameters. They don’t get assigned when the block is executed. Think of them as reserved variable names for use within the block.

Special Case: where these rules don’t apply:

Variable shadowing is when you initialize a local variable and name it the same as the block parameter. Do not name your local variables the same as your block variables. It will almost always result in unintended results.

num = 53.times do |num|
puts "hello"
end
Output:
#=> "hello"
#=> "hello"
#=> "hello"
puts num
#=> 5

Outside of the block the num variable is defined and points to the integer object 5.

Within the block the #times method assigns values to the num block parameter.

In the above case, you might think that the block parameter num would be overridden, but it is instead preserved outside of the block.

A Take Away On Variable Shadowing:
The names of block parameters aren’t shared with local variables of the same name in the surrounding scope.

--

--

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