I have a method which needs to be able to access the nesting hierarchy of a module definition. Suppose I have this definition:
module A
module B
module C
end
end
end
I'm trying to write a method such that, if a reference to C is passed to the method, it is able to return the result of Module.nesting as if it was called inside the definintion. For example:
get_nesting(A::B::C) # => [A::B::C, A::B, A]
However, I can't figure out how to call Module.nesting in a different context. I've tried using instance_exec, but this just returns the nesting in the current scope.
module X
def self.get_nesting(m)
m.instance_exec { Module.nesting }
end
end
X.get_nesting(A::B::C) # => [X]
I want this to return [A::B::C, A::B, A] instead.
Is there a way to get the nesting for a module in this way, using Module.nesting or otherwise?
Try this:
module A
module B
module C
Module.nesting
end
end
end
#=> [A::B::C, A::B, A]
module A::B
module C
Module.nesting
end
end
#=> [A::B::C, A::B]
The reason A is not included in the last return value is that nesting depends on the structure of the code ("lexical") and not on the parent-child relationships of the modules. For that reason I think any method that causes self to equal the given module (the method's argument), and then execute Module.nesting, is doomed to failure.
You can, however, do the following.
def get_nesting(mod)
a = mod.to_s.split('::')
a.size.times.map { |i| Module.const_get(a[0..i].join('::')) }.reverse
end
get_nesting(A) #=> [A]
get_nesting(A::B) #=> [A::B, A]
get_nesting(A::B::C) #=> [A::B::C, A::B, A]
get_nesting(A::B::C).map { |m| m.class }
#=> [Module, Module, Module]
Considering that this depends on Module#to_s, this would be classified as a kludge.
Related
Say we have a class that we cannot change,
class C
def foo
super
puts "Low!"
end
end
We'll need to dynamically define the method foo in something that we'll be able to inject into C's ancestry chain. The behavior of super must be specific to a given object, not class-wide. We'll be able to enclose that logic into an anonymous module (let's name it for now):
module M
def foo
puts "High!"
end
end
Extending an instance of C with the module:
c = C.new
c.extend(M)
c.foo
# High!
will not work since we've put the method from the module before the method we've defined in the class. Looking at our object's ancestors
c.singleton_class.ancestors
# => [#<Class:#<C:0x00005652be630b20>>, M, C, ...]
I came up with an ugly workaround, which is redefining the methods from our class in our singleton_class, i.e.
c.define_singleton_method(:foo, c.class.instance_method(:foo))
c.foo
# High!
# Low!
While this works (does it work? I've tested it for a bit and it seems to, but I'm no longer certain), I wonder whether I'm missing something obvious and there's an easier way to dynamically define a "super" method for an instance of a class.
To be clear, we want to be able to extend another instance of C with another module, i.e.
C.new.extend(Module.new do
def foo
puts "Medium!"
end
end).foo
# Medium!
# Low!
and have its output not tainted by other instances.
Now that I understand you're trying to work around an issue in some third-party code, I can suggest a more reasonable solution. In the code below, I'm thinking that B and C are defined by the gem and you don't want to change their source code, but you want to inject some code into the place where C#foo calls B#foo.
class B
def foo
puts "Highest!"
end
end
class C < B
def foo
super
puts "Low!"
end
end
module CE
def foo
super
foo_injected
end
end
C.include(CE)
module M
def foo_injected
puts "High!"
end
end
c = C.new
c.extend(M)
p c.singleton_class.ancestors
c.foo
The output is:
[#<Class:#<C:0x000055ce443366a8>>, M, C, CE, B, Object, Kernel, BasicObject]
Highest!
High!
Low!
When using the Module.nesting method, I can return a list of nested Modules from the point of call. The only examples I saw in documentation and elsewhere show the method call being placed and run from within the nested module definition:
module M1
module M2
Module.nesting #=> [M1::M2, M1]
end
end
In the following example:
module A
module B; end
module C
module D
# I want to access the name-space chain from this point
end
end
end
# But how can I reference it from our here?
# A::C::D .... ?
is there any way to check the name-space chain within the module once it has been defined?
The only examples I can see create a variable within the definition to hold the resulting call to be referenced again outside of the definition:
module A
module B; end
module C
module D
$chain = Module.nesting
end
end
end
p $chain
#=> [A::C::D, A::C, A]
Is there a method to looking this name-spacing chain up, without creating a variable in the definition to be referenced later?
In Ruby, the way to "remember" the value of an expression is to assign that value to a variable.
Also, the value of a module definition is the value of the last expression that was evaluated inside the body.
If you put the two together, you get something like this:
chain = module A
module B; end
module C
module D
Module.nesting
end
end
end
p chain
# [A::C::D, A::C, A]
Consider the following nested modules mentioned in the question.
module A
module B; end
module C
module D
Module.nesting
end
end
end
#=> [A::C::D, A::C, A]
Now try this:
module A::C::D
Module.nesting
end
#=> [A::C::D]
Module::nesting resolves the nesting of modules lexicographically, meaning that modules are ordered by their point of inclusion, beginning at A::C::D and working outward. Module::nesting does not "remember" the nesting from where the module was defined; it merely examines the code locally. This is in contrast to subclassing.
Perhaps you can obtain the information you need by using the method ObjectSpace::each_method.
outer_module = A
ObjectSpace.each_object(Module).select { |m| m.to_s.match?(/\A#{outer_module}::/) }
#=> [A::B, A::C::D, A::C]
In Ruby, constant lookup is affected by nesting and methods retain their nesting.
For example, if I have these modules:
module A
X = 1
end
module B
X = 2
end
module Foo
end
I can define a method Foo.a that has a nesting of [A, Foo]:
module Foo
module ::A
Module.nesting #=> [A, Foo]
def Foo.a
X
end
end
end
Foo.a #=> 1
And a method Foo.b that has a nesting of [Foo, B]:
module B
module ::Foo
Module.nesting #=> [Foo, B]
def Foo.b
X
end
end
end
Foo.b #=> 2
The difference becomes apparent if I define Foo::X:
Foo::X = 3
Foo.a #=> 1 <- still resolves to A::X
Foo.b #=> 3 <- now resolves to Foo::X
But how do I determine the nesting of a given method?
This would work:
Foo.method(:a).to_proc.binding.eval('Module.nesting')
#=> [A, Foo]
Foo.method(:b).to_proc.binding.eval('Module.nesting')
#=> [Foo, B]
Tested with Ruby 2.2.1 and 2.3.1.
It doesn't work with Ruby 2.1.5.
You are thinking about it the wrong way. There is no such thing as "nesting of methods". Constants are nested somewhere. Nesting has path resolution connected to names of modules and classes. Methods are contained within a module/class (be it "normal" or singleton).
Where a method is placed is semantical. There is a concept, similar to self, which determines where a method will be defined, called default definee. There is no keyword for it, but it's roughly equivalent to:
kind_of?(Module) ? name : self.class.name
Where a constant is placed/searched for is purely syntactical. When you refer to X, it does not care the least bit if you placed it a method or not:
DEEP_MIND = Object.new
module Foo
X = 42
end
module Foo
module Bar
def DEEP_MIND.talk
p X
end
end
end
DEEP_MIND.talk # => 42
module Foo::Bar
def DEEP_MIND.talk
p X
end
end
DEEP_MIND.talk # => uninitialized constant
All it cares about is what the "current nesting" on the line of the code where you tried to reference it.
Now, if you actually wanted to find "the current nesting inside the body of the method", then you need some way to pretend you are actually there.
Sadly, I don't think there is any other way other than the one showed in #Eric's answer. Using block with instance_eval/instance_exec will give you the nesting of where the block was defined.
So far I know to get a child module, one should performs such an operation:
module ParentModule
module Foo
# to be implemented
def self.get_bar
::ParentModule::Bar
end
end
module Bar
# to be implemented
end
end
However is there a way to get a sibling module without referencing a parent one? Something like this:
module Foo
def self.get_bar
::Bar # doesn't work actually
end
end
It's simpler than you thought
module ParentModule
module Foo
def self.get_bar
Bar
end
end
end
The :: you tried with, it instructs ruby to look this name up in the top-level scope. If you omit it, ruby will first look in the current scope, then its parent, then its parent, and all the way to the top-level.
So, ParentModule::Foo::Bar will not be found, but ParentModule::Bar will be.
mod = ParentModule::Bar
#=> "ParentModule::"
parent_str = mod.to_s[/.+::/]
#=> "ParentModule::"
kids = ObjectSpace.each_object(Module).select { |m| m.to_s =~ /^#{parent_str}/ }
#=> [ParentModule::Bar, ParentModule::Foo]
So mod's siblings are:
kids - [mod]
#=> [ParentModule::Foo]
If you have:
module A
class B
end
end
You can find B and similar classes via A.constants. However, in Ruby 1.9.3, you cannot get B if it is within another module. In Ruby 1.8.7 you can.
module A
module Aa
class B
end
end
end
How do you get B from the first level of A? What I would like as output is an array of constants, which include all classes like B, but anywhere within the module A.
class Module
def all_the_modules
[self] + constants.map {|const| const_get(const) }
.select {|const| const.is_a? Module }
.flat_map {|const| const.all_the_modules }
end
end
A.all_the_modules
# => [A, A::Aa, A::Aa::B]
This code will break if you do have circular namespaces, aka
A::Aa::B.const_set(:A, A).
I was getting stack overflows when I tried Reactormonk's answer on large libraries like RSpec.
Here's a solution that should filter out circular references and foreign references by checking to make sure that "children" are really children of the parent module we're iterating through:
def parent_of(mod)
parent_name = mod.name =~ /::[^:]+\Z/ ? $`.freeze : nil
Object.const_get(parent_name) if parent_name
end
def all_modules(mod)
[mod] + mod.constants.map { |c| mod.const_get(c) }
.select {|c| c.is_a?(Module) && parent_of(c) == mod }
.flat_map {|m| all_modules(m) }
end
(The parent_of() method is adapted from ActiveSupport's Module#parent, which doesn't seem to work reliably for library classes.)
module Foo
class Bar
module Baz
class Qux
CORGE = Random::Formatter
GARPLY = Foo::Bar::Baz
module Quux
end
end
end
end
end
Foo::Bar::Baz::Qux::CORGE.is_a?(Module)
# => true
all_modules(Foo)
# => [Foo, Foo::Bar, Foo::Bar::Baz, Foo::Bar::Baz::Qux, Foo::Bar::Baz::Qux::Quux]