Ruby classes, include, and scope

2019-06-02 10:13发布

问题:

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.

  • Why is ClassA in scope inside of ModuleB (test 1) but not in ClassB?
  • Why does the import ModuleB bring ClassA into scope in ClassB?

回答1:

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.



回答2:

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