manejo - Busca a todos los descendientes de una clase en Ruby
objetos y clases en ruby (15)
(Rails <= 3.0) Alternativamente, podría usar ActiveSupport :: DescendantsTracker para realizar la escritura. De la fuente:
Este módulo proporciona una implementación interna para rastrear descendientes, que es más rápido que iterar a través de ObjectSpace.
Dado que se modulariza muy bien, puede simplemente ''seleccionar'' ese módulo en particular para su aplicación Ruby.
Puedo ascender fácilmente la jerarquía de clases en Ruby:
String.ancestors # [String, Enumerable, Comparable, Object, Kernel]
Enumerable.ancestors # [Enumerable]
Comparable.ancestors # [Comparable]
Object.ancestors # [Object, Kernel]
Kernel.ancestors # [Kernel]
¿Hay alguna forma de descender también de la jerarquía? Me gustaría hacer esto
Animal.descendants # [Dog, Cat, Human, ...]
Dog.descendants # [Labrador, GreatDane, Airedale, ...]
Enumerable.descendants # [String, Array, ...]
pero no parece haber un método de descendants
.
(Esta pregunta surge porque quiero encontrar todos los modelos en una aplicación Rails que descienden de una clase base y listarlos; tengo un controlador que puede funcionar con cualquier modelo y me gustaría poder agregar nuevos modelos sin tener que modificar el controlador.)
Alternativamente (actualizado para ruby ββ1.9+):
ObjectSpace.each_object(YourRootClass.singleton_class)
Manera compatible con Ruby 1.8:
ObjectSpace.each_object(class<<YourRootClass;self;end)
Tenga en cuenta que esto no funcionará para los módulos. Además, YourRootClass se incluirá en la respuesta. Puede usar Array # - u otra forma de eliminarlo.
Aquí hay un ejemplo:
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
pone Parent.descendants te da:
GrandChild
Child
pone Child.descendants te da:
GrandChild
Aunque el uso de ObjectSpace funciona, el método de clase heredado parece ser más adecuado aquí . Documentación de Ruby heredada (subclase)
El espacio de objetos es esencialmente una forma de acceder a cualquier cosa y a todo lo que está usando actualmente la memoria asignada, por lo que iterar sobre cada uno de sus elementos para comprobar si es una subclase de la clase Animal no es ideal.
En el siguiente código, el método heredado de clase Animal implementa una devolución de llamada que agregará cualquier subclase recién creada a su matriz de descendientes.
class Animal
def self.inherited(subclass)
@descendants = []
@descendants << subclass
end
def self.descendants
puts @descendants
end
end
Este método devolverá un hash multidimensional de todos los descendientes de un Objeto.
def descendants_mapper(klass)
klass.subclasses.reduce({}){ |memo, subclass|
memo[subclass] = descendants_mapper(subclass); memo
}
end
{ MasterClass => descendants_mapper(MasterClass) }
Puede require ''active_support/core_ext''
y usar el método de descendants
. Mira el documento y pruébalo en IRB o haz palanca. Se puede usar sin rieles.
Rails proporciona un método de subclases para cada objeto, pero no está bien documentado, y no sé dónde está definido. Devuelve una matriz de nombres de clase como cadenas.
Reemplaza el método de clase llamado inherited . A este método se le pasará la subclase cuando se crea y se puede seguir.
Ruby Facets tiene descendientes de clase #,
require ''facets/class/descendants''
También es compatible con un parámetro de distancia generacional.
Sé que estás preguntando cómo hacer esto en herencia, pero puedes lograrlo directamente en Ruby separando los nombres de la clase ( 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]
Es mucho más rápido de esta manera que la comparación con todas las clases en ObjectSpace
como proponen otras soluciones.
Si realmente necesita esto en una herencia, puede hacer algo como esto:
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
puntos de referencia aquí: http://www.eq8.eu/blogs/13-ruby-ancestors-descendants-and-other-annoying-relatives
actualizar
al final todo lo que tienes que hacer es esto
class DarthVader
def self.inherited(klass)
@descendants ||= []
@descendants << klass
end
def self.descendants
@descendants || []
end
end
class Foo < DarthVader
end
DarthVader.descendants #=> [Foo]
gracias @saturnflyer por sugerencia
Si tiene acceso al código antes de cargar cualquier subclase, puede usar el método inherited .
Si no lo hace (que no es un caso pero puede ser útil para cualquier persona que encuentre esta publicación) puede escribir:
x = {}
ObjectSpace.each_object(Class) do |klass|
x[klass.superclass] ||= []
x[klass.superclass].push klass
end
x[String]
Lo siento si me perdí la sintaxis, pero la idea debería ser clara (no tengo acceso a ruby ββen este momento).
Si usa Rails> = 3, tiene dos opciones en su lugar. Utilice apidock.com/rails/Class/descendants si desea más de una profundidad de nivel de clases de niños, o use .subclasses
para el primer nivel de clases de niños.
Ejemplo:
class Animal
end
class Mammal < Animal
end
class Dog < Mammal
end
class Fish < Animal
end
Animal.subclasses #=> [Mammal, Fish]
Animal.descendants #=> [Dog, Mammal, Fish]
Una versión simple que proporciona una matriz de todos los descendientes de una clase:
def descendants(klass)
all_classes = klass.subclasses
(all_classes + all_classes.map { |c| descendants(c) }.reject(&:empty?)).flatten
end
Usar la gema descenddants_tracker puede ayudar. El siguiente ejemplo se copia del documento de la joya:
class Foo
extend DescendantsTracker
end
class Bar < Foo
end
Foo.descendants # => [Bar]
Esta gema es utilizada por la popular gema virtus , así que creo que es bastante sólida.
Ruby 1.9 (o 1.8.7) con ingeniosos iteradores encadenados:
#!/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
Úselo así:
#!/usr/bin/env ruby
p Animal.descendants