如何递归地找到模块中的所有模块和类?

时间:2012-03-24 00:46:36

标签: ruby

如果你有:

module A
  class B
  end
end

你可以通过A.constants找到B和类似的类。但是,在Ruby 1.9.3中,如果它在另一个模块中,则无法获得B.在Ruby 1.8.7中你可以。

module A
  module Aa
    class B
    end
  end
end

你如何从A的第一级获得B?我想要输出的是一个常量数组,其中包括所有类,如B,但在模块A中的任何位置。

2 个答案:

答案 0 :(得分:14)

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]

如果你确实有圆形命名空间,那么这段代码就会中断 A::Aa::B.const_set(:A, A)

答案 1 :(得分:0)

当我在RSpec之类的大型库上尝试Reactormonk's answer时,我出现了堆栈溢出的情况。 这是一个解决方案,应通过检查以确保“子级”确实是我们要遍历的父级模块的子级,来过滤掉循环引用和外部引用。

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

({parent_of()方法是从ActiveSupport的Module#parent改编而成的,该方法似乎无法可靠地用于库类。)

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]