Nesting versus namespace operator - ruby

I had believed that
module A
module B
...
end
end
and (provided that module A is defined in advance):
module A::B
...
end
are equivalent, but it turned out not. Given:
module A
C = :foo
end
the two forms behave differently:
module A
module B
puts C
end
end
# => :foo
module A::B
puts C
end
# => NameError: uninitialized constant A::B::C
What is the logic behind this difference? Particularly, why cannot A::B access A::C in the second form (although it can in the first form)?
Update
I found some related posts on the Ruby core:
https://bugs.ruby-lang.org/issues/5148
https://bugs.ruby-lang.org/issues/6810

Module::nesting is a good tool to answer you here. As the doc is saying - Returns the list of Modules nested at the point of call.
Here is the reason why the below one is not working :
module A
C = :foo
end
module A::B
puts C
end
To let this explain I would write something like below :
module A
C = :foo
end
module A::B
$a = Module.nesting
end
$a # => [A::B]
So from the output of $a,it is clear that why the constant C is not accessible.
The reason why the below code does work :
module A
C = :foo
end
module A
module B
puts C
end
end
To let this explain I would write something like below :
module A
C = :foo
end
module A
module B
$a = Module.nesting
end
end
$a # => [A::B, A]
So from the output of $a,it is clear that why the constant C is accessible.
Read here(Everything you ever wanted to know about constant lookup in Ruby) is an wonderful resource for the same I just found.

Related

Fetch dynamically a defined constant

According to Ruby const_get's documentation, this method returns value for a constant.
So to reproduce the following behavior:
module A
FOO = 42
module B
def self.call
FOO
end
end
end
A::B.call # => 42
I wrote this code:
A = Module.new
A.const_set(:FOO, 42)
A.const_set(:B, Module.new do
def self.call
const_get(:FOO)
end
end)
But when I call the method, I've got a NameError exception:
A::B.call # => exception
# NameError (uninitialized constant A::B::FOO)
# Did you mean? A::FOO
It looks like FOO and const_get(:FOO) are not exactly the same.
Is there another way to find the FOO constant looking recursively in the parents modules?
Edit:
I also have this trouble with const_get doing directly:
module A
FOO = 42
module B
def self.a_method
const_get(:FOO)
end
end
end
A::B.a_method # => exception
# NameError (uninitialized constant A::B::FOO)
# Did you mean? A::FOO
We can use the method Module::nesting to better understand what is going on here.
module A
FOO = 42
module B
puts "Module.nesting = #{Module.nesting}"
def self.call
FOO
end
end
end
displays
Module.nesting = [A::B, A]
This tells us that, when executing A::B.call, in searching for the value of FOO, Ruby first looks in module A::B, does not find it, then searches in module A where she does find it.
Your third code fragment also creates a nested module A::B:
module A
FOO = 42
module B
puts "Module.nesting = #{Module.nesting}"
def self.a_method
const_get(:FOO)
end
end
end
displays
Module.nesting = [A::B, A]
A::B.a_method executes A::B.const_get(:FOO). The doc for Module#const_get states, "Checks for a constant with the given name in mod." Therefore, if it fails to find the constant in the module that is its receiver it gives up without examining any modules within which the given module is nested.
Your second code fragment is similar to the third in that it fails to find the value of FOO because of the behaviour of const_get.

Instance_eval doesn't have access to class in module

I've came across an issue with instance_eval and module inclusion.
Please take a look at the following code:
module B
class C
def initialize
puts 'This is C'
end
end
def hello
puts 'hello'
end
end
class A
include B
def initialize(&block)
hello
C.new
instance_eval(&block)
end
end
A.new do
hello
C.new
end
When I run this code I get
hello
This is C
hello
-:25:in `block in ': uninitialized constant C (NameError)
from -:19:in `instance_eval'
from -:19:in `initialize'
from -:23:in `new'
from -:23:in `'
I understand that it has to do with bindings and how methods and objects are binded to class. What I cannot understand is how come I have access to C within A, but not when I eval the block. I would expect them to be in the same scope.
Thanks!
In the below code
A.new do
hello
C.new
end
You are are trying to create the object of C, as if it is defined in the scope of the class Object. No, it is not. class C is defined inside the scope of module B. And you need to tell that explicitly as B:C.new.
Above explanation is for the error -:25:in 'block in ': uninitialized constant C (NameError).
What I cannot understand is how come I have access to C within A ?
Module#constants has the answer for you :-
Returns an array of the names of the constants accessible in mod. This includes the names of constants in any included modules (example at start of section), unless the inherit parameter is set to false.
Look at the example :
module M
X = 10
class D; Y = 10 ;end
end
class C
include M
def initialize
p self.class.constants
end
end
C.new
# >> [:X, :D]
Now applying to your example :
module B
class C
def initialize
'This is C'
end
end
def hello
'hello'
end
end
class A
include B
def initialize(&block)
p self.class.constants
C.new # it will work
#instance_eval(&block)
end
end
A.new do
hello
C.new # but this will throw error.
end
# it confirms that C is accessible, within the method.
# That is the same reason you got the output "This is C"
# >> [:C]
Hope that helps.

How does Ruby modules lookup path behave?

module A
def go
p 'go'
end
end
module B
extend self
include A
def start
go
end
end
# doesn't work
# B.start
module C
include A
extend self
def start
go
end
end
# works
C.start
module Constants
HELLO = "Hello!"
end
module D
extend self
include Constants
def start
p HELLO
end
end
# works
D.start
Could someone please explain this to me? How come C.start works if I include A first before extend self? How come the order works if I'm only including Constants?
Any help would be appreciated, thanks.
Why D.start work?
Because extend self adds method start to the singleton class of D. Look the below 2 examples:
without extend self
module Constants
HELLO = "Hello!"
end
module D
#extend self
include Constants
def start
p HELLO
end
end
D.singleton_methods # => []
with extend self
module Constants
HELLO = "Hello!"
end
module D
extend self
include Constants
def start
p HELLO
end
end
D.singleton_methods # => [:start]
why B.start doesn't work ?
This is because you first added the methods of module B to its singleton methods, then included the methods of module A, so #go is not added to the singleton methods of B. Take a look at the below 2 examples.
first include A then extend self.
B.start will work here because, you first included methods from A to B. Then adding all the methods(methods which are defined in B and included from A) of B to the singleton class of B. Thus call to #go is succeed within B.start.
module A
def go
p 'go'
end
end
module B
include A
extend self
def start
go
end
end
B.singleton_methods # => [:start, :go]
first extend self, then include A.
B.start will not work here because, you first extended methods from A to B, then only all the methods from B has been added to the singleton class of B. After that you included methods from A, this is the reason #go not added to the singleton class of B. Thus call to #go is not succeed within B.start.
module A
def go
p 'go'
end
end
module B
extend self
include A
def start
go
end
end
B.singleton_methods # => [:start]
I hope why C.start works is now understood.

calling method in nested module included in class

I have the following configuration:
module A
module B
def foo
puts "foo"
end
end
end
class C
include A
end
c = C.new
c.foo
NoMethodError: undefined method `foo' for #<C:0x8765284>
How do I achieve the above?
Thanks.
Module B is "defined" inside of A, it is not "included" in A. This is why you don't get access to the #foo instance method when you include the A module in C. You could do the following:
class C
include A::B
end
C.new.foo
You can use the included callback to have B include when A is included.
module A
def A.included(klass)
klass.include B
end
module B
def foo
puts "foo"
end
end
end
class C
include A
end
and the following would work
c = C.new
c.foo

Override module method from another module

I want to override a method from a module A from another module B that will monkey-patch A.
http://codepad.org/LPMCuszt
module A
def foo; puts 'A' end
end
module B
def foo; puts 'B'; super; end
end
A.module_eval { include B } # why no override ???
class C
include A
end
# must print 'A B', but only prints 'A' :(
C.new.foo
module A
def foo
puts 'A'
end
end
module B
def foo
puts 'B'
super
end
end
include A # you need to include module A before you can override method
A.module_eval { include B }
class C
include A
end
C.new.foo # => B A
Including a module places it above the module/class that is including it in the class hierarchy. In other words, A#foo is not super of B#foo but rather the other way round.
If you think of including a module as a way of doing multiple inheritance this makes sense, include SomeModule is a way of saying, "Treat SomeModule like it is a parent class for me".
To get the output you wanted you need to reverse the inclusion so that B includes A:
module A
def foo; puts 'A' end
end
module B
def foo; puts 'B'; super; end
end
B.module_eval { include A } # Reversing the inclusion
class C
include B # not include A
end
puts C.new.foo
Edit in response to comment:
Then either include both A and B in C with B included after A:
# A and B as before without including B in A.
class C
include A
include B
end
or patch A in C itself and don't bother with B.
# A as before, no B.
class C
include A
def foo; puts 'B'; super; end
end
The only way for this to work is if the method lookup on C is C -> B -> A and there is no way to do this without including B into C.
This is also one solution to your question. I am trying to achieve with module hooks included. When you include the module A into class C. included callbacks defined in module A is called and executed. We included the module B on-fly. So our module A method foo is overridden by Module B foo to print the superclass module method just called super.
module A
def self.included klass
klass.send(:include, B)
end
def foo
puts 'A'
end
end
module B
def foo
super
puts 'B'
end
end
class C
include A
end
C.new.foo #out put A,B
Another way to accomplish this is to include module B when module A is included.
module A
def foo
puts "this should never be called!"
"a"
end
end
module B
def foo
"b"
end
end
module A
def self.included(base)
base.class_eval do
include B
end
end
end
class C
include A
end
C.new.foo # "b"

Resources