In Ruby, how do module_function and extend self differ for a utility module such as AuditFormat that is also mixed into classes?
answer
- both allow AuditFormat.method calls
- module_function makes copies
- instance copies become private
- extend self shares one method
- not available inside a class
basics
~20 smodule_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.
solid answer
~40 sBoth let you call `AuditFormat.change_line(...)` on the module. `module_function` **copies** the method: the module gets a public singleton copy, and the original instance method is made **private**, so a class that includes `AuditFormat` can call `change_line` internally but not `product.change_line` from outside. Because they are copies, redefining the instance method later does not change `AuditFormat.change_line`. `extend self` adds the module to its own singleton class, so there is **one** method: public on the module and public on every includer, and a later redefinition shows up everywhere. `Math` in the core library is built with module functions, so `Math.sqrt(2)` works and an includer gets a private `sqrt`. `module_function` is undefined in classes and works only in modules.
code
ruby · 21 linesmodule AuditFormat
module_function
def change_line(field, from, to) = "#{field}: #{from} -> #{to}"
end
module AuditTags
extend self
def tag(model) = "[#{model.class.name}]"
end
class Product
include AuditFormat
include AuditTags
def describe = "#{tag(self)} #{change_line(:price, 10, 12)}"
end
AuditFormat.change_line(:qty, 1, 2) # => "qty: 1 -> 2"
AuditTags.tag(Product.new) # => "[Product]"
Product.new.describe # => "[Product] price: 10 -> 12"
Product.new.tag(Product.new) # => "[Product]" (public via extend self)
Product.new.change_line(:a, 1, 2) # NoMethodError: private methodgo deeper
Recall that both let you call helpers directly on the module, as AuditFormat.change_line, and that the core Math module is built with module functions.
Explain copies versus one shared method, private versus public instance versions, and what a later redefinition affects in each case.
Pick the idiom from how the module is used: a function library that should not widen includers' public API, or a mixin that should expose one consistent method, and explain the patching trap.
Set a house style for utility modules so helpers behave the same across the codebase, weighing function-library modules against mixins and against plain def self. methods that forbid mixing in.
## Two ways to make module methods callable on the module A **utility module** holds stateless helpers: formatting an audit line, converting units. Callers want `AuditFormat.change_line(:price, 10, 12)`, and some classes also want to mix the helpers in and call `change_line` without a receiver. Ruby offers two idioms. | | `module_function` | `extend self` | |---|---|---| | Call on the module | `AuditFormat.change_line` works | `AuditFormat.change_line` works | | Methods involved | two copies per method | one method | | Visibility when included | private | public (as written) | | Later redefinition of the instance method | not seen by `AuditFormat.change_line` | seen everywhere | | Scope | methods named, or all defined after a bare call | every instance method of the module | | Available in | modules only | modules | ## How module_function works `module_function` is a private method of `Module`, used in two forms: 1. **With names**: `module_function :change_line` affects the named, already-defined methods. 2. **Without arguments**: every method defined after it in the module body becomes a module function, like `private` with no arguments. For each method it does two things: it defines a **copy** as a singleton method of the module, and it makes the **instance method private**. The core documentation stresses that the copies "may be changed independently": reopening `AuditFormat` and redefining `change_line` changes what includers call, but `AuditFormat.change_line` keeps the old body. The core `Math` module works this way: `Math.sqrt(16)` is public, and a class that includes `Math` gets a private `sqrt` it can call internally. `module_function` is undefined for classes, so writing it inside a `class` body raises `NoMethodError`. ## How extend self works `extend self` inside `module AuditFormat` is `AuditFormat.extend(AuditFormat)`: the module is added to its own singleton class. The module's instance methods are then reachable on the module object through normal lookup, with no copies: - `AuditFormat.change_line` calls the one and only `change_line`. - A class that includes `AuditFormat` gets `change_line` as a **public** instance method, so `product.change_line(...)` is callable from outside. - Redefining `change_line` later affects both. - Methods marked `private` in the module are private on both sides, so `AuditFormat.some_private_helper` with an explicit receiver raises `NoMethodError`. ## Choosing between them - Pick **`module_function`** when the module is mainly a function library and mixing it in is an internal convenience; the private instance copies keep includers' public surface clean, which is why the core library uses it for `Math`. - Pick **`extend self`** when the module is primarily a mixin whose methods are also handy on the module itself, and you want one definition with consistent visibility. - Consider a plain module with `def self.change_line` when no one should mix it in at all. ## The same helper written three ways For a single `change_line` helper, the three designs lead to different call sites: 1. **`module_function`**: `AuditFormat.change_line(...)` anywhere, and `change_line(...)` without a receiver inside a class that includes `AuditFormat`; `product.change_line` is refused. 2. **`extend self`**: `AuditFormat.change_line(...)` anywhere, and `product.change_line(...)` from any caller once a class includes the module, because the method stays public. 3. **`def self.change_line`**: only `AuditFormat.change_line(...)`; including the module gives the class nothing, since the method belongs to the module object alone. Picking one of the three per module, and writing it at the top of the module body, tells the reader immediately how the helpers are meant to be called. ## Pitfalls interviewers probe - Expecting `module_function` helpers to be public on the includer, then getting `NoMethodError` (private method called) from `product.change_line`. - Patching a module-function helper by reopening the module and redefining the method, then finding `AuditFormat.change_line` unchanged because the singleton copy was made earlier. - Mixing `private` and `extend self` and being surprised that private helpers cannot be called as `AuditFormat.helper`. - Using instance variables inside such helpers: with `extend self`, `@cache` on the module object and `@cache` on an includer are different variables, which is a hint the helper should be stateless.
- Why does reopening AuditFormat and redefining change_line not change AuditFormat.change_line when module_function was used?`module_function` copied the method into the module's singleton class when it ran. Redefining the instance method replaces only the instance version that includers use; the singleton copy keeps the old body until `module_function` is applied to the new definition too.
- Why does AuditTags.helper raise NoMethodError when helper is marked private in a module that uses extend self?With `extend self` there is only one method, and its visibility applies everywhere. A private method cannot be called with an explicit receiver, so `AuditTags.helper` is refused, while other methods of the module can still call `helper` without a receiver. `module_function` avoids this because the module-level copy is public.
saying these in an interview costs you the question
- module_function and extend self are two spellings of the same thing
- module_function keeps the instance methods public for classes that include the module
- With extend self, the module and its includers each get separate copies of every method
- module_function can be used inside a class body to make class methods
- Redefining a module_function method later updates the module-level copy too