In Ruby, what is the difference between include and extend when you mix an Auditable module into a Product class?
answer
- who gains the methods
- include: every Product instance
- extend on the class: Product itself
- modules only, never a class
- include returns self, extend the object
basics
~20 sinclude 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 linesmodule 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 # NoMethodErrorgo deeper
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.
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.
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.
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