skip to content

Include, Extend & Prepend

include puts a module's methods above the class, prepend puts them below it, and extend adds them to one object's singleton class. Interviewers ask which one lets a module wrap a method.

on this pageshow

explore

questions

5

In Ruby, what is the difference between include and extend when you mix an Auditable module into a Product class?

level: juniorimportance: must knowfreq 80%

answer

  1. who gains the methods
  2. include: every Product instance
  3. extend on the class: Product itself
  4. modules only, never a class
  5. include returns self, extend the object

basics

~20 s

include Auditable makes the module's instance methods available on every Product instance. extend Auditable inside the class adds them to the Product class object itself, so they become class methods like Product.audited_fields, and instances do not get them.

solid answer

~40 s

`include` inserts the module into the class's method lookup chain, so its instance methods behave like instance methods of `Product`: `product.audit_trail` works, and `self` inside them is the product. `extend` adds the module's methods to one particular object; called inside `class Product`, that object is the class, so the methods become **class methods**: `Product.recent_changes` works while `Product.new.recent_changes` raises `NoMethodError`. Both accept only modules, so passing a class raises `TypeError`. A common design keeps instance behaviour and class-level queries in two modules, one included and one extended, so each lands on the right receiver. The class's own methods still take precedence over an included module's methods of the same name.

code

ruby · 20 lines
ruby
module Auditable
  def audit_trail = (@audit_trail ||= [])
  def record_change(field, from, to) = audit_trail << [field, from, to]
end

module AuditQueries
  def audited_fields = %i[name price quantity]
end

class Product
  include Auditable     # instance methods for every product
  extend AuditQueries   # class methods on Product itself
end

product = Product.new
product.record_change(:price, 10, 12)
product.audit_trail     # => [[:price, 10, 12]]
Product.audited_fields  # => [:name, :price, :quantity]
product.audited_fields  # NoMethodError
Product.audit_trail     # NoMethodError

go deeper

for a junior

Say who receives the methods: include gives them to instances, extend in a class body gives them to the class object, so they become class methods.

for a middle

Explain that include inserts the module into the ancestors after the class, why the class's own method wins, and why def self. methods in a module are not shared.

for a senior

Show how you split shared behaviour into an included module and an extended one across several models, and how later changes to a module reach every host because nothing is copied.

for a principal

Judge when a mixin is the right sharing mechanism at all across a large model layer, versus plain objects the models delegate to, given that every included method widens each host's public surface.

## Two receivers for the same module A Ruby **module** is a bundle of methods (and constants) that cannot be instantiated. It becomes useful when you attach it to something. `include` and `extend` attach it to different things: | | `include Auditable` in `class Product` | `extend AuditQueries` in `class Product` | |---|---|---| | Who gains the methods | every instance of `Product` (and subclasses) | the `Product` class object only | | Call site | `product.audit_trail` | `Product.recent_changes` | | `self` inside the method | the product instance | the `Product` class | | Instance variables touched | the product's | the class object's | | Return value of the call | `Product` (the receiver) | `Product` (the extended object) | The key idea is that `extend` always works on **one object**. Inside a class body, `self` is the class, so `extend AuditQueries` is `Product.extend(AuditQueries)`, and the methods become what Ruby developers call class methods. ## An inventory example An inventory system wants every `Product`, `Warehouse` and `StockMovement` to keep an audit trail, and wants class-level queries over audited records: - **`Auditable`** holds instance behaviour: `record_change(field, from, to)` and `audit_trail`. It is included, so each record gets it. - **`AuditQueries`** holds class-level behaviour: `audited_fields` and `recent_changes(limit)`. It is extended, so each model class gets it. Splitting them keeps each method on the receiver where it makes sense. Putting everything in one module and both including and extending it would give instances class-level methods they should not have, and the reverse. ## What include actually does `include` does not copy methods. It inserts the module into the class's **ancestors**, just after the class itself. Consequences worth knowing: 1. A method defined in `Product` wins over a same-named method in `Auditable`; the module's version runs only if `Product`'s method calls `super`. 2. Changing `Auditable` later (adding or redefining a method) is visible to every class that includes it, because the lookup goes through the module each time. 3. Subclasses of `Product` inherit the inclusion. When you need the module's method to run **before** the class's own method, the tool is `prepend`, not `include`. ## Where self and state point A module method has no object of its own; it always runs with `self` set to whatever received the call. That decides which instance variables it reads and writes: - Included into `Product`, `Auditable#audit_trail` runs with `self` as the product, so `@audit_trail` is stored per product record. - Extended onto `Product`, `AuditQueries#recent_changes` runs with `self` as the `Product` class, so any `@cache` it sets lives on the class object and is shared by the whole model. - The same module used both ways would keep two unrelated sets of instance variables, one per record and one per class, which is rarely what anyone intended. Keeping instance behaviour and class behaviour in separate modules makes this split obvious to the reader. ## Rules shared by both - **Modules only.** `include BaseModel` or `extend BaseModel` where `BaseModel` is a class raises `TypeError` (wrong argument type Class, expected Module). Classes are reused through inheritance. - **Several at once.** `include Auditable, Taggable` is allowed and returns the class, like a single include. - **Callbacks.** Each call also notifies the module (`included` or `extended`), which libraries use to add class methods automatically when a module is included; that hook pattern is a topic of its own. ## Common mistakes - Defining `def self.recent_changes` inside `Auditable` and expecting `include Auditable` to give `Product.recent_changes`. A `self.` method defined in a module belongs to the module object (`Auditable.recent_changes`) and is not copied anywhere by `include` or `extend`. - Extending the class and then calling the method on an instance, or including and calling it on the class. The error in both cases is `NoMethodError`, and the fix is choosing the other keyword. - Using `extend self` or `module_function` in a module meant as a mixin without realising they change what the module offers when used on its own.

  • Why doesn't include Auditable make def self.recent_changes, defined in Auditable, available as Product.recent_changes?
    `def self.recent_changes` inside `module Auditable` defines a singleton method of the module object itself. `include` and `extend` only share a module's instance methods, so that method stays callable only as `Auditable.recent_changes`. Class-level behaviour for the host has to live in instance methods of a module that the host extends.
  • If Product defines audit_trail itself, which version does product.audit_trail call after include Auditable?
    `Product`'s own method. An included module sits after the class in the lookup chain, so the class's definition is found first. The module's version runs only if `Product#audit_trail` calls `super`; to make the module's version run first, use `prepend`.

saying these in an interview costs you the question

  • include copies the module's methods into the class at that moment
  • extend inside a class gives every instance the module's methods
  • A module's def self.method becomes a class method of the class that includes it
  • You can include a class to share its methods without inheriting from it
  • An included module's method overrides the class's own method of the same name
open as a page

In Ruby, how does prepend differ from include, and why does only prepend let an AuditSave module wrap Product#save through super?

level: middleimportance: must knowfreq 60%

basics

~20 s

include places a module after the class in method lookup, so the class's own save wins. prepend places it before the class, so AuditSave#save runs first and its super calls Product#save, which makes wrapping possible.

open as a page

In Ruby, what happens when you call extend on one StockMovement instance instead of on the StockMovement class?

level: middleimportance: should knowfreq 35%

basics

~10 s

movement.extend(ManualReview) adds the module's methods to that one object only; other StockMovement instances and the class are unchanged. Calling StockMovement.extend(ManualReview) instead adds them to the class object as class methods.

open as a page

In Ruby, how do you check whether a Product class mixes in Auditable, and what do Module#include? and Module#< report for include, prepend and extend?

level: middleimportance: should knowfreq 30%

basics

~20 s

Product.include?(Auditable) returns true when Auditable is included or prepended in Product or an ancestor. A module extended onto the class is not in its ancestors, so check Product.singleton_class.include?(M). Product < Auditable also returns true, or nil when unrelated.

open as a page

In Ruby, how do module_function and extend self differ for a utility module such as AuditFormat that is also mixed into classes?

level: seniorimportance: should knowfreq 33%

basics

~20 s

module_function copies each method to the module's singleton and makes the instance version private, so mixers get private helpers and later redefinitions do not reach AuditFormat.x. extend self shares the very same public methods between the module and every includer.

open as a page