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
?
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.
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