Procure todos os descendentes de uma turma em Ruby

Eu posso facilmente subir a hierarquia de classs em Ruby:

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

Existe alguma maneira de descer a hierarquia também? Eu gostaria de fazer isso

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

mas não parece haver um método de descendants .

(Essa questão surge porque quero encontrar todos os modelos em um aplicativo Rails que descendem de uma class base e listá-los; tenho um controlador que pode trabalhar com qualquer modelo desse tipo e gostaria de poder adicionar novos modelos sem ter que modificar o controlador.)

   

Aqui está um exemplo:

 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 

coloca o Parent.descendants:

 GrandChild Child 

puts Child.descendants oferece:

 GrandChild 

Se você usa Rails> = 3, você tem duas opções no lugar. Use .descendants se você quiser mais do que uma profundidade de níveis de classs filhas, ou use .subclasss para o primeiro nível de classs .subclasss .

Exemplo:

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

Ruby 1.9 (ou 1.8.7) com iteradores nifty encadeados:

 #!/usr/bin/env ruby1.9 class Class def descendants ObjectSpace.each_object(::Class).select {|klass| klass < self } end end 

Ruby pré-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 

Use assim:

 #!/usr/bin/env ruby p Animal.descendants 

Substituir o método de class chamado herdado . Este método seria passado a subclass quando é criado que você pode acompanhar.

Alternativamente (atualizado para o ruby ​​1.9+):

 ObjectSpace.each_object(YourRootClass.singleton_class) 

Maneira compatível Ruby 1.8:

 ObjectSpace.each_object(class<  

Note que isso não funcionará para módulos. Além disso, YourRootClass será incluído na resposta. Você pode usar Array # - ou outra maneira de removê-lo.

Embora o uso do ObjectSpace funcione, o método de class herdada parece ser mais adequado aqui na documentação do Ruby (subclass) herdada

O espaço de objects é essencialmente uma maneira de acessar qualquer coisa e tudo o que atualmente está usando memory alocada, então iterar sobre cada um dos seus elementos para verificar se é uma subclass da class Animal não é o ideal.

No código abaixo, o método de class Animal herdado implementa um retorno de chamada que adicionará qualquer subclass recém-criada à sua matriz de descendentes.

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

(Rails < = 3.0) Alternativamente, você pode usar o ActiveSupport :: DescendantsTracker para fazer a escritura. A partir da fonte:

Este módulo fornece uma implementação interna para rastrear descendentes, que é mais rápida que a iteração através do ObjectSpace.

Como ele é bem modular, você pode simplesmente selecionar esse módulo específico para seu aplicativo Ruby.

Eu sei que você está perguntando como fazer isso em inheritance, mas você pode conseguir isso diretamente em Ruby, espaçando o nome da class ( Class ou 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] 

É muito mais rápido assim do que comparar com todas as classs no ObjectSpace como outras soluções propõem.

Se você realmente precisa disso em uma inheritance, você pode fazer algo assim:

 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 

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

atualizar

no final, tudo o que você precisa fazer é

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

obrigado @saturnflyer por sugestão

Ruby Facets possui descendentes da class #,

 require 'facets/class/descendants' 

Ele também suporta um parâmetro de distância geracional.

Você pode require 'active_support/core_ext' e usar o método descendants . Confira o documento e dê uma olhada no IRB ou arrombe. Pode ser usado sem Rails.

Uma versão simples que fornece uma matriz de todos os descendentes de uma class:

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

O Rails fornece um método de subclasss para cada object, mas não está bem documentado e não sei onde ele está definido. Ele retorna um array de nomes de classs como strings.

Usar gem descendants_tracker pode ajudar. O exemplo a seguir é copiado do documento da gem:

 class Foo extend DescendantsTracker end class Bar < Foo end Foo.descendants # => [Bar] 

Essa gema é usada pela popular gema virtus , então eu acho que é bem sólido.

Este método retornará um hash multidimensional de todos os descendentes de um object.

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

Se você tiver access ao código antes que qualquer subclass seja carregada, você poderá usar o método herdado .

Se você não o fizer (o que não é um caso, mas pode ser útil para qualquer um que tenha encontrado este post) você pode simplesmente escrever:

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

Desculpe se perdi a syntax mas a ideia deve ficar clara (não tenho access ao ruby ​​neste momento).