Look up all descendants of a class in Ruby
I can easily ascend the class hierarchy in Ruby:
String.ancestors # [String, Enumerable, Comparable, Object, Kernel]
Enumerable.ancestors # [Enumerable]
Comparable.ancestors # [Comparable]
Object.ancestors # [Object, Kernel]
Kernel.ancestors # [Kernel]
Is there any way to descend the hierarchy as well? I'd like to do this
Animal.descendants # [Dog, Cat, Human, ...]
Dog.descendants # [Labrador, GreatDane, Airedale, ...]
Enumerable.descendants # [String, Array, ...]
but there doesn't seem to be a descendants
method.
(This question comes up because I want to find all the models in a Rails application that descend from a base class and list them; I have a controller that can work with any such model and I'd like to be able to add new models without having to modify the controller.)
Here is an example:
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 gives you:
GrandChild
Child
puts Child.descendants gives you:
GrandChild
If you use Rails >= 3, you have two options in place. Use .descendants
if you want more than one level depth of children classes, or use .subclasses
for the first level of child classes.
Example:
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 (or 1.8.7) with nifty chained iterators:
#!/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
Use it like so:
#!/usr/bin/env ruby
p Animal.descendants
Override the class method named inherited. This method would be passed the subclass when it is created which you can track.