Over a million developers have joined DZone.

A Deep Dive Into Ruby Scopes

DZone's Guide to

A Deep Dive Into Ruby Scopes

Scoping in Ruby is central to understanding the encapsulation it provides. This article provides a deep technical dive into how scopes are utilized in encapsulation.

· Web Dev Zone
Free Resource

Learn how to build modern digital experience apps with Crafter CMS. Download this eBook now. Brought to you in partnership with Crafter Software

The Ruby language was designed with a pure object-oriented approach. In Ruby, everything is an object.

Object-oriented design provides encapsulation for properties and actions. Encapsulation’s purpose is to protect methods and data from outside interference and misuse. With encapsulation, everything has certain scopes from which they may be utilized. Several categories of scope in Ruby are global, instance, and local scopes. These are the primary scopes within Ruby, but there are some outliers to the rules, such as class variables and the use of lexical scope with refinements.

Understanding Ruby scopes will go a long way in helping you fully leverage the language. I’ve compiled an in-depth overview to demonstrate how they can assist you with having a more beautiful code base.

"Understanding Ruby scopes will go a long way in helping you fully leverage the language."

Click To Tweet


Let’s start out with an example of encapsulation with local variables. (Local variables can be created when you use the equals sign for assignment, such as a = 1.) First, I’ll show some code written within a begin-end block which has no encapsulation and then a simple method definition which does.

  a = 4
puts a if defined?(a)
# 4

def local_var_example
  b = 4
puts b if defined?(b)
# => nil

Here, we can clearly see that when we assigned the value 4 to the local variable b that the variable did not exist beyond the scope of the function. This way, you can write as much code as you want within the method and not worry about variables leaking out. Local variables are very scoped; you cannot write a local variable before a standard method definition and retrieve it.

a = "example"
def a?
  puts a

#NameError: undefined local variable or method `a' for main:Object

If you want to draw in the environment and access local scope from outside a method definition, you may use closure definitions such as define_method, proc, or lambda.

word = "moo"

define_method �� do
  puts word
# moo

y = proc {puts word}
# moo

z = lambda {puts word}
# moo


Local variables take precedence over methods of the same name. To explicitly ask for the method result when there’s a local variable of the same name, you can use the send method.

a = 4
def a

puts a
# 4
send :a
# => 5


With Ruby being an object-oriented language, we get to create multiple object instances of their class definitions. Every Ruby object is its own singleton instance. And, I mean that purely by the definition of the word singleton: "a single person or thing of the kind under consideration." If you had two identical human clones, they would still each be their own individual existence. It’s the same way in Object-Oriented Programming.

When you want to define a kind of object that you plan on having more than one instance of, you write a classification of the object with class.

class Pet
  def mood

cat = Pet.new
dog = Pet.new
mouse = Pet.new

# => "hungry"
# => "hungry"

The method mood is an instance method. It’s defined only on all instances created from the class Pet. The one place it is not defined, however, is on the classification of Pet itself.

#NoMethodError: undefined method `mood' for Pet:Class

The Pet class is not an instance of itself; it is a classification of the kind of objects you can propagate from it. This is the intent of Ruby’s class design, where it provides the new method for you to instantiate individual instances of this kind of class. So the scope of methods defined in this way are all for the objects that will be created from it.

Now it is possible to write methods for the class Pet itself and not for the instances created from it. To do this, we define a method on self. Here are two ways you may do this:

class Pet
  def self.definition
    "Living thing belongs to an owner and is cared for.  Can be a plant, animal, or amoeba."

  class << self
    def free?
      "Not likely.  How much money do you have?"

# => "Living thing belongs to an owner and is cared for.  Can be a plant, animal, or amoeba."

# => "Not likely.  How much money do you have?"

dog = Pet.new
#NoMethodError: undefined method `free?' for #<Pet:0x00000002845330>

As you can see, the scope of methods defined on self within a class is only available on that singleton instance of the class. The created objects from this classification will not have those methods defined, as they were written specifically for the class Pet.

The same thing is true with modules. When you define a method with def mood in a module, it will only be available within the scope of classes that have it "included" (much like what the class method new does). And if you use the self identifier for defining a method on a module, it will only be available on that singleton instance of the module and not any class it is inherited in.

module Car
  def self.description
    "A vehicle of transportation"

  def engine

# => "A vehicle of transportation"
#NoMethodError: undefined method `engine' for Car:Module

class Boxcar
  include Car

betsy = Boxcar.new
# => "vroom"
#NoMethodError: undefined method `description' for #<Boxcar:0x000000025e61c0>

The scope of the methods defined are dependent on whether you assign it to the singleton instance of that object or let it be defined on instances from that object.

Singleton Instance

Saying "singleton instance" feels a bit repetitive to me, but it is important to specify so as not to confuse it with the Singleton Design Pattern or the singleton_class object which exists on most Ruby objects (which is not the singleton instance of the object it is on but is an extra singleton instance of its own).

Ruby is designed where everything is an instance of the Object class and, therefore, is a singleton instance, meaning that it exists as its own individual self. Yes, this may seem confusing at first, but once you see every module, class, and object as their own singleton instance which may or may not create more singleton instances from their definitions, then things become clearer.

Global Scope

When you write code at the top level, you are writing in global scope. Local variables will not cross over any scope, instance variables will become available to local methods, and methods and classes are available everywhere.

local_variable = 1     # not available in any other scope

@instance_variable = 2 # available within methods in the same scope

$global_variable = 3   # available everywhere

CONSTANT = 4           # available everywhere

def a_method           # available everywhere

class Klass            # available everywhere

module Mod             # available everywhere

All of these, except for global variables, can be encapsulated within singleton instances and maintain the same behavior as described above.

Now the way method definitions are managed in the global namespace is quite interesting. As you may recall that everything in Ruby is an object, they are all also instances of the Object class itself. So, the way that global methods are handled is that they are defined as private instance methods on the Object class.

def hey_you
  "it's me"

Object.private_method_defined? :hey_you
# => true

Array.send :hey_you
# => "it's me" 
12345.send :hey_you
# => "it's me"
nil.send :hey_you
# => "it's me"

This is also how classes and modules are made available at lower levels of scope.


Namespacing is the practice of placing code within the scope of another class or module. This is a good practice for both clarifying purpose, usage, and to protect code from potential clashes with other people’s code. You may reuse class or module names within a namespace without overwriting the outer definitions.

module Help
  def self.me
    "this is a general help"

module Dog
  module Help
    def self.me
      "woof woof woof woof"

# => "this is a general help" 
# => "woof woof woof woof"

You’ve protected your code from overwriting the other Help object by namespacing one specifically for Dog. If you want access to constants, classes, or modules at the top level of scope, you may use a double-colon :: before the object to access it.

CONSTANT = "world"

module Greeter
  CONSTANT = "hello"
  def self.greet
    puts CONSTANT + " " + ::CONSTANT

# hello world


In Ruby, you can reopen every object to add or make changes to it. Making changes outside of the scope of the original definition is known as monkey patching.

class Warn
  def warn
    "original behavior"

class Warn
  alias_method :_warn, :warn
  def warn
    "not " + _warn

# => "not original behavior"

The problem with monkey patching is that the changes happen globally. Any other place in your code base where others have used this object and method has now been changed. Very often this is how things will break; when depended-upon code is modified globally, everyone experiences the change.

Ruby’s solution for this is to use refinements. Refinements allow you to do the same thing as monkey patching but restrict the changes only to the very specific places you specify to use it. This way you won’t break anyone else’s code because your changes are lexically scoped.

module FixForMe
  refine String do
    alias_method :_to_s, :to_s
    def to_s
      "not " + _to_s

class A
  using FixForMe
  def a
    "to be".to_s

class B
  def b
    "to be".to_s

# => "not to be" 
# => "to be"

Here we have changed the behavior for String#to_s only where we’ve written using FixForMe, and the to_s behavior did not change in class B. This is how lexical scope works.

Lexical scope only goes as far as the visual block of code before you. If you reopen a class and don’t write the using syntax in it, the refinements behavior will not be there even if you’ve previously used it in the same class. Refinements are well worth using to avoid the pains that monkey patching may bring.

Binding: The Exception to Scope

The Binding object is the only object that lets you pass and modify local variables out of scope. To create a binding, you simply type the method binding, and it creates a binding of the local environment. You may pass this binding into other scopes and access the local variables from where the binding was instantiated.

module A
  def self.a(bnd)
    printf "%s\n", bnd.local_variables

    x = bnd.local_variable_get ��
    y = bnd.local_variable_get :y
    z = bnd.local_variable_get :z

    printf "%s\n", [x, y, z]

    bnd.local_variable_set(:z, x + y)

module B
  def self.b
    x = 1
    y = 7
    z = 0


    puts "x + y = #{z}"

# [:x, :y, :z]
# [1, 7, 0]
# x + y = 8

The local variable z has been changed from a different scope in A, and the result was within B.

Class Variables

Class variables are rarely used as the scope is broadened to all instances of the same class. If you were to modify the value of a class variable in one instance, it will be changed for all other instances.

class Building
  def initialize
    @@state ||= :built

  def state(value = nil)
    @@state = value if value

library = Building.new
office = Building.new

# => :built
# => :built

office.state :demolished
# => :demolished
# => :demolished

As you can see, using class variables may cause surprise values in your other classes if they aren’t managed properly. It would probably be wise to think of using these variables for either read-only values or by having a thread-safe system in place.


Ruby is a language that has been designed to make programmers happy, and understanding its scope gives you full leverage in using the language. With it, you may employ many strategies in design that help you toward having a more beautiful code base.

I recommend studying good object-oriented design. Each language/feature is a tool, and tools are most effective when understood and mastered. Encapsulation as a core design in Ruby will serve you well if you use it in the way it was designed to be used. Thankfully Ruby is a very flexible language, so we do have a lot of free reign in how we use it.

Crafter is a modern CMS platform for building modern websites and content-rich digital experiences. Download this eBook now. Brought to you in partnership with Crafter Software.

ruby ,encapsulation ,scope ,deep dive ,object oriented programming

Published at DZone with permission of Moritz Plassnig, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.


Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.


{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}