Getting all classes defined under a module

407 views Asked by At

I have several files defining nested modules, say:

File1:

module A
  module B
    class B1
      class B1Error < Exception ; end
    end
  end
end

File 2:

module A
  module B
    class B2
      class B2Error < Exception ; end
      class B2_inner
      end
    end
  end
end

I need a method to get all classes defined under a given module.

def get_all_classes_under_module_hier(hier)
  ???
end
get_all_classes_under_module_hier(A::B) 
#=> A::B::B1, A::B::B1::B1Error, A::B::B2, A::B::B2::B2Error, A::B::B2::B2_inner.

How can I achieve the purpose?

The reason I need this is: I am trying to use log4r. I have several classes and I am creating logger with classNames on each of them. In the YAML configuration, it is required to again point out all defined logger names to configure further. I am trying to use a generic code to pull out all classes under a module hierarchy and have dynamic configuration.

Any input regarding my approach for log4r (or any simpler way) is also appreciated.

3

There are 3 answers

0
Arup Rakshit On

You can use either Module::nesting as below:

Returns the list of Modules nested at the point of call.

module A
  module B
    class B2
      class B1Error < Exception ; $b = Module.nesting ;end
      class B2_inner
        $a = Module.nesting
      end
    end
  end
end

$a # => [A::B::B2::B2_inner, A::B::B2, A::B, A]
$b # => [A::B::B2::B1Error, A::B::B2, A::B, A]

or,

Module::constants

returns an array of the names of all constants accessible from the point of call.

module A
  module B
    class B2
      class B1Error < Exception ;end
      class B2_inner
        $a = Module.constants
      end
    end
  end
end

$a - Module.constants
# => [:B1Error, :B2_inner, :B2, :B]
0
sawa On
def get_all_modules_under_module_hier(hier)
  a = hier
  .constants
  .map{|e| hier.const_get(e)}
  .select{|e| e.kind_of?(Module)}
  a + a.flat_map{|klass| get_all_classes_under_module_hier(klass)}
end
def get_all_classes_under_module_hier(hier)
  get_all_modules_under_module_hier(hier)
  .select{|e| e.instance_of?(Class)}
end

get_all_classes_under_module_hier(A::B)
# => [A::B::B1, A::B::B2, A::B::B1::B1Error, A::B::B2::B2Error, A::B::B2::B2_inner]
0
bestmike007 On

sawa's answer is ok, and also you can use a method like this to dynamically create logger for each class.

However, I prefer not to get all classes under a module. In able to create logger for every single class, you can do things as follow.

module Kernel

  #######
  private
  #######

  def logger
    Log4r::Logger[logger_name]
  end

  def logger_name
    clazz = self.class
    unless clazz.respond_to? :logger_name
      name = clazz.module_eval 'self.name'
      clazz.define_singleton_method(:logger_name) { name }
    end
    clazz.logger_name
  end

end

module A
  module B
    class C

      def hello
        logger.debug logger_name
      end

    end
  end
end

A::B::C.new.hello

For classes within a specific module you can write a filter in the logger_name method, for example:

module Kernel

  #######
  private
  #######

  def logger
    Log4r::Logger[logger_name]
  end

  def logger_name
    clazz = self.class
    unless clazz.respond_to? :logger_name
      name = clazz.module_eval 'self.name'
      name = 'root' unless name.start_with?('A::B')
      clazz.define_singleton_method(:logger_name) { name }
    end
    clazz.logger_name
  end

end

module A
  module B

    class C

      def hello
        logger.debug logger_name
      end

      class << self
        def hello
          logger.debug logger_name
        end
      end

    end
  end

  class D
    def hello 
      logger.debug logger_name
    end
  end
end

A::B::C.new.hello # A::B::C
A::B::C.hello # A::B::C
A::D.new.hello # root

And also, you can cache the logger:

def logger
  _logger = Log4r::Logger[logger_name]
  self.class.send(:define_method, :logger) { _logger }
  self.class.define_singleton_method(:logger) { _logger }
  return _logger
end

Hope it helps.