Tracking Class Descendants in Ruby (II)
My previous post explains a way to keep track of a class' descendants, and encapsulates the technique into a module.
There are two things you may want to do different: Since all descendants inherit the descendants class method you may prefer them to be functional. On the other hand, the module defines the inherited class method into the base class because it needs it to be a closure. That may work for some particular need, but it is not good for a generic solution. The inherited hook is the business of your client's code.
Now we'll see a different approach that addresses both concerns. Using the same hook any class in the hierarchy may easily keep track of its direct subclasses, and compute its descendants:
class C def self.inherited(subclass) subclasses << subclass end def self.subclasses @subclasses ||= [] end def self.descendants subclasses + subclasses.map(&:descendants).flatten end end
In the previous solution the inherited hook needed to ensure descendants was invoked on the root of the hierarchy. In this solution it doesn't care because we precisely take advantage of polymorphism. The way it is written a class pushes into its own @subclasses instance variable, which is what we want.
The module that encapsulates that pattern is much simpler:
module DescendantsTracker def inherited(subclass) subclasses << subclass super end def subclasses @subclasses ||= [] end def descendants subclasses + subclasses.map(&:descendants).flatten end end class C extend DescendantsTracker end
You know extend is like doing an include in the metaclass of C. In particular we are not defining C.inherited, we are defining a method with the same name in an ancestor of the metaclass. That way C can still define its own inherited class method. A call to super within such a C.inherited will go up the next ancestor of the metaclass, eventually reaching the inherited from DescendantsTracker.