Reason #162 • June 11th, 2026

Tracking extensions via Module#extended

We've talked about tracking subclasses with Class#subclasses as well as Class#inherited this week. But it's time to step away from inheritance and look at a different approach: module extension.

Module#extended works similarly to Class#inherited, but it triggers when we extend a module:

Ruby
module MyModule
  def self.extended(base)
    puts "#{base} is extending #{self}"
  end

  def my_method
    puts "Hello from MyModule!"
  end
end

class MyClass
  extend MyModule
end
# Output: MyClass is extending MyModule

# As a reminder, extending a module adds its methods as class methods:
MyClass.my_method
# Output: Hello from MyModule!
    

Now let's use it to implement the command registry in our lovely CLI example and take the opportunity to provide DSL-like methods for setting the description and usage of our commands:

Ruby
module Command
  @commands = {}

  def self.commands = @commands

  def self.extended(base)
    @commands[base.name.downcase] = base
  end

  def description(description = nil)
    description ? @description = description : @description
  end

  def usage(usage = nil)
    usage ? @usage = usage : @usage
  end

  def call(*args)
    raise NotImplementedError, "Commands must implement the call method"
  end
end

module Greet
  extend Command

  description "Greet someone"
  usage "cli.rb greet <name>"

  def self.call(name)
    puts "Hello, #{name}!"
  end
end

module Add
  extend Command

  description "Add two numbers"
  usage "cli.rb add <x> <y>"

  def self.call(x, y)
    puts x.to_i + y.to_i
  end
end

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

command_name = ARGV.shift
command = Command.commands[command_name]

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

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

command.call(*ARGV)
    

Isn't it neat how description and usage now have that declarative feel to them?

Let's see if it works:

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

$ ruby cli.rb greet --help
Command: greet
Greet someone
Usage: cli.rb greet <name>

$ ruby cli.rb greet Alice
Hello, Alice!

Like a charm!

History

Module#extended arrived in Ruby 1.8.1, released on Christmas 2003, one point release after the related hook Module#included.