Получение всех классов, определенных в модуле

У меня есть несколько файлов, определяющих вложенные модули, скажем:

File1:

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

Файл 2:

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

Мне нужен метод, чтобы получить все классы, определенные в данном модуле.

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.

Как я могу достичь цели?

Причина, по которой мне это нужно: я пытаюсь использовать log4r. У меня есть несколько классов, и я создаю регистратор с classNames на каждом из них. В конфигурации YAML необходимо снова указать все определенные имена регистраторов для дальнейшей настройки. Я пытаюсь использовать общий код для извлечения всех классов в иерархии модулей и динамической конфигурации.

Любой вклад в мой подход к log4r (или любой более простой способ) также приветствуется.

3 ответа

Ответ Sawa в порядке, и вы также можете использовать такой метод для динамического создания логгера для каждого класса.

Однако я предпочитаю не включать все классы в модуль. В состоянии создать регистратор для каждого отдельного класса, вы можете сделать следующее.

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

Для классов в определенном модуле вы можете написать фильтр в logger_name метод, например:

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

А также, вы можете кэшировать регистратор:

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

Надеюсь, поможет.

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]

Вы можете использовать либо Module::nesting как показано ниже:

Возвращает список модулей, вложенных в точке вызова.

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]

или же,

Module::constants

возвращает массив имен всех констант, доступных из точки вызова.

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]
Другие вопросы по тегам