Understanding Variable Scope & Binding Objects in Ruby

Scope is an important concept to understand for all Ruby developers.

Why?

Because it’s the source of many error messages & confusion.

What is scope?

Scope refers to what variables are available at any given point in time.

Different kind of variables have different scopes.

A scope can be very narrow (local variables) or very wide (global variables).

You want to use the narrowest scope possible to avoid problems with state mutation & name collision.

What follows is a list of examples of how scope affects your Ruby code.

Local Variable Scope

A local variable has the most narrow scope.

Specifically, local variables defined inside methods will no longer exist after the method returns.

Here’s an example:

a = 50

def apple
  a = 100

  puts a
end

What do you think apple will print?

Here’s the answer:

When you call apple it’ll always print 100.

The fact that a is defined outside the method as 50 doesn’t have any impact on the a variable inside the method.

They are different variables.

You can think of scope as a soap buble

This first a = 50 is in one bubble, then when you call a method, ANY method, you enter a new EMPTY bubble.

You don’t bring any local variables over the new bubble.

And when the method reaches the end…

The bubble pops.

The variables inside the bubble disappear & you can’t access them.

That’s how local variables work.

Instance Variable Scope

Instance variables have a wider scope.

Specifically, they are used for sharing data inside a Ruby object.

Here’s an example:

class Fruit
  def more_juice
    @quantity = 100
  end

  def less_juice
    @quantity = 50
  end
end

In this example, @quantity is the same variable for both the more_juice & less_juice methods.

It’s a shared value between methods.

But outside this class, and even on different Fruit objects, @quantity is going to be different.

Example:

orange = Fruit.new
apple  = Fruit.new

orange.more_juice
apple.less_juice

Every object has its own set of instance variables.

So in this example, orange is going to have a @quantity of 100, and apple is going to have a @quantity of 50.

Just like different persons have different names, age, country, etc.

How Scope Works in Blocks

Blocks are very interesting when it comes to scope.

If we follow our bubble analogy again, what a block does is bring over local variables from the current bubble.

You can access & change them.

Example:

a = []

3.times { a << 1 }

p a
# [1, 1, 1]

BUT...

The bubble still pops, removing any NEW local variables that were created inside the block.

Isn't that interesting?

Here's an example of what I mean:

1.times { b = [1,2,3] }

b
# NameError

Not only that, but blocks will also carry with them the bubble at the point they were created.

An effect known as "closure".

Remember, this "bubble" is a collection of all the variables that can be accessed at a specific point in the source code.

It's the scope itself, encapsulated as an object.

We call this a Binding in Ruby.

How to Use Ruby Bindings

One more concept I'd like to share with you in this article is about bindings.

You can save all of these bubbles we have been talking about in a Ruby object, an object of the Binding class.

Example:

def banana
  a = 100

  binding
end

banana.class
# Binding

This binding object is the bubble.

You can even look into the bubble & see what's in there.

Example:

banana.send(:local_variables)
# [:a]

What About Rails Scopes?

A scope in Rails is a different thing than scope in Ruby.

So, what is a scope in Rails?

It's a way to name a custom database query, composed of ActiveRecord methods.

Here's an example:

class User < ApplicationRecord
  scope(:with_email) { where.not(email: nil) }
end

User.with_email

Don't confuse this with the concept of scope in Ruby 🙂

Summary

You have learned about scopes & binding objects in Ruby!

Remember that scope defines what variables you can access at any given point in time.

It's like a bubble, local variables have their own bubbles, while objects share another bubble for instance variables.

Thanks for reading.

3 thoughts on “Understanding Variable Scope & Binding Objects in Ruby”

  1. why will orange.more_juice and apple.less_juice return different quantities? doesn’t there need to be an initializer?

Comments are closed.