rubyguides

Ruby hooks and callbacks: included, extended, prepended, and inherited

Ruby hooks are the language’s built-in way to intercept events that happen during your program’s lifetime. When a module gets mixed into a class, when a class gets subclassed, or when someone calls a method that doesn’t exist, Ruby can notify you. You define a special method and the interpreter calls it at the right moment. No event emitters, no observer objects. Just methods with reserved names.

These reserved methods are called hooks or callbacks, and they power nearly every Ruby framework you’ve used. ActiveRecord, RSpec, Rake, and Sinatra all rely on them. This guide covers every important hook, when it fires, and what you can do with it.

Intro Context

Ruby hooks are the points where the language pauses and asks your code what should happen next. That makes them useful for logging, auto-registration, module setup, and dynamic behavior that needs to run at the exact moment a class changes. If you understand when each hook fires, you can build cleaner metaprogramming tools without scattering setup code all over your app.

The same idea shows up in frameworks because they need predictable moments to attach behavior. A module can add helper methods when it is included, a subclass can register itself when it is inherited, and an object can answer unknown method names when method_missing runs. Those are different hooks, but they all solve the same basic problem: reacting to a language event instead of wiring everything by hand.

The practical benefit is that the hook stays close to the behavior it configures. That keeps the setup readable, because the code that reacts to the event lives in the same file as the module or class that owns the behavior. You do not need a separate initializer or a large bootstrap script to explain what is happening.

TL;DR

  • included runs when a module is mixed into a class.
  • extended runs when a module is extended onto an object or class.
  • prepended lets a module wrap or override methods before the class itself.
  • inherited runs when a class is subclassed.
  • method_missing handles undefined methods, but only when you also keep respond_to_missing? honest.

the included callback: module inclusion

When you mix a module into a class using include, Ruby immediately calls self.included on the module, passing the including class as an argument.

module Printable
  def self.included(base)
    puts "#{base} just included #{self}"
  end

  def print
    puts content
  end
end

class Report
  include Printable
end
# Output: Report just included Printable

The included callback is the workhorse of Ruby’s DSL-building. The most common pattern uses it to add class methods to whatever class includes your module. By calling base.extend(ClassMethods) inside the callback, you give the including class access to the module’s class-level methods without forcing the user to call extend separately. This is how Rails gems attach helper methods, validations, and configuration DSLs to your models:

module MacroMethods
  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def foo
      puts "foo!"
    end
  end
end

class MyClass
  include MacroMethods
end

MyClass.foo  # => "foo!"

Because include is processed at class definition time, included fires during the class definition itself. Frameworks like ActiveRecord use this to set up validation rules, associations, and callbacks the moment your model class is defined.

That timing is the main reason the callback is so useful. You can attach behavior exactly once, right when the module becomes part of the class, and then let the class use those methods normally after that.

That is also why included shows up so often in Rails-style code. It lets a module bring its own class-level helpers, validations, or configuration into the host class without making the host class manually call a setup method every time.

the extended callback: module extension

Where include gives a class instance methods, extend gives an object (or a class) singleton methods. When extend is called, Ruby fires self.extended:

module SayHello
  def self.extended(obj)
    puts "Extended onto #{obj}"
  end

  def say_hello
    "Hello, #{self}!"
  end
end

obj = Object.new
obj.extend(SayHello)
# Output: Extended onto #<Object:0x00007f9a2c03a418>

obj.say_hello  # => "Hello, #<Object:0x00007f9a2c03a418>!"

When you extend a class itself (rather than an instance), the class gets the module’s methods as class methods. The extended callback fires once, giving you a hook to set up class-level state or register the class in a registry. Notice how User.extend(AdminFeatures) gives User the admin_panel method as a class method without touching the inheritance hierarchy:

class User
end

module AdminFeatures
  def self.extended(klass)
    puts "AdminFeatures added to #{klass}"
  end

  def admin_panel
    "Admin panel for #{self}"
  end
end

User.extend(AdminFeatures)
User.admin_panel  # => "Admin panel for User"

This pattern lets you attach capabilities to specific objects at runtime without affecting other instances of the same class.

Use extended when you want a module to become a set of class methods or object-level helpers without changing the rest of the inheritance tree. It is a clean way to add capabilities only where you need them.

The important detail is scope. extend changes one object at a time, so you can add behavior exactly where it belongs instead of altering the class globally. That makes it a good fit for one-off helpers, configuration objects, and small DSL entry points.

the prepended callback: module prepending

prepend works like include, but with a critical difference in method lookup order. When a module is prepended, Ruby places it before the class in the method lookup chain. This means the module’s methods take priority and can call super to reach the original:

module UpcaseName
  def self.prepended(base)
    puts "#{base} prepended #{self}"
  end

  def name
    "PREFIX: #{super}"
  end
end

class Person
  prepend UpcaseName

  def name
    "Alice"
  end
end

p = Person.new
p.name  # => "PREFIX: Alice"

With include, if both the module and class define name, the class wins. With prepend, the module wins. For decorating or wrapping existing methods, this is much cleaner than alias-chaining. The example below shows how a prepended module can wrap an initialize method, printing a log message before calling the original constructor. The prepended module sits before the class in the method-lookup chain, so super inside the module resolves to the class’s own implementation:

module Timestamper
  def self.prepended(base)
    base.alias_method :original_initialize, :initialize
  end

  def initialize
    puts "Object being created"
    original_initialize
  end
end

ActiveSupport uses prepend extensively for method wrapping. Before Module#prepend existed, Rails used alias_method_chain which was essentially manual prepend via aliasing.

prepend is the right tool when you want to wrap behavior rather than replace it outright. Because the module sits before the class in the lookup path, super gives you a clean way to call the original implementation after your wrapper does its work.

That makes prepend especially good for instrumentation, logging, or safety checks. You can add work before or after the original method and still keep the original implementation available, which is much easier to reason about than old alias-based techniques.

the inherited callback: class inheritance

When a class subclasses another class, Ruby calls self.inherited on the parent class, passing the new subclass:

class Animal
  def self.inherited(subclass)
    puts "#{subclass} inherited from #{self}"
    @subclasses ||= []
    @subclasses << subclass
  end
end

class Dog < Animal; end
class Cat < Animal; end

Animal.subclasses  # => [Dog, Cat] - Ruby 2.4+

The inherited hook is the foundation of auto-registration patterns. A plugin system can track every class that inherits from a base without requiring manual registration calls. The parent class maintains a list of its descendants and the hook appends each new subclass automatically. This is the same mechanism Rails uses in ApplicationRecord and ApplicationJob:

class Plugin
  @plugins = []

  def self.inherited(subclass)
    @plugins << subclass
    puts "Registered: #{subclass}"
  end

  def self.registered_plugins
    @plugins
  end
end

class MyPlugin < Plugin; end
class AnotherPlugin < Plugin; end

Plugin.registered_plugins  # => [MyPlugin, AnotherPlugin]

In Rails, ApplicationRecord uses inherited to ensure every model gets the same base setup automatically. You subclass ApplicationRecord, and the framework hooks handle the rest.

This is the same pattern you see in plugin registries and base classes that need to track subclasses. The hook turns subclass creation into a point where the parent can collect metadata, set defaults, or register the child class for later use.

If you need to know what subclasses exist, inherited gives you a natural place to collect that information. The parent class becomes the registry owner, and every new subclass is recorded at the moment it appears instead of being discovered later through a scan.

method missing: intercepting undefined calls

Every time you call a method Ruby can’t find in the normal lookup chain, it calls method_missing on the receiver, passing the method name and any arguments. To understand how blocks and Ruby blocks, procs, and lambdas relate to this, it helps to know that method_missing receives whatever arguments the caller passed, including any block attached via &:

class HashLike
  def initialize(data = {})
    @data = data
  end

  def method_missing(method_name, *args, &block)
    if method_name.to_s.end_with?("=")
      key = method_name.to_s.chomp("=").to_sym
      @data[key] = args.first
    else
      @data[method_name.to_sym]
    end
  end
end

h = HashLike.new
h.user_name = "Alice"
h.user_name  # => "Alice"

method_missing is powerful but you need a companion method. When Ruby evaluates respond_to?(:user_name), it checks the method table and returns false unless you override respond_to_missing?. Without this companion, code that uses respond_to? before calling dynamic methods — such as delegation libraries, testing frameworks, or serializers — will incorrectly report that your dynamic methods do not exist:

class HashLike
  def respond_to_missing?(method_name, include_private = false)
    method_name.to_s.end_with?("=") || @data.key?(method_name.to_sym)
  end
end

h = HashLike.new
h.respond_to?(:user_name=)  # => true
h.respond_to?(:foo)         # => false

Without respond_to_missing?, code using respond_to? before calling dynamic methods gets incorrect information. Always implement both or you’ll break send, delegate, and other metaprogramming tools.

This hook is different from the others in the guide because it reacts to a missing method name rather than a lifecycle event. That makes it flexible enough for DSLs and dynamic lookup, but it also means you need to be stricter about guards so you do not hide typos or swallow errors that should stay visible.

That stricter approach is what keeps dynamic APIs understandable. The hook should recognize a clear pattern, handle only that pattern, and let everything else fail normally so bugs are easy to spot.

tracking method definitions

Ruby also fires callbacks when methods are added, removed, or undefined on a class or module. The most commonly used is method_added, which fires each time an instance method is defined:

class Tracker
  def self.method_added(name)
    puts "Instance method defined: #{name}"
  end

  def one; end
  def two; end
end
# Output:
# Instance method defined: method_added
# Instance method defined: one
# Instance method defined: two

Note that method_added fires for itself when you define it on the class — that’s why you see method_added as the first logged name.

There are matching hooks for singleton methods and for removal/undefinition. The example below shows a pattern where a module uses included to install method-tracking hooks onto the host class, giving you visibility into every method definition that happens after the module is mixed in:

module Plugin
  def self.included(base)
    base.extend(PluginHooks)
  end

  module PluginHooks
    def method_added(name)
      puts "Method added: #{name}"
    end

    def method_removed(name)
      puts "Method removed: #{name}"
    end
  end
end

These hooks are useful for building AOP-style instrumentation or for tracking what methods a class or module defines over time.

They are especially helpful when a library wants to observe how a class changes after it has already been loaded. A plugin registry, a test helper, or a code loader can use these callbacks to record exactly when behavior was defined or removed.

That makes them a good fit for diagnostics as well as automation. You can log when a method appears, keep a history of load order, or trigger follow-up setup whenever a class changes in a way the rest of the app cares about.

putting it together: a self-registering service pattern

Here’s a pattern that combines multiple hooks to build a self-registering service class. It uses included to set up class-level state, and inherited to automatically register any subclass:

module Registrable
  def self.included(base)
    base.extend(ClassMethods)
    base.class_eval { @registry = [] }
  end

  module ClassMethods
    def register(klass)
      @registry << klass
    end

    def registered
      @registry
    end
  end

  def self.inherited(subclass)
    subclass.class_eval { @registry = [] }
    register(subclass)
  end
end

class BaseService
  include Registrable
end

class UserService < BaseService
  def self.name
    "UserService"
  end
end

class PaymentService < BaseService
  def self.name
    "PaymentService"
  end
end

BaseService.registered  # => [UserService, PaymentService]

You can add new services without changing any registration code. The hooks handle it automatically.

That is the sweet spot for these callbacks: tiny pieces of behavior that fire at the right lifecycle moment, without manual glue code in every class. Once you see the pattern, you can reuse it for registries, plugins, and class-level setup hooks throughout your own codebase.

The result is less repetition and fewer special-case initializers. Instead of asking each service to register itself, the framework can observe the class lifecycle and collect the services in one place.

If you want to keep going, compare these callback hooks with define_method, singleton_class, and method_missing in isolation. Those tools cover the next layer of Ruby metaprogramming, where you choose whether the behavior should be attached at definition time, runtime, or lookup time.

summary

Hooks and callbacks give Ruby a structured way to respond to lifecycle events. Use included and extended when a module should shape another class or object, use prepend when you want to wrap existing behavior, use inherited when subclasses need to register themselves, and use method_missing only when dynamic lookup is truly the cleanest choice.

See Also