Reason #160 • June 9th, 2026

Tracking subclasses with Class#subclasses

The Class#subclasses method allows us to retrieve the direct subclasses of a class at runtime:

Ruby
class Parent
end

class Child < Parent
end

AnotherChild = Class.new(Parent)

Parent.subclasses.sort_by(&:name)
# => [AnotherChild, Child]

# Only direct subclasses are tracked, so it won't include GrandChild below:
class GrandChild < Child
end

Parent.subclasses.sort_by(&:name)
# => [AnotherChild, Child]
    

Continuing our ongoing CLI example, we can change the approach to register commands by inheriting from a base Command class and then use subclasses for command discovery:

Ruby
class Command
end

class Greet < Command
  def description = "Greet someone"
  def usage = "cli.rb greet <name>"
  def call(name) = puts "Hello, #{name}!"
end

class Add < Command
  def description = "Add two numbers"
  def usage = "cli.rb add <x> <y>"
  def call(x, y) = puts x.to_i + y.to_i
end

help = ARGV.delete("--help")
if ARGV.empty?
  puts "Usage: cli.rb <command> [args...]"
  puts
  puts "Available commands:"
  Command.subclasses.sort_by(&:name).each do |subclass|
    puts "  #{subclass.name.downcase}"
  end
  exit
end

command_name = ARGV.shift
command_class = Command.subclasses.find { |c| c.name.downcase == command_name }

abort "ERROR: Unknown command - #{command_name}" unless command_class

command = command_class.new

if help
  puts "Command: #{command_name}"
  puts command.description
  puts "Usage: #{command.usage}"
  exit
end

command.call(*ARGV)
    

Adding commands is now as simple as defining a new class that inherits from Command.

As a bonus, we're now rendering a list of available commands when the user doesn't provide any arguments:

$ ruby cli.rb
Usage: cli.rb <command> [args...]

Available commands:
  add
  greet

Admittedly, the value of using subclasses here is limited. Many Rubyists would likely prefer using modules, and I would not necessarily disagree. But hey, without inheriting we can't showcase the subclasses method, can we? 😇

History

Class#subclasses was added in Ruby 3.1, released on Christmas 2021. The method exposes part of the subclass tracking Ruby already maintained internally for the class hierarchy and method-cache invalidation.

Rails developers may recognize the idea from ActiveSupport's descendants tracker. Ruby's built-in version is narrower since it only returns direct child classes, while Rails often wants recursive descendants for framework tasks such as finding models and controllers.

Before Class#subclasses was introduced, subclasses could be tracked through the inherited hook, which we may look at some other day!