How do you find all modules and classes within a module, recursively? - ruby

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]

Related

Getting the nesting heirarchy for any given module in Ruby

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.

How do I find a method's nesting in Ruby?

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.

Ruby: get a sibling module

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]

Why does including this module not override a dynamically-generated method?

I'm trying to override a dynamically-generated method by including a module.
In the example below, a Ripple association adds a rows= method to Table. I want to call that method, but also do some additional stuff afterwards.
I created a module to override the method, thinking that the module's row= would be able to call super to use the existing method.
class Table
# Ripple association - creates rows= method
many :rows, :class_name => Table::Row
# Hacky first attempt to use the dynamically-created
# method and also do additional stuff - I would actually
# move this code elsewhere if it worked
module RowNormalizer
def rows=(*args)
rows = super
rows.map!(&:normalize_prior_year)
end
end
include RowNormalizer
end
However, my new rows= is never called, as evidenced by the fact that if I raise an exception inside it, nothing happens.
I know the module is getting included, because if I put this in it, my exception gets raised.
included do
raise 'I got included, woo!'
end
Also, if instead of rows=, the module defines somethingelse=, that method is callable.
Why isn't my module method overriding the dynamically-generated one?
Let's do an experiment:
class A; def x; 'hi' end end
module B; def x; super + ' john' end end
A.class_eval { include B }
A.new.x
=> "hi" # oops
Why is that? The answer is simple:
A.ancestors
=> [A, B, Object, Kernel, BasicObject]
B is before A in the ancestors chain (you can think of this as B being inside A). Therefore A.x always takes priority over B.x.
However, this can be worked around:
class A
def x
'hi'
end
end
module B
# Define a method with a different name
def x_after
x_before + ' john'
end
# And set up aliases on the inclusion :)
# We can use `alias new_name old_name`
def self.included(klass)
klass.class_eval {
alias :x_before :x
alias :x :x_after
}
end
end
A.class_eval { include B }
A.new.x #=> "hi john"
With ActiveSupport (and therefore Rails) you have this pattern implemented as alias_method_chain(target, feature) http://apidock.com/rails/Module/alias_method_chain:
module B
def self.included(base)
base.alias_method_chain :x, :feature
end
def x_with_feature
x_without_feature + " John"
end
end
Update Ruby 2 comes with Module#prepend, which does override the methods of A, making this alias hack unnecessary for most use cases.
Why isn't my module method overriding the dynamically-generated one?
Because that's not how inheritance works. Methods defined in a class override the ones inherited from other classes/modules, not the other way around.
In Ruby 2.0, there's Module#prepend, which works just like Module#include, except it inserts the module as a subclass instead of a superclass in the inheritance chain.
If you extend the instance of the class, you will can do it.
class A
def initialize
extend(B)
end
def hi
'hi'
end
end
module B
def hi
super[0,1] + 'ello'
end
end
obj = A.new
obj.hi #=> 'hello'

Add existing classes into a module

I have some existing ruby classes in a app/classes folder:
class A
...
end
class B
...
end
I'd like to group those classes in a module MyModule
I know I could do like:
module MyModule
class A
...
end
class B
...
end
end
but is there a meta programming shortcut that could do the same so I could "import" all the existing classes ?
Thanks,
Luc
module Foo
A = ::A
B = ::B
end
Foo::A.new.bar
Note that the :: prefix on a constant starts searchign the global namespace first. Like a leading / on a pathname. This allows you differentiate the global class A from the modularized constant Foo::A.
Use the const_missing hook. If the constant can't be found in the current module, try to resolve in the global namespace:
class A; end
class B; end
module M
def self.const_missing(c)
Object.const_get(c)
end
end
M::A.new
M::B.new
#Squeegy's answer already tells you what to do, but I think it is equally important to understand why that works. And it's actually pretty simple: classes in Ruby are nothing special. They are just objects like any other object that get assigned to variables just like any other variable. More precisely: they are instances of the Class class and they usually get assigned to constants (i.e. variables whose name starts with an uppercase letter).
So, just like you can alias any other object to multiple variables:
a = ''
b = a
a << 'Hello'
c = b
b << ', World!'
puts c # => Hello, World!
You can also alias classes to multiple variables:
class Foo; end
bar = Foo
p bar.new # => #<Foo:0x1d9f220>
If you want to move the classes into the namespace instead of just aliasing them, you also need to set the original variables to some other object like nil, in addition to #Squeegy's answer:
::A = nil
::B = nil
If you do want to put them in a module I don't see the point of first including them in the global namespace and then aliasing them inside the module.
I think what you want to do (although I doubt it is a good thing to do) is something like this:
file classes/g1.rb
class A1
def self.a
"a1"
end
end
class B1
def self.b
"b1"
end
end
file classes/g2.rb
class A2
def self.a
"a2"
end
end
class B2
def self.b
"b2"
end
end
file imp.rb
module MyModule
["g1.rb", "g2.rb"].each do |file|
self.class_eval open("classes/#{file}"){ |f| f.read }
end
end
puts defined? MyModule
puts defined? A1
puts defined? MyModule::A1
puts MyModule::A1.a
puts MyModule::B2.b
outputs
constant
nil
constant
a1
b2
I can think of a few disadvantages of this approach (harder to debug for one thing, and probably a bit slower to load although I am only guessing).
Why don't you just do something like this:
Dir["classes/*.rb"].each do |file|
contents = open(file) { |f| f.read }
open(file, "w") do |f|
f.puts "module MyModule\n"
contents.each { |line| f.write " #{line}" }
f.puts "\nend"
end
end
That'll fix your classes to be in your module since in ruby you can reopen a module at any time. Then just include them like you do normally.
Yes, in your module create the class and have it inherit from your outside classes.
For example,
class A
...
end
module MyModule
class NewA < A
end
end
The class MyModule::NewA will have all the attributes and methods of class A.
Then again, modules in ruby are never locked, so there is nothing stopping you just writing the class definition straight into the module.

Resources