Namespacing with Modules
A Ruby module is a container for classes, methods, and constants that is never instantiated on its own — and one of its most common jobs is namespacing: giving a group of related code a shared prefix so its names don’t collide with unrelated code that happens to reuse the same name. Without namespacing, two libraries that each define a class called Circle, or a constant called VERSION, would silently overwrite each other. Wrapping related code in a module turns Circle into Shapes::Circle and Report::Circle — two completely distinct constants that coexist peacefully. This lesson covers how Ruby resolves those nested names, the two different ways to write them, and the mistakes that trip people up when Ruby’s lexical scoping rules don’t do what they expect.
Overview / How it works
Every class name, module name, and constant in Ruby lives inside a namespace, whether you declare one explicitly or not — top-level definitions live implicitly inside Object. When you write module Shapes; class Circle; end; end, you are declaring a constant named Circle nested inside the constant named Shapes, reachable from anywhere in your program as Shapes::Circle. Referencing a bare name like Circle or VERSION triggers Ruby’s constant lookup algorithm, and that algorithm is not as simple as \”search everywhere\”: Ruby first searches the current lexical scope outward — the literal, textual nesting of module/class keywords surrounding the point where the constant is referenced (Ruby exposes this list as Module.nesting) — and only if nothing turns up there does it fall back to the ancestor chain (superclasses and included modules) of the innermost enclosing class or module. This lexical-first behavior is exactly why the \”nested\” module form and the \”compact\” module form (module Api::V1) look similar but behave differently, as the examples below will show.
Modules used purely for namespacing usually aren’t included or extended into anything else — they exist just to hold nested classes, constants, and often module-level utility methods defined with self. or gathered under module_function. Because a module can never be instantiated (there is no Shapes.new), Ruby programmers lean on that restriction to signal \”this is an organizational container, not a type you create objects from.\” The standard library uses this pattern constantly: Math is a namespacing-and-utility module holding the constant Math::PI alongside module methods like Math.sqrt, and it is never mixed into anything.
Syntax
module Namespace
CONSTANT = "value"
class Inner
end
module Nested
end
def self.module_method
"called directly on the module"
end
end
module Namespace ... end— declares (or reopens) a module namedNamespace. Module and class names must start with a capital letter.Namespace::CONSTANT— the::scope resolution operator reaches into a namespace to fetch a constant, class, or module defined inside it.Namespace::Inner.new— becauseInneris a class nested insideNamespace, you instantiate it via its full path.module Namespace::Nested ... end— the \”compact\” form. It opensNesteddirectly, assumingNamespacealready exists, but crucially does not lexically nestNamespacearoundNested‘s body.def self.module_method— defines a method directly on the module object itself, callable asNamespace.module_methodwithout ever creating an instance.
Examples
Example 1: Avoiding a naming collision
module Shapes
class Circle
def initialize(radius)
@radius = radius
end
def area
(Math::PI * @radius**2).round(2)
end
end
end
module Report
class Circle
def initialize(title)
@title = title
end
def to_s
"Report: #{@title}"
end
end
end
circle1 = Shapes::Circle.new(4)
circle2 = Report::Circle.new("Q1 Circle Chart")
puts circle1.area
puts circle2
Output:
50.27
Report: Q1 Circle Chart
Two entirely unrelated classes are both named Circle, and that’s fine, because they live in different namespaces. Shapes::Circle and Report::Circle are different constants pointing to different class objects; Ruby never confuses them because you always reach one or the other through its full, namespaced path.
Example 2: A module as a utility namespace
module StringUtils
module_function
def shout(text)
"#{text.upcase}!"
end
def whisper(text)
text.downcase
end
end
puts StringUtils.shout("hello")
puts StringUtils.whisper("HELLO")
Output:
HELLO!
hello
module_function, called with no arguments, turns every method defined below it in the module body into a callable module method (StringUtils.shout) rather than a plain instance method. StringUtils is never instantiated or included anywhere — it’s a pure namespace for related helper methods, much like Math.
Example 3: Nested namespaces and constant lookup
module Api
VERSION = "1.0"
module V1
class User
def self.info
"User endpoint version #{VERSION}"
end
end
end
end
puts Api::V1::User.info
Output:
User endpoint version 1.0
Api::V1::User is three levels of namespacing deep. Inside User.info, the bare reference to VERSION resolves correctly because the class User block is lexically written inside module V1, which is lexically written inside module Api — so Ruby’s lexical search finds Api::VERSION without needing to write out the full path.
How it works step by step
Walking through Example 3 the way the Ruby interpreter does:
- When Ruby parses the call to
VERSIONinsideUser.info, it builds the list of lexically enclosing modules/classes at that exact point:[Api::V1::User, Api::V1, Api]. This list is available at runtime viaModule.nesting. - Ruby checks each entry from innermost to outermost for a constant literally named
VERSIONdefined directly on it: not found onUser, not found onV1, found onApi. Lookup stops there and returns"1.0". - Had lexical lookup failed everywhere, Ruby would fall back to searching the ancestor chain of the innermost enclosing class (
User‘s ancestors:User,Object,Kernel,BasicObject), and only after that raiseNameErrorif the constant truly can’t be found anywhere.
This two-phase search — lexical scope first, then the ancestor chain — is the single most important thing to understand about namespacing in Ruby, because it explains every mistake in the next section.
Common Mistakes
Mistake 1: The compact form breaks lexical nesting
The compact form module Api::V1 looks like a shorthand for nesting inside Api, but it isn’t — Module.nesting inside a compact-form body contains only Api::V1, never Api. Referencing a constant that lives on the outer module by its bare name fails:
module Api
VERSION = "1.0"
end
module Api::V1
class User
def self.info
"User endpoint version #{VERSION}"
end
end
end
puts Api::V1::User.info
Output:
NameError: uninitialized constant Api::V1::User::VERSION (raised before anything is printed)
The fix is either to switch to the fully nested form shown in Example 3, or to qualify the constant explicitly so lookup doesn’t depend on lexical nesting at all:
module Api
VERSION = "1.0"
end
module Api::V1
class User
def self.info
"User endpoint version #{Api::VERSION}"
end
end
end
puts Api::V1::User.info
Output:
User endpoint version 1.0
Mistake 2: Forgetting self. on a module-level method
A plain def inside a module defines an instance method — one that only becomes callable once the module is mixed into a class with include. Calling it directly on the module itself, the way you’d call Math.sqrt, fails:
module MathHelpers
def double(x)
x * 2
end
end
puts MathHelpers.double(4)
Output:
NoMethodError: undefined method 'double' for module MathHelpers (raised before anything is printed)
Defining the method with self. makes it a genuine module method:
module MathHelpers
def self.double(x)
x * 2
end
end
puts MathHelpers.double(4)
Output:
8
Mistake 3: Expecting module_function methods to work as normal mixin methods
module_function doesn’t just add a module-level copy of a method — it also makes the original instance method private. That’s invisible when you only ever call the module directly, but it surfaces the moment someone includes the module expecting ordinary public instance methods:
module Greetable
module_function
def greet(name)
"Hello, #{name}!"
end
end
class Person
include Greetable
end
puts Person.new.greet("Ada")
Output:
NoMethodError: private method 'greet' called for an instance of Person (raised before anything is printed)
If a module is meant to be mixed in, leave its methods as plain public instance methods instead of wrapping them in module_function:
module Greetable
def greet(name)
"Hello, #{name}!"
end
end
class Person
include Greetable
end
puts Person.new.greet("Ada")
Output:
Hello, Ada!
Best Practices
- Mirror your module nesting in your file layout — code for
Shapes::Circlebelongs inshapes/circle.rb— so the namespace is predictable from the directory structure alone. - Prefer the fully nested
module Api; module V1; ... end; endform over the compactmodule Api::V1form whenever inner code needs to reference constants from the outer module by their bare name, since only the nested form preserves lexical scoping. - Keep namespaces shallow — two levels, rarely three. A constant path like
App::Api::V1::Handlers::Users::Showis a sign the hierarchy has grown past the point of being helpful. - Use
def self.method_name, ormodule_function/extend selffor pure utility libraries, for anything meant to be called directly on the module — a baredefinside a namespacing-only module is unreachable code. - Reserve
module_functionfor modules that are never mixed into a class. If a module is meant to be a mixin, define ordinary public instance methods instead. - Favor the dot form (
Math.sqrt(9)) over::for calling module methods; reserve::for reaching into namespaces to access constants, classes, and nested modules, which is what current Ruby style guides recommend.
Practice Exercises
- Create two modules,
InventoryandStorefront, each defining a class namedItemwith different attributes (for example,Inventory::Itemtracks a quantity,Storefront::Itemtracks a price). Instantiate one of each and print a description from both to confirm there’s no naming collision. - Write a utility module
Temperaturewith module-level methodsto_fahrenheit(celsius)andto_celsius(fahrenheit), callable asTemperature.to_fahrenheit(100). Hint: the expected output ofTemperature.to_fahrenheit(100)is212.0. - Using the fully nested module syntax (not the compact form), build
App::Configholding a constantENV_NAME = "production", plus a classApp::Config::Loaderwhose class methodcurrentreturns"Loaded config for production"by referencingENV_NAMEthrough lexical scoping rather than a fully qualified path.
Summary
- Modules act as namespaces, letting classes, constants, and methods with the same short name coexist safely under different module prefixes.
- The
::operator reaches into a namespace to access a nested constant, class, or module. - Ruby resolves a bare constant reference by searching the lexical nesting first (
Module.nesting), then falling back to the ancestor chain of the innermost enclosing class or module. - The fully nested
module Foo; module Bar; end; endform preserves lexical nesting; the compactmodule Foo::Barform does not, which is a common source of surprisingNameErrors. - A module-level method needs
def self.method_name,module_function, orextend self— a plaindefinside a module only becomes usable once the module is mixed into a class. module_functionmethods become private instance methods when the module is included, which surprises anyone expecting a normal public mixin method.- Keep namespaces shallow and let your file layout mirror your module structure for readability.
