Cerca tutti i discendenti di una class in Ruby

Posso facilmente salire alla gerarchia delle classi in Ruby:

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

C’è un modo per discendere anche la gerarchia? Mi piacerebbe farlo

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

ma non sembra essere un metodo descendants .

(Questa domanda viene fuori perché voglio trovare tutti i modelli in un’applicazione Rails che discendono da una class base ed elencarli, ho un controller che può funzionare con qualsiasi di questi modelli e mi piacerebbe poter aggiungere nuovi modelli senza dover modificare il controller.)

Ecco un esempio:

 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 

mette Parent.descendants ti dà:

 GrandChild Child 

mette Child.descendants ti dà:

 GrandChild 

Se si utilizza Rails> = 3, sono disponibili due opzioni. Usa .descendants se vuoi più di un livello di profondità di classi per bambini, o usa .subclasss per il primo livello di classi .subclasss .

Esempio:

 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 (o 1.8.7) con netti iteratori concatenati:

 #!/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 

Usalo in questo modo:

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

Sostituisci il metodo di class denominato inherited . Questo metodo dovrebbe passare la sottoclass quando viene creata, che è ansible tracciare.

In alternativa (aggiornato per Ruby 1.9+):

 ObjectSpace.each_object(YourRootClass.singleton_class) 

Ruby 1.8 modo compatibile:

 ObjectSpace.each_object(class<  

Si noti che questo non funzionerà per i moduli. Inoltre, YourRootClass sarà incluso nella risposta. Puoi usare # matrice - o un altro modo per rimuoverlo.

Sebbene l’utilizzo di ObjectSpace funzioni, il metodo di class ereditato sembra essere meglio adatto alla documentazione di Ruby ereditata (sottoclass)

Objectspace è essenzialmente un modo per accedere a qualsiasi cosa e ad ogni cosa che utilizza attualmente la memoria allocata, quindi iterare su ognuno dei suoi elementi per verificare se si tratta di una class sublocazione della class Animal non è l’ideale.

Nel codice seguente, il metodo della class Animal ereditato implementa un callback che aggiungerà qualsiasi sottoclass appena creata alla sua matrice di discendenti.

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

(Rails < = 3.0) In alternativa è possibile utilizzare ActiveSupport :: DescendantsTracker per eseguire l'atto. Dalla fonte:

Questo modulo fornisce un’implementazione interna per tenere traccia dei discendenti che è più veloce di iterare con ObjectSpace.

Dal momento che è modulare in modo gradevole, è ansible selezionare semplicemente quel particolare modulo per la tua app Ruby.

So che stai chiedendo come fare questo in ereditarietà, ma puoi farlo direttamente in Ruby digitando il nome della class ( Class o 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] 

È molto più veloce in questo modo rispetto al confronto con ogni class in ObjectSpace come altre soluzioni propongono.

Se hai seriamente bisogno di questo in un’eredità, puoi fare qualcosa del genere:

 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 

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

aggiornare

alla fine tutto ciò che devi fare è questo

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

grazie @saturnflyer per il suggerimento

Ruby Facets ha discendenti di Class #,

 require 'facets/class/descendants' 

Supporta anche un parametro di distanza generazionale.

Puoi require 'active_support/core_ext' e utilizzare il metodo descendants . Controlla il documento e dagli un colpo in IRB o fai leva. Può essere usato senza Rails.

Una versione semplice che fornisce una schiera di tutti i discendenti di una class:

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

Rails fornisce un metodo di sottoclassi per ogni object, ma non è ben documentato e non so dove è definito. Restituisce una serie di nomi di classi come stringhe.

Usare la gem descendants_tracker può aiutare. Il seguente esempio è copiato dal doc della gem:

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

Questa gem è usata dalla famosa gem virtus , quindi penso che sia piuttosto solida.

Questo metodo restituirà un hash multidimensionale di tutti i discendenti di un object.

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

Se hai accesso al codice prima che venga caricata qualsiasi sottoclass, puoi utilizzare il metodo ereditato .

Se non lo fai (che non è un caso ma potrebbe essere utile per chiunque abbia trovato questo post) puoi scrivere:

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

Scusa se ho perso la syntax ma l’idea dovrebbe essere chiara (non ho accesso al ruby ​​in questo momento).