2015-04-05 5 views
3

Интересно, можно ли получить список классов, полученных из заданного Class.Получить список классов, полученных из данного класса

Я вижу, есть обратный вызов Class::inherited, который должен быть Есть две проблемы, связанные с этим подходом «вызывается всякий раз, когда подкласс текущего класса создается.»:

  1. Когда я не продюсер из этого класса (я сказал, что я должен обезвредить его), я не могу вообще убедиться, что мой monkeypatch будет применяться до того, как будет создан первый производный класс.
  2. В идеальном мире я бы получил список классов, несмотря на то, были ли они уже инициализированы или нет, а обратный вызов должен называться фактически при создании экземпляра класса.

Я понимаю, что RTTI, вероятно, не лучший способ получить необходимую мне информацию (из-за вышеприведенного выше). Кто-нибудь предложит другой подход? Анализ статического кода? Все, что?

Я бы очень признателен за любые идеи. Скажем, у меня есть весь код, представляющий интерес в моей директории (другими словами, я заинтересован в моих классов, полученных только из некоторого заранее определенного класса, например, ApplicationController s в мое приложение Rails.)

ответ

6

Как насчет использования TracePoint? Дай мне знать, если следующий код служит вашей цели -

class DerivedClassObserver 

    def initialize(classes) 
    @classes, @subclasses = classes, {} 
    end 

    def start 
    @trace_point = TracePoint.new(:class) do |tp| 
     tp.self.ancestors.map do |ancestor| 
     if ancestor != tp.self && @classes.include?(ancestor.name) 
      (@subclasses[ancestor.name] ||= []) << tp.self.name 
     end 
     end 
    end 

    @trace_point.enable 
    end 

    def stop 
    @trace_point.disable 
    end 

    def subclasses(class_name) 
    @subclasses[class_name] 
    end 
end 

Пример использования

observer = DerivedClassObserver.new %w|A AA| 
observer.start 

# Borrowed example from @Cary 
class A  ; end 
class AA < A ; end 
class AB < A ; end 
class AC < A ; end 
class AAA < AA; end 
class AAB < AA; end 
class ABA < AB; end 
class ABB < AB; end 

observer.stop 

puts "A subclasses #{observer.subclasses('A').join(', ')}" 
# => A subclasses AA, AB, AC, AAA, AAB, ABA, ABB 

puts "AA subclasses #{observer.subclasses('AA').join(', ')}" 
# => AA subclasses AAA, AAB 

Благодаря

+0

Upvote для предоставления мне уведомления о 'TracePoint', но этот код, к сожалению, работает (как код в любом другом ответе здесь) только тогда, когда механизм загрузки классов известен и предсказуем. Последнее, например, не относится к Rails. – mudasobwa

+0

Привет @mudasobwa Я попробовал его с рельсов, вот мой результат - ActiveRecord :: Base подклассам PaperTrail :: Версия, PaperTrail :: VersionAssociation и все мои модели ... Просто добавьте его на boot.rb –

+0

@mudasobwa, поэтому он должен работать с непредсказуемым контекстом, если вы можете потребовать этот файл перед инициализацией остальной части файлов. TracePoint отлично работает, он не требует инициализации класса, только триггеры определения: событие класса и т. Д. Сообщите мне, если у вас есть какой-либо конкретный вопрос, который, возможно, пропустил –

0

Try:

ApplicationController.subclasses 
+0

DOCO звено - HTTP : //apidock.com/rails/Class/subclasses –

+1

1. Вы, вероятно, хотели предложить «потомков класса №», так как я требую чтобы не ограничивать поиск прямых подклассов. 2. Почти каждый рельс дерьмо на самом деле не более чем [синтаксический сахар] (https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/class/subclasses.rb) - ваш ответ на самом деле является синтаксическим сахаром для ответа Кэри выше. – mudasobwa

2

Вы можете использовать ObjectSpace::each_object:

def derived_classes(klass) 
    ObjectSpace.each_object(Class).with_object([]) { |k,a| a << k if k < klass } 
end 

class A  ; end 
class AA < A ; end 
class AB < A ; end 
class AC < A ; end 
class AAA < AA; end 
class AAB < AA; end 
class ABA < AB; end 
class ABB < AB; end 

derived = derived_classes(A) 
    #=> [AC, AA, AB, AAA, AAB, ABA, ABB] 

Edit:

Увы, это не то, что нужно, но позвольте мне предложить метод, который может быть полезным, как только производные классы были определены:

def order_classes(top, derived) 
    children = derived.select { |k| k.superclass == top } 
    return top if children.empty? 
    { top=>children.each_with_object([]) { |k,a| 
    a << order_classes(k, derived-children) } } 
end 

order_classes(A, derived) 
    #=> {A=>[AC, {AA=>[AAA, AAB]}, {AB=>[ABA, ABB]}]} 
+0

К сожалению, это не будет работать для 'B' до' класса B mudasobwa

+0

Я забыл« унаследованный », применяемый к другим, чем прямые потомки. Спасибо за напоминание. Это очень хаки, и, возможно, ненадежны, но вы могли бы разобрать файл как текст? Может ли быть какой-либо другой способ определить потомков до их создания? –

+0

На самом деле я почти решил пойти с разбором, так как, вероятно, нет способа получить эту информацию во время выполнения. В любом случае, спасибо за ваши усилия; напоминание о «ObjectSpace» спасло меня много времени на поиск в Google. – mudasobwa

0

Вы можете использовать класс # < и # each_object пространства объектов.

class A; end 
class B < A; end 
class C < A; end 
class CC < C; end 
ObjectSpace.each_object(Class).select{|c| c < A} 
# => [C, B, CC] 

class Class 
    def subclasses 
    ObjectSpace.each_object(Class).select{|c| c < self} 
    end 
end 
A.subclasses 
# => [C, B, CC] 

Я не знаю, как назвать это, inferior_classes, подклассы, derived_classes, children_rec, потомки?

+0

Как вы отвечаете, отличается от ответа Кэри? – mudasobwa

0

Гадкое решения, применимый для испытаний:

def derived from 
    Dir["#{Rails.root}/**/*.rb"].map do |f| 
    File.read(f).match(/(\S+)\s*<\s*#{from}\s/) && $1 
    end.compact 
end 

▶ derived User 
#⇒ [Admin, Editor] 
Смежные вопросы