Reason #161 • June 10th, 2026

Tracking inheritance via Class#inherited

Yesterday we used Class#subclasses to find subclasses of a class and used it to implement a CLI command registry. However, this approach had a limitation in that it only tracked direct subclasses, and wouldn't include deeper descendants.

While we could implement a recursive search to find all descendants, that wouldn't give me an opportunity to show off the inherited hook, so here we go!

Let's start with some simple examples of the inherited hook in action:

Ruby
class Parent
  def self.inherited(subclass)
    puts "#{subclass} is inheriting from #{self}"
  end
end

class Child < Parent
end
# Output: Child is inheriting from Parent

AnotherChild = Class.new(Parent)
# Output: AnotherChild is inheriting from Parent

# Since the hook itself is inherited, it triggers on the Child class as well:
class GrandChild < Child
end
# Output: GrandChild is inheriting from Child
    

Now let's use it to implement a command registry in a way which also includes deeper descendants:

Ruby
class Command
  @commands = {}

  def self.commands
    @commands
  end

  def self.inherited(subclass)
    # E.g. {"greet" => Greet, "add" => Add }
    Command.commands[subclass.name.downcase] = subclass
  end
end

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

class Greet_Enthusiastically < Greet
  def description = "Greet someone enthusiastically"
  def usage = "cli.rb greet_enthusiastically "
  def call(name) = super "❤️ #{name.upcase} ❤️!!"
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.commands.keys.each do |name|
    puts "  #{name}"
  end
  exit
end

command_name = ARGV.shift
command_class = Command.commands[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)
    

There we have it, we can now inherit to our heart's content!

Note that while the inherited method gets inherited by subclasses, the @commands instance variable does not, hence we refer explicitly to Command.commands to ensure subclasses register on the same hash.

How it works in practice:

$ ruby cli.rb
Usage: cli.rb <command> [args...]
Available commands:
  add
  greet
  greet_enthusiastically

$ ruby cli.rb greet Kabir
Hello, Kabir!

$ ruby cli.rb greet_enthusiastically Kabir
Hello, ❤️ KABIR ❤️!!!

Couldn't have wished for a more enthusiastic greeting ❤️‍🔥

History

The first version of Class#inherited shipped in Ruby 1.2 in 1998, with some tweaks following in later versions:

Ruby 1.6 made Class.new(superclass) trigger inherited as well.

Ruby 1.8 tightened the timing so the hook runs after the class has been assigned to its constant, but before the class body is evaluated.