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

前端 未结 2 1986
深忆病人
深忆病人 2021-01-05 09:28

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 i

相关标签:
2条回答
  • 2021-01-05 09:56
    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).

    0 讨论(0)
  • 2021-01-05 10:02

    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] 
    
    0 讨论(0)
提交回复
热议问题