Why does Ruby constant lookup sometimes fail when using :: notation? [duplicate] - ruby

I just got stuck on this for a while. Take this base:
module Top
class Test
end
module Foo
end
end
Later, I can define classes inside Foo that extends Test by doing this:
module Top
module Foo
class SomeTest < Test
end
end
end
However, if I try to minimize indentation by using :: to specify the module:
module Top::Foo
class Failure < Test
end
end
This fails with:
NameError: uninitialized constant Top::Foo::Test
Is this a bug, or is it just a logical consequence of the way Ruby resolves variable names?

Is this a bug, or is it just a logical consequence
It's a "quirk". Some consider it a bug.
Parent scopes used for looking up unresolved constants are determined by module nesting. It just so happens that when you use module Top::Foo, it creates just one level of nesting instead of two. Observe:
module Top
module Foo
class SomeTest
Module.nesting # => [Top::Foo::SomeTest, Top::Foo, Top]
end
end
end
module Top::Foo
class SomeTest
Module.nesting # => [Top::Foo::SomeTest, Top::Foo]
end
end

This is expected. Using :: changes the scope of constant lookup and expects Test to be defined under Top::Foo.
To get the expected result, you could write:
module Top::Foo
class SomeTest < Top::Test
end
end
or:
module Top
class Foo::SomeTest < Test
end
end
or even:
class Top::Foo::SomeTest < Top::Test
end

Related

Ruby Class namespacing with modules: Why do I get NameError with double colons but not module blocks?

I am working with a lot of pre-existing files, classes, and modules and trying to come up with better namespacing for the different components of the framework. I've been using modules as a way to namespace mainly because this seems like the standard convention (and being able to 'include' different parts of the framework could be useful).
The problem is that there was a ton of classes underneath the global namespace that should exist underneath a module. For example, let's say there is a class that was simply defined as:
class FirstClass
def meth
puts "HELLO"
end
end
But now I want to have this class within a module:
Using Double Colons:
module Foo; end
class Foo::FirstClass
def meth
puts 'HELLO'
end
end
Using Module Blocks:
module Foo
class FirstClass
def meth
puts 'HELLO'
end
end
Using double colons is a lot cleaner and also a lot easier to implement since I am changing many class definitions. Both of these ways work and I thought that they are both effectively the same thing, but evidently they are not. The double colon method seems to result in a different namespace within each class compared to the module block. For instance, with two classes underneath "Foo":
Using Module Blocks:
module Foo
class FirstClass
def meth
puts 'HELLO'
end
end
class SecondClass
def meth
FirstClass.new.meth
end
end
end
Foo::SecondClass.new.meth
Using Double Colons:
module Foo; end
class Foo::FirstClass
def meth
puts 'HELLO'
end
end
class Foo::SecondClass
def meth
FirstClass.new.meth
end
end
Foo::SecondClass.new.meth
The code works when using module blocks, but doesn't work with double colons. With the double colons, NameError is raised because it resolves FirstClass as Foo::SecondClass::FirstClass (instead of Foo::FirstClass), which doesn't exist.
This can easily be solved by including Foo in SecondClass, but how come this isn't done by default?
Note: I'm using Ruby 2.1.5, which I know is outdated, but I get the same results on repl.it with ruby 2.5.5p157: https://repl.it/#joep2/Colon-vs-Block-Namespacing
It may seem counter-intuitive, but constant lookup in Ruby is done using current lexical scope, i.e. the current lexical nesting level (location in the source code), not the semantic nesting level.
This can be tested by inspecting Module.nesting, which prints the current lexical scope:
class Foo::SecondClass
pp Module.nesting # -> [Foo::SecondClass]
end
module Foo
class SecondClass
pp Module.nesting # -> [Foo::SecondClass, Foo]
end
end
Since Ruby uses this nesting level for symbol lookup, it means in the situation where you try to look up FirstClass within nesting [Foo::SecondClass], Ruby will not find it.
However when you try to look it up within nesting [Foo::SecondClass, Foo], it will find FirstClass under Foo, just like you expect.
To get around this, you could do:
class Foo::SecondClass
def meth
Foo::FirstClass.new.meth
end
end
Which will now work as you expect, since you provided the necessary lookup hint for FirstClass, and told Ruby it is inside Foo.

RSpec: why included modules make available only methods, but not constants?

I have a module including constants that I want to use in the unit tests across a test suite.
What I don't understand is why, if I include the module, constants are not available, but methods (if I add any to the module) are.
Example:
module Namespace
module Constants
K = 1
def mymethod; end
end
end
describe Namespace::Subject do
include Namespace::Constants
context "Context" do
it "should execute" do
mymethod # succeeds
K # fails
end
end
end
The same principle works in a console:
2.5.5 :008 > include Namespace::Constants
=> Object
2.5.5 :010 > K
=> 1
Constants defined within a namespace can be called by prefacing the constants from within the namespace.
describe Namespace::Subject do
include Namespace::Constants
context "Context" do
it "should execute" do
mymethod # succeeds
Namespace::Constants::K # succeeds
end
end
end
The reason this makes sense is that it should follow OOP design whereby a method defined within a class or module should be accessible only from within the namespace where it is defined. Here is a simple example:
class A
FOO = 'foo'
end
class B
FOO = 'bar'
end
a = A.new
b = B.new
FOO = 'baz'
a.class::FOO
=>"foo"
b.class::FOO
=>"bar"
FOO
=>"baz"

Load constant from string, as resolved in current scope

Say I have the following structure:
module Library
class DSL
def met(str)
# load `str` here; for this case, `MyApplication::MyClass`
end
end
class Superclass
extend DSL
end
end
module MyApplication
class MySubclass < Library::Superclass
met 'MyClass'
end
class MyClass
end
end
Inside Library::DSL#met I need to load constant str, which is provided as string. Obviously, it does not work if I simply do Object.const_get(str).
I need somehow to fully resolve str in the context of the current scope (MyApplication::MySubclass); str would therefore be resolved as MyApplication::MyClass. I need to fully resolve this because later I need to use this constant outside this namespace.
When resolving a constant, Ruby first checks the current nesting of modules and then the ancestors of the innermost module.
This can be replicated by traversing Module.nesting and Module.nesting.first.ancestors (in that order). If a module defines the constant, we can get its value via const_get:
class A
class B
def met
str = 'C'
mods = Module.nesting
mods.concat(mods.first.ancestors)
mod = mods.find { |c| c.const_defined?(str, false) }
mod.const_get(str)
end
end
class C
end
end
A::B.new.met #=> A::C

Refer to a class without explicitly mentioning its namespace

I have a class within several modules: This::Is::A::Long::ClassName. Is there any way, within one script or method, to make ClassName available without having to reference the namespace? Instead of writing:
This::Is::A::Long::ClassName.do_something
This::Is::A::Long::ClassName.do_something_else
This::Is::A::Long::ClassName.do_something_different
is anything as below possible?
include This::Is::A::Long
ClassName.do_something
ClassName.do_something_else
ClassName.do_something_different
If you are using modules for namespacing, the code you posted should work, see this example:
module Long
module Name
class ClassName
end
end
end
ClassName
# => ... uninitialized constant ClassName (NameError)
include Long::Name
ClassName
# => Long::Name::ClassName
Ruby has no equivalent to C++ using namespace, and you can not reference a class without being in the right namespace, but you can always make it a variable since a class is also an object
long_class = This::Is::A::Long::ClassName
long_class.do_something
long_class.do_something_else
# and so on
EDIT
An include does not put you in the right namespace, it includes the methods & classes in the module you are including (that is, it puts the module in the classes ancestors) and is therefore most certainly not suitable for your needs: Consider the following:
module This
module Is
module A
def foo
puts 'A#foo'
end
def bar
puts 'A#bar'
end
class ClassName
end
end
end
end
Now, you may not want to write This::Is::A::ClassName in another class, let's say:
class C
def foo
puts 'C#foo'
end
end
class B < C
include This::Is::A
end
Now, B.new.foo still puts out C#foo, right? Wrong. Since you included the module, the method has been overwritten.

Get a reference to the enclosing module in ruby

How do you get a reference to the enclosing module in ruby?
module Foo
##variable=1
def variable
##variable
end
class A
def somemethod
puts "variable=#{Foo.variable}" #<--this won't run, resolving Foo
# as the class instead of the module
end
end
class Foo
... # doesn't matter what's here
end
end
I ran into this question caused by naming confusion. While the names are easy enough to fix, I"m wondering what the "correct" way is to do this in ruby. If I try to run this it seems like ruby is trying to resolve Foo.variable as Foo::Foo.variable which of course fails. It seems like there should be a simple way in the language to refer to the outer module method.
You can get the outer module reference by adding the :: prefix to Foo:
::Foo.variable
In your example code:
module Foo
##variable=1
def variable
##variable
end
class A
def somemethod
puts "variable=#{::Foo.variable}"
end
end
class Foo
... # doesn't matter what's here
end
end

Resources