Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I get a list of all subclasses without instantiating first in Ruby

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.

like image 677
user1535152 Avatar asked Jan 15 '16 18:01

user1535152


2 Answers

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)

like image 62
Frederick Cheung Avatar answered Oct 02 '22 01:10

Frederick Cheung


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

like image 35
Cyzanfar Avatar answered Oct 02 '22 01:10

Cyzanfar