also
also

Reputation: 974

Ruby classes, include, and scope

How does including a module affect scope? Specifically, in this example:

module ModuleA
  class ClassA
    def initialize
      puts "test passed"
    end
  end
end

module ModuleB
  include ModuleA

  # test 1
  C = ClassA.new

  class ClassB
    def initialize
      c = ClassA.new
    end
  end
end

# test 2 and 3 fail without this
#include ModuleB

module ModuleC
  # this doesn't help
  include ModuleB

  # test 2
  ClassB.new

  # test 3
  ModuleB::ClassB.new
end

test 1 works fine, but test 2 and test 3 fail without the commented-out import ModuleB.

Upvotes: 1

Views: 5556

Answers (2)

m-sharp
m-sharp

Reputation: 17105

The keywords class, module and def are what is known as "scope gates". They create new scopes.

#!/usr/bin/env ruby

module ModuleA
  class ClassA
    def initialize
      puts "test passed"
    end
  end
end

module ModuleB
  include ModuleA

  # test 1
  c = ClassA.new  # this works as ModuleA has been included into this module

  class ClassB  # class is a scope gate, creates new scope
    def initialize  # def is a scope gate, creates new scope
      c = ModuleA::ClassA.new  # must fully qualify ClassA
    end
  end

  ClassB2 = Class.new do  # no scope gate
    define_method :initialize do # no scope gate
      c = ClassA.new  # this works, no need to fully qualify
    end
  end
end

b = ModuleB::ClassB.new
b2 = ModuleB::ClassB2.new

I began to understand scopes in Ruby after reading the book "Metaprogramming Ruby". It is truly enlightening.

Edit: In response to also's comment below.

A class is essentially a Ruby constant (notice that it is an object with a capitalized name). Constants have a defined lookup algorithm within scopes. The Ruby Programming Language O'Reilly book explains it well in section 7.9. It is also briefly described in this blog post.

Top-level constants, defined outside of any class or module, are like top-level methods: they are implicitly defined in Object. When a top-level constant is referenced from within a class it is resolved during the search of the inheritance hierarchy. If the constant is referenced within a module definition it does an explicit check of Object after searching the ancestors of the module.

That's why include ModuleB at the top level makes the class in ModuleB visible in all modules, classes and methods.

Upvotes: 7

andrewdotnich
andrewdotnich

Reputation: 16784

The reason is (I think) to do with bindings. The clue for me is that the following also won't work:

module ModuleB
 include ModuleA

 class ClassB
  def initialize
   c = ClassA.new
  end
 end

 ClassB.new
end

ClassA doesn't mean anything in the ClassB definition because it's not a constant in ClassB - the module has only been included in its parent module. Making this change should make everything work:

module ModuleB
  include ModuleA
  class ClassB
    def initialize
      c = ModuleA::ClassA.new
    end
  end
end

Upvotes: 0

Related Questions