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.
why will
orange.more_juice
andapple.less_juice
return different quantities? doesn’t there need to be an initializer?Hi Bob,
the initializer is optional, you don’t need to “create” the variable.
More details on this article.
They return different quantities because calling one of these two methods gives
@quantity
a new value.Exactly