Detect subclasses in Ruby

I recently came across a problem which involved being able to select subclasses dynamically based on user input.

Given a master class of say A and a user input string, we should be able to instantiate a new subclass of A.

If we are using Rails framework, we can use ActiveSupport#CoreExt which has monkey-patched Class with two additional methods: descendants and subclasses

The source code from ActiveSupport is as follows:

 1 class Class
 2   def descendants
 3     descendants = []
 4     ObjectSpace.each_object(singleton_class) do |k|
 5       # prepends to front of descendants
 6       descendants.unshift k unless k == self
 7     end
 8 
 9     descendants
10   end
11 
12   def subclasses
13     subclasses, chain = [], descendants
14     chain.each do |k|
15       subclasses << k unless chain.any? { |c| c > k }
16     end
17     subclasses
18   end
19 end

Given the following class hierarchy we can use the extensions like so:

 1 require 'active_support'
 2 require 'active_support/core_ext'
 3 
 4 class A; end
 5 class B < A; end
 6 class C < B; end
 7 
 8 puts A.descendants # => [B,C]
 9 
10 puts A.subclasses # => [B]

Calling ObjectSpace.each_object with a class or module argument returns a list of matching classes or modules including its subclasses.

In this case, we are passing in ObjectSpace.each_object(A.singleton_class) which returns matching classes whose singleton class is either A.singleton_class or a subclass of it:

1 ObjectSpace.each_object(A.singleton_class).to_a # => [A,B,C]
2 
3 B.singleton_class.superclass # => # <Class:A>
4 
5 C.singleton_class.superclass # => # <Class:B>
6 
7 C.singleton_class.superclass.superclass # => # <Class:A>

Calling A.descendants returns only [B,C] as within the each_object loop it excludes A itself.

Calling A.subclasses will return only direct subclasses, namely [B]. This is because it loops through the descendants list and makes a class comparison whereby only the top level subclasses are selected:

1 B > C # => true
2 
3 B > B # => false
4 
5 C > C # => false
6 
7 C > B # => false

ObjectSpace module is certainly a powerful introspection tool to have in your toolbelt if you need detailed analytics about the objects in your current ruby process.

Stay curious and keep hacking!

Resources