10

If I have 4 classes with the following hierarchy:

class MainClass < ActiveRecord::Base
  ...
end

class SubClassA < MainClass
  ...
end

class SubClassB < MainClass
  ...
end

class SubClassC < MainClass
  ...
end

How can I get a list of the subclasses of MainClass without going through and creating instances of each of the other classes?

In a fresh IRB session I can go in and say

irb(main)> MainClass.descendants
=> []

However if I go through and create instances of each subclass I'll see the following

irb(main)> SubClassA.new
=> #<SubClassA ...>
irb(main)> SubClassB.new
=> #<SubClassB ...>
irb(main)> SubClassC.new
=> #<SubClassC ...>
irb(main)> MainClass.descendants
=> [SubClassA(...), SubClassB(...), SubClassC(...)]

I'm basically looking for a way to programmaticly supply all subclasses so in the future when I want to add SubClassD, SubClassE, etc., I won't have to worry that each one is instantiated in the code before the user can see them.

user1535152
  • 256
  • 4
  • 17
  • I tried on rail console (rails 4.1.2). I am getting this (and not an empty list like you) :- irb(main):012:0> MainClass.descendants => [SubClassA(Table doesn't exist), SubClassB(Table doesn't exist)] – rohan Jan 15 '16 at 18:51
  • Interesting. Originally, I split my subclasses out into their own files (sub_class_a.rb, sub_class_b.rb, sub_class_c.rb) all at the same level and I was getting what I detailed above. When I consolidated the classes all in the same main_class.rb, the descendants method worked as you said. – user1535152 Jan 15 '16 at 19:02

3 Answers3

11

This is an artefact of development mode only loading classes when first referenced: those files haven't been read by the interpreter yet - as far as ruby is concerned the classes genuinely do not exist yet

A workaround is to put

require_dependency "subclass_a"
require_dependency "subclass_b"
....

At the bottom the file for main class (outside the class definition)

Frederick Cheung
  • 83,189
  • 8
  • 152
  • 174
8

There are several ways you can achieve this. Either by using a gem such as the descendants_tracker:

class MainClass < ActiveRecord::Base
  extend DescendantsTracker
end

class SubClassA < MainClass 
end

...

MainClass.descendants # => [SubClassA]

Or you could create a custom one in your parent class like this:

class  MainClass < ActiveRecord::Base
  def self.descendants
    ObjectSpace.each_object(Class).select { |klass| klass < self }
  end
end

class SubClassA < MainClass 
end

class SubClassB  < MainClass 
end

puts MainClass.descendants 

#=> SubClassA
    SubClassB  

You can also check for levels of dependencies. If you want an array of the direct child of MainClass for example you could use the .subclasses class method

MainClass.subclasses  # => [SubClassA, SubClassB,...]

Now if you want more than one level depth of child classes use .descendants. You can find an example here

Community
  • 1
  • 1
Cyzanfar
  • 6,997
  • 9
  • 43
  • 81
0

You could use ObjectSpace#each_object to compile a list of MainClass's children:

Suppose:

class MainClass            ; end
class SubClassA < MainClass; end
class SubClassB < MainClass; end
class SubClassC < SubClassB; end

The fact that MainClass may be a subclass is irrelevant.

ObjectSpace.each_object(Class).select { |o| o < MainClass }.
  tap { |siblings| siblings.reject! { |klass| siblings.any? { |k| klass < k } } }
  #=> [SubClassB, SubClassA]  
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100