Updates: 2015-04-24 23:22 Update 1: adding “Inherited hook” solution, recommended way how to do this

(Spoiler alert, using StarWars plot to describe behavior)

Let say you have an inheritance:

class DarthVader end class Luke < DarthVader end class Leia < DarthVader end

…and you want to pull some Ancestor information from classes

Luke.superclass # => DarthVader DarthVader.superclass # => Object Luke.ancestors # => [Luke, DarthVader, Object, Kernel, BasicObject] DarthVader.ancestors # => [DarthVader, Object, Kernel, BasicObject] # compare Luke <= DarthVader # true Luke <= Object # true Luke <= Leia # nil DarthVader <= Luke # false

(So we finaly know who is Darth Vader’s father)

But how would you pull “Descendats” (children classes) from parent class ( DarthVader ) ?

Well turns out in our Ruby world the StarWars plot is in reverse Luke knows that Darth Vader is his father but Darth Vader has no clue.

This logically make sence. Class Luke knows about existance of DarthVader but class DarthVader is just a class on it’s own.

Here is a little UML to demonstrate this

# ____________ ________ # |DarthVader| <- | Luke | # ------------ -------- # ________ # <- | Lea | # --------

This is really good example of no mather how much you try, your application is always different than the real life. Ruby objects only represent real life. Think about a married couple in a divorce where each of them is represented by a lawyer. These two lawyers are married in real life. The lawyer couple not neceseraly need to be in a divorce themself to represent their clients. (Lawyer example stolen from Robert C. Martin)

So there is no “bulit in” way how to just call DarthVader.descendants but you can build one:

class Parent def self.descendants ObjectSpace.each_object(Class).select { |klass| klass < self } end end class Child < Parent end class GrandChild < Child end Parent.descendants => [Child, GrandChild] Child.descendants = [GrandChild]

(stolen from http://stackoverflow.com/questions/2393697/look-up-all-descendants-of-a-class-in-ruby)

…however that could be bit slow if you have too many clases as you are looping through all Objects in ObjectSpace (discussion here)

When you think about it, it’s like DarthVader taking DNA test with everyone in the known universe, not even knowing if he have any children in the first place.

Benchmarking on my machin was not that bad:

require 'benchmark' Benchmark.bm do |bm| bm.report('1st call') { Parent.descendants } bm.report('2nd call') { Parent.descendants } end

Benchmark on plain Ruby project (irb):

user system total real 1st call 0.010000 0.000000 0.010000 ( 0.011786) 2nd call 0.010000 0.000000 0.010000 ( 0.004776)

Benchmark on medium size production Rails project that I work on currently:

user system total real 1st call 0.030000 0.000000 0.030000 ( 0.034386) 2nd call 0.020000 0.000000 0.020000 ( 0.013391)

If this is too slow for you, you may want to cache this into a instance variable:

class Parent def self.descendants @descendants ||= ObjectSpace.each_object(Class).select { |klass| klass < self } end end

Benchmark on medium size production Rails project that I work on currently:

user system total real 1st call 0.030000 0.010000 0.040000 ( 0.033732) 2nd call 0.000000 0.000000 0.000000 ( 0.000003)

…but if you need to have a fresh list of moduls each time you run this method then this solution just feels wrong.

There are some other ways how to accomplish the same thing but all are bit messy.

But we are Ruby developers lets think about another way how to do this.

Module namespace to save the day

Depening what are the business rules we may want to just scope in namespace the related Classes:

module DarthVader class Luke end class Lea end end

Here you can do:

DarthVader::Luke.ancestors # => [DarthVader::Luke, Object, Kernel, BasicObject] DarthVader.constants # => [:Luke, :Lea] DarthVader .constants .map { |const_symbol| DarthVader.const_get(class_symbol) } # => [DarthVader::Luke, DarthVader::Lea]

You still have a wierd situation wher Luke kinda knows about DarthVader beeing his father, so there will be no drama in cinema, but you can finally pull the children from DarthVader .

Just watch out .constants will pull all classes in namespace:

module DarthVader module DarkForce end BlowUpDeathStar = Class.new(StandardError) class Luke end class Lea end end

…will give you:

DarthVader.constants # => [:DarkForce, :BlowUpDeathStar, :Luke, :Lea]

So you may want to filter out the values you don’t want:

DarthVader .constants .map { |class_symbol| DarthVader.const_get(class_symbol) } .select { |c| !c.ancestors.include?(StandardError) && c.class != Module } # => [DarthVader::Luke, DarthVader::Lea]

You don’t have to blacklist all classes you don’t like. The filter can be anything related to your domain. For example .select { |c| c.resopond_to?(:ligtsaber) }

Now you are like: “A ha! I still need to do some wierd filtering !” Well yes, but you are pulling classes only from DarthVader namespace and filtering it against DarthVader module, not against the entire ObjectSpace

Here are the benchmarks:

module DarthVader DarkForce = Module.new BlowUpDeathStar = Class.new(StandardError) Luke = Class.new Lea = Class.new def self.descendants DarthVader .constants .map { |class_symbol| DarthVader.const_get(class_symbol) } .select { |c| !c.ancestors.include?(StandardError) && c.class != Module } end end require 'benchmark' Benchmark.bm do |bm| bm.report('1st call') { DarthVader.descendants } bm.report('2nd call') { DarthVader.descendants } end

Benchmark on medium size production Rails project that I work on currently:

user system total real 1st call 0.000000 0.000000 0.000000 ( 0.000083) 2nd call 0.000000 0.000000 0.000000 ( 0.000039)

Class namespance and inheritance

If you are in a situation that you have to inherit from DarthVader think about this solution:

class DarthVader def self.descendants DarthVader .constants .map { |class_symbol| DarthVader.const_get(class_symbol) } end class Luke < DarthVader # ... end class Lea < DarthVader # ... end def force 'May the Force be with you' end end

Benchmark on medium size production Rails project that I work on currently:

user system total real 1st call 0.000000 0.000000 0.000000 ( 0.000050) 2nd call 0.000000 0.000000 0.000000 ( 0.000027)

…and you can now do:

DarthVader.new.force # => "May the Force be with you" DarthVader::Luke.new.force # => "May the Force be with you"

UPDATE 1:

Using the inherited hook

As Jim Gay (SaturnFlyer) kindly pointed out in his comment bellow and Steve Jorgensen in his reddit post there is even better solution:

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

I don’t really have to benchmark it as this solution is registering classes once they inherit parent class therefore is the fastest one. I’m recomending this approach as you will avoid silly cases where you both inherit and namespace the same Class.

Thank you Jim