Посмотрите всех потомков класса в Ruby

Я могу легко подняться по иерархии классов в Ruby:

String.ancestors     # [String, Enumerable, Comparable, Object, Kernel]
Enumerable.ancestors # [Enumerable]
Comparable.ancestors # [Comparable]
Object.ancestors     # [Object, Kernel]
Kernel.ancestors     # [Kernel]

Есть ли способ спустить иерархию? Я бы хотел сделать это

Animal.descendants      # [Dog, Cat, Human, ...]
Dog.descendants         # [Labrador, GreatDane, Airedale, ...]
Enumerable.descendants  # [String, Array, ...]

но, похоже, не существует метода descendants.

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

Ответ 1

Вот пример:

class Parent
  def self.descendants
    ObjectSpace.each_object(Class).select { |klass| klass < self }
  end
end

class Child < Parent
end

class GrandChild < Child
end

puts Parent.descendants
puts Child.descendants

puts Parent.descendants дает вам:

GrandChild
Child

puts Child.descendants дает вам:

GrandChild

Ответ 2

Если вы используете Rails >= 3, у вас есть два варианта. Используйте .descendants, если вы хотите более одного уровня глубины дочерних классов, или используйте .subclasses для первый уровень дочерних классов.

Пример:

class Animal
end

class Mammal < Animal
end

class Dog < Mammal
end

class Fish < Animal
end

Animal.subclasses #=> [Mammal, Fish] 
Animal.descendants  #=> [Dog, Mammal, Fish]

Ответ 3

Ruby 1.9 (или 1.8.7) с отличными итераторами с цепочкой:

#!/usr/bin/env ruby1.9

class Class
  def descendants
    ObjectSpace.each_object(::Class).select {|klass| klass < self }
  end
end

Ruby pre-1.8.7:

#!/usr/bin/env ruby

class Class
  def descendants
    result = []
    ObjectSpace.each_object(::Class) {|klass| result << klass if klass < self }
    result
  end
end

Используйте его так:

#!/usr/bin/env ruby

p Animal.descendants

Ответ 4

Переопределите метод класса с именем inherited. Этот метод будет передан подкласс, когда он будет создан, который вы можете отслеживать.

Ответ 5

Альтернативно (обновлено для ruby ​​1.9 +):

ObjectSpace.each_object(YourRootClass.singleton_class)

Совместимость с Ruby 1.8:

ObjectSpace.each_object(class<<YourRootClass;self;end)

Обратите внимание, что это не будет работать для модулей. Кроме того, YourRootClass будет включен в ответ. Вы можете использовать Array # - или другой способ удалить его.

Ответ 6

Хотя использование ObjectSpace работает, метод унаследованного класса, по-видимому, лучше подходит здесь унаследованный (подкласс) Ruby documentation

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

В приведенном ниже коде унаследованный метод класса Animal реализует обратный вызов, который добавит любой вновь созданный подкласс в его массив потомков.

class Animal
  def self.inherited(subclass)
    @descendants = []
    @descendants << subclass
  end

  def self.descendants
    puts @descendants 
  end
end

Ответ 7

Ruby Facets имеет потомство класса #,

require 'facets/class/descendants'

Он также поддерживает параметр расстояния между поколениями.

Ответ 8

Я знаю, что вы спрашиваете, как это сделать в наследовании, но вы можете добиться этого напрямую с помощью Ruby, используя пробел между классами (Class или Module)

module DarthVader
  module DarkForce
  end

  BlowUpDeathStar = Class.new(StandardError)

  class Luck
  end

  class Lea
  end
end

DarthVader.constants  # => [:DarkForce, :BlowUpDeathStar, :Luck, :Lea]

DarthVader
  .constants
  .map { |class_symbol| DarthVader.const_get(class_symbol) }
  .select { |c| !c.ancestors.include?(StandardError) && c.class != Module }
  # => [DarthVader::Luck, DarthVader::Lea]

Это намного быстрее, чем сравнение с каждым классом в ObjectSpace, как предлагают другие решения.

Если вам это действительно нужно в наследстве, вы можете сделать что-то вроде этого:

class DarthVader
  def self.descendants
    DarthVader
      .constants
      .map { |class_symbol| DarthVader.const_get(class_symbol) }
  end

  class Luck < DarthVader
    # ...
  end

  class Lea < DarthVader
    # ...
  end

  def force
    'May the Force be with you'
  end
end

: http://www.eq8.eu/blogs/13-ruby-ancestors-descendants-and-other-annoying-relatives

Обновление

в конце концов, все, что вам нужно сделать, это

class DarthVader
  def self.inherited(klass)
    @descendants ||= []
    @descendants << klass
  end

  def self.descendants
    @descendants || []
  end
end

class Foo < DarthVader
end

DarthVader.descendants #=> [Foo]

Благодарю вас @saturnflyer за предложение

Ответ 9

(Rails <= 3.0) Альтернативно вы можете использовать ActiveSupport:: DescendantsTracker для выполнения этого действия. Из источника:

Этот модуль обеспечивает внутреннюю реализацию для отслеживания потомков, которая быстрее, чем повторение через ObjectSpace.

Так как он хорошо модулизуется, вы можете просто "выбрать вишневый" этот конкретный модуль для своего приложения Ruby.

Ответ 10

Вы можете require 'active_support/core_ext' и использовать метод descendants. Проверьте документ и сделайте снимок в IRB или pry. Может использоваться без Rails.

Ответ 11

Простая версия, которая дает массив всех потомков класса:

def descendants(klass)
  all_classes = klass.subclasses
  (all_classes + all_classes.map { |c| descendants(c) }.reject(&:empty?)).flatten
end

Ответ 12

Rails предоставляет метод подклассов для каждого объекта, но он недостаточно хорошо документирован, и я не знаю, где он определен. Он возвращает массив имен классов в виде строк.

Ответ 13

Использование descendants_tracker gem может помочь. Следующий пример копируется из документа gem:

class Foo
  extend DescendantsTracker
end

class Bar < Foo
end

Foo.descendants # => [Bar]

Этот драгоценный камень используется популярным virtus gem, поэтому я думаю, что он довольно прочный.

Ответ 14

Этот метод вернет многомерный хэш всех потомков объекта.

def descendants_mapper(klass)
  klass.subclasses.reduce({}){ |memo, subclass|
    memo[subclass] = descendants_mapper(subclass); memo
  }
end

{ MasterClass => descendants_mapper(MasterClass) }

Ответ 15

Если у вас есть доступ к коду перед загрузкой любого подкласса, вы можете использовать метод inherited.

Если вы этого не сделаете (это не случай, но может быть полезно для всех, кто нашел это сообщение), вы можете просто написать:

x = {}
ObjectSpace.each_object(Class) do |klass|
     x[klass.superclass] ||= []
     x[klass.superclass].push klass
end
x[String]

Извините, если я пропустил синтаксис, но идея должна быть ясной (у меня нет доступа к рубину в данный момент).