Посмотрите всех потомков класса в 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]
Извините, если я пропустил синтаксис, но идея должна быть ясной (у меня нет доступа к рубину в данный момент).