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
includedruns when a module is mixed into a class.extendedruns when a module is extended onto an object or class.prependedlets a module wrap or override methods before the class itself.inheritedruns when a class is subclassed.method_missinghandles undefined methods, but only when you also keeprespond_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.
Forward Link
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
- Ruby Module documentation — official reference for include, extend, and prepend
- Ruby Blocks, Procs, and Lambdas — the foundational concepts behind hooks and callbacks
- Using define_method — dynamically defining methods that hooks can track
- instance_eval and class_eval — evaluating code in the context of classes and instances