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