Ruby’s Scope Gates (def, class, and module keywords)

The scope of a program changes whenever you hit the def, class, and module keywords, the three Ruby ‘scope gates’. The scope determines the value of self and local variables that are accessible at any given time. Read my blog post on the main object and Ruby’s self keyword if you need some review before diving deeper into Ruby scopes.

# filename: ex1.rb
# top level scope
p self # => main (the top-level scope object)
x = 42 # local variable bound to main object

def hi # begin method scope
  x
end # end method scope

# back in top level scope
p hi() # => NameError: undefined local variable or method `x' for main:Object

# x is still accessible in the top level scope
p x # => 42

When the ex1.rb file is run (example above) a new scope is opened with the keyword self pointing to the main object. This is referred to as the top-level scope and all local variables and methods defined in the top-level scope are bound to the top-level main object. When the hi() method is defined, a new scope is opened that doesn’t have access to any of the local variables defined in the top-level ‘outer’ scope. After the hi() method is defined, the program reverts back to the top-level scope and the local variables bound to the main object are accessible again.

# filename: ex2.rb

# top level scope
class A # begin class scope
  y = 99
  p y # => 99

  def self.hey # begin method scope
    y # => error because y is no longer in scope
  end # end method scope

  # back in class scope
  p y # => 99 (local variables are accessible again)
end # end class scope
# back in top level scope

Local variables defined in the class scope are accessible anywhere within the class scope. When the self.hey() method opens a new scope, none of the local variables defined in the class scope are accessible. The class scope can be thought of as an ‘outer scope’ and the method scope is an ‘inner scope’. None of the local variables defined in the outer scope are accessible by the inner scope.

Instance variables are bound to objects and are accessible whenever the scope causes the self keyword to equal the right object.

# filename: ex3.rb

# top level scope
p self # => main
@hero = 'robert shiller' # @hero is bound to main object

def hero # begin method scope
  p self # => main
  @hero # accessible because implicit self is the main object
end # end method scope
# back in top level scope

In ex3.rb, the @hero instance variable is set in the top level scope and accessible in the hero() method, even though the scope has changed. Instance variables are bound to objects and are fetched for the implicit self receiver unless an explicit receiver is used. In ex3.rb, the self keyword points to main in both the top level scope and the hero() method, so the @hero instance variable is accessible in the hero() method.

When modules are included in a class, self is assigned to an instance of the class, so the class’s instance methods have access to the methods and instance variables defined in the module.

# filename: ex4.rb
module M # begin module scope
  def ok_setter # begin method scope
    @ok = 'ok, alright'
  end # end method scope
end # end module scope

class C # begin class scope
  include M
  def ok_reader # begin method scope
    # the module's instance methods are accessible
    ok_setter
    @ok
  end # end method scope
end # end class scope

Nesting modules and classes encapsulates code and decreases the liklihood of variable collisions (i.e. two classes with the same name in the same scope). The following example illustrates all the scopes that are opened and closed when modules and classes are nested.

# filename: ex5.rb
# top level scope
module Wrapper # begin Wrapper
  module Container # begin Container
    class A # begin A
      def hi # begin method
        'Wrapper::Container::A#hi'
      end # end method
    end # end A
  end # end Container

  class A # begin A
    def hi # begin method
      'Wrapper::A#hi'
    end # end method
  end # end A
end # end Wrapper
# end top level scope

A new Ruby scope is created whenever the keywords def, module, and class are used. As a result, these keywords are referred to as ‘scope gates’ in Ruby. Since these keywords are used all the time, new scopes are created constantly. In ex5.rb, there are two A classes (Wrapper::Container::A and Wrapper::A), but they’re defined in different scopes, so the constant variable names do not collide.

Navigating a complex jungle of Ruby scopes can be confusing, especially when dealing with deep nesting and multiple files. Master the scope basics and you’ll save yourself from some ‘banging your head against the wall’ situations.

Advertisements

One thought on “Ruby’s Scope Gates (def, class, and module keywords)

  1. Pingback: Bypassing Ruby Scope Gates | Ruby/Rails Programming

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s