Rails has convenient methods to fetch the subclasses and descendants of a class.

Unfortunately, Ruby doesn’t offer a “descendants” method i.e. the converse of the “ancestors” method. I tried to implement my own solution for “descendants” but I realized that obtaining the converse of “ancestors” was not going to be straightforward.

# ruby code

module M1
  class Parent
    class << self
      def descendants
        ObjectSpace.each_object(Class).select { |k| k < self } << self
      end
    end
  end

  class Child1 < Parent; end
  class GrandChild1 < Child1; end
  class GreatGrandChild1 < GrandChild1; end

  puts "#{Parent.descendants}"
  #=> [M1::GreatGrandChild1, M1::GrandChild1, M1::Child1, M1::Parent]
end

Great. But what about having parallel inheritence layers?

# ruby code

module M1
  class Child2 < Parent; end
  class GrandChild2 < Child2; end
  class GreatGrandChild2 < GrandChild2; end

  puts "#{Parent.descendants}"
  #=> [M1::GreatGrandChild2, M1::GrandChild2, M1::Child2, M1::GreatGrandChild1, M1::GrandChild1, M1::Child1, M1::Parent]
end

See how there’s no order to the array? Or, better put, an inconvenient order to the array. So I contemplated using layers via a Hash.

# ruby code

module M2
  class Parent
    # Alternatively, move this to ::Object for wider access.
    class << self
      def inherited(subclass)
        (@descendants ||= {})[subclass] ||= {}
      end

      def descendants
        {}.tap do |h|
          next unless @descendants
          @descendants.each_key { |k| h[k] = k.descendants }
        end
      end
    end
  end

  class Child1 < Parent; end
  class GrandChild1 < Child1; end
  class GreatGrandChild1 < GrandChild1; end

  class Child2 < Parent; end
  class GrandChild2 < Child2; end
  class GreatGrandChild2 < GrandChild2; end

  puts "#{Parent.descendants}"
  #=> {M2::Child1=>{M2::GrandChild1=>{M2::GreatGrandChild1=>{}}}, M2::Child2=>{M2::GrandChild2=>{M2::GreatGrandChild2=>{}}}}
end

There we go. Not exactly the converse of ancestors (since a Hash isn’t the converse of an Array), but still a workable model.