skip to content

Modules & Mixins

Ruby modules serve as namespaces and as mixins added with include, extend or prepend, plus hooks and scoped refinements. Interviewers probe composition in a language with one superclass.

on this pageshow

explore

questions

22

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, what does Module#ancestors return for a class, and how does Ruby use that list to pick which log method runs?

level: juniorimportance: must knowfreq 55%

basics

~20 s

Module#ancestors returns the ordered list of classes and modules Ruby searches for an instance method: the class, its mixed-in modules, then the superclass chain down to BasicObject. Calling job.log runs the first log found walking that list.

open as a page

In Ruby, how does a module serve as a namespace, and what does the leading :: in ::JSON change?

level: juniorimportance: must knowfreq 62%

basics

~20 s

A module is a container for constants, so Ingest::JSON::Parser and Ingest::CSV::Parser can coexist. :: walks into a module; a leading ::JSON starts the lookup at Object, the top level, bypassing a nested JSON that shadows it.

open as a page

In Ruby, how does a mixin's self.included(base) hook combined with base.extend(ClassMethods) give the including class both instance and class methods?

level: middleimportance: must knowfreq 62%

basics

~10 s

include only gives the host's instances the module's methods. The module's included hook calls base.extend(ClassMethods), mixing a nested module into the class itself, so its methods become class methods such as configuration macros.

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, for ImportJob < Job that prepends Redacted and includes ConsoleLog then FileLog, what does ancestors return, and where do extended modules fit?

level: middleimportance: must knowfreq 48%

basics

~20 s

ImportJob.ancestors is [Redacted, ImportJob, FileLog, ConsoleLog, Job, ...]: prepended modules, the class, included modules with the most recent first, then the superclass chain. For one object, its singleton class and extended modules come before all of that.

open as a page

In Ruby, why can class Ingest::CSV::Parser fail to see constants that the same class written inside module Ingest; module CSV sees?

level: middleimportance: must knowfreq 52%

basics

~20 s

Bare constants are searched through the lexical nesting first. Nested syntax puts Ingest::CSV and Ingest in that nesting; compact class Ingest::CSV::Parser puts only the class itself there, so constants of Ingest and Ingest::CSV are not found by bare name.

open as a page

In Ruby, which hook method runs on a module when a class includes, extends or prepends it, and what does each hook receive?

level: juniorimportance: should knowfreq 42%

basics

~10 s

Ruby calls a singleton method on the module: included(base) after include, prepended(base) after prepend and extended(obj) after extend. Each receives whatever took the module in, and runs once the module is already in place.

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, when a FileLog module's log method calls super, which method does that super reach, and what happens if nothing follows?

level: middleimportance: should knowfreq 42%

basics

~20 s

super in a module method continues the search from the entry after that module in the receiver's ancestors, so its target depends on the host class. If no later entry defines the method, Ruby raises NoMethodError (super: no superclass method).

open as a page

In Ruby, how does Module#const_get resolve a path string such as "CSV::Parser", and how do its inherit flag and a leading :: behave?

level: middleimportance: should knowfreq 28%

basics

~20 s

Module#const_get splits a String on :: and resolves each segment from the previous result, starting at Object when it begins with ::. The inherit flag (default true) applies to every segment; a Symbol path raises NameError, and a missing segment raises NameError.

open as a page

In Ruby, what does Module#private_constant block, and which ways of reaching a private constant still work?

level: middleimportance: should knowfreq 30%

basics

~20 s

Module#private_constant makes a scoped reference such as Ingest::JSON::Tokenizer raise NameError. Bare references from code inside the namespace, subclasses or includers still work, and const_get still returns it; it signals internal API, not a security boundary.

open as a page

In Ruby, why does a String refinement activated with using in one file raise NoMethodError when a helper from another file calls it?

level: middleimportance: should knowfreq 35%

basics

~20 s

Refinements are lexically scoped: a call sees a refinement only if its source text sits after a using in the same file or body. The helper's call was written in another file, so it looks up plain String and misses.

open as a page

In Ruby, how should a mixin require its including class to define a method, and why does checking method_defined? inside self.included usually fail?

level: seniorimportance: should knowfreq 32%

basics

~20 s

The included hook runs at the include line, before the class body defines its methods, so an eager method_defined? check rejects valid hosts. Declare the contract lazily: a stub raising a clear error, overridden by the host's own method.

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

In Ruby 4.0, what happens to ancestors when a module is included twice, re-included in a subclass, or prepended after already being included?

level: seniorimportance: should knowfreq 22%

basics

~20 s

Including a module already in the chain is a no-op: it is neither added again nor moved to the front, even when a superclass included it. Since Ruby 3.1, prepending a module the class already includes adds a second entry in front of the class.

open as a page

In Ruby, when a nesting module and a superclass both define DELIMITER, which one does a bare DELIMITER resolve to, and why does an inherited method ignore the subclass's override?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Ruby checks each module in Module.nesting (own constants only) before the ancestors, so the enclosing module's DELIMITER wins over the superclass's. Constant lookup is fixed where code is written, so an inherited method keeps its own scope; use self.class::DELIMITER for a per-subclass value.

open as a page

In Ruby, how do refine and using add a String#to_slug method without patching String for the whole program?

level: juniorimportance: nice to knowfreq 25%

basics

~20 s

Define the method inside refine String do ... end in a module, then call using with that module. Only code after that using line sees it, up to the end of the file or the class or module body.

open as a page

In Ruby, how does overriding a module's append_features differ from defining its included hook, and when would you override append_features?

level: seniorimportance: nice to knowfreq 18%

basics

~20 s

append_features does the mixing: its default inserts the module into the host's ancestors unless already present. included is a notification sent afterwards. Override append_features only to veto or alter insertion, and call super or nothing is mixed in.

open as a page

In Ruby 4.0, which dynamic calls such as send, public_send, method and &:sym honor an active refinement, and why does String.instance_methods not list it?

level: seniorimportance: nice to knowfreq 18%

basics

~20 s

Written in a scope after using, send, send, public_send, method, instance_method and &:to_slug all honor the refinement in Ruby 4.0. Listings such as methods and instance_methods ignore refinements, so a refined-only method never appears in them.

open as a page

In Ruby 4.0, why must a refine block reuse a helper module's methods through import_methods rather than include, and what can import_methods not copy?

level: seniorimportance: nice to knowfreq 12%

basics

~20 s

Inside refine, self is a Refinement whose include and prepend now raise TypeError. Refinement#import_methods, added in Ruby 3.1, copies a module's Ruby-defined methods into the refinement; C-implemented methods raise ArgumentError, and the module's ancestors are skipped with a warning.

open as a page