In Ruby, how does a module serve as a namespace, and what does the leading :: in ::JSON change?
answer
- constants live inside modules
- :: is the scope operator
- inner name shadows the outer
- leading :: starts at Object
- modules have no new
basics
~20 sA 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.
solid answer
~40 sIn Ruby, classes and modules are values stored in **constants**, and every module has its own constant table, so wrapping code in `module Ingest` gives each name a qualified path: `Ingest::JSON::Parser` and `Ingest::CSV::Parser` never collide. The `::` operator reads a constant from the module on its left, and it looks only at that module and its ancestors, not at the code's surroundings. A bare name such as `JSON` inside `module Ingest` finds `Ingest::JSON` first, which **shadows** the standard library's top-level `JSON`, so `JSON.parse` fails with `NoMethodError`. Writing `::JSON` starts the lookup at `Object`, where top-level constants live, and reaches the library module. A module cannot be instantiated, so it is a pure namespace unless you also mix it in.
code
ruby · 20 linesrequire "json"
module Ingest
module JSON
class Parser
def parse(raw)
# JSON.parse(raw) # NoMethodError: JSON here is Ingest::JSON
::JSON.parse(raw) # top-level JSON from the standard library
end
end
end
module CSV
class Parser; end
end
end
Ingest::JSON::Parser.new.parse('{"id": 1}') # => {"id" => 1}
Ingest::CSV::Parser == Ingest::JSON::Parser # => false
Ingest::String # NameError: uninitialized constant Ingest::Stringgo deeper
Explain that a module groups constants under a path like Ingest::CSV::Parser, that :: reads a constant from a module, and that ::JSON means the top-level JSON.
Show the shadowing case: an inner module named JSON hides the library for bare references, and a leading :: fixes it; mention that explicit paths do not fall back to Object.
Talk about naming policy in a shared gem: one root module, reusing a top-level name only deliberately, and writing ::Name consistently so readers and loaders agree on what each constant means.
Frame namespaces as the unit of ownership across teams and gems: they prevent collisions, but they are not an access boundary, so decide which constants are public API and mark the rest private_constant.
## Constants are the names of classes and modules In Ruby, `class Parser` and `module Ingest` are not special declarations. Each one creates an object (an instance of `Class` or `Module`) and stores it in a **constant** named `Parser` or `Ingest`. A constant is any name starting with an uppercase letter. Every class and module owns a **constant table**. When you write one module inside another, the inner constant is stored in the outer module's table, not at the top level: - `module Ingest` stores `Ingest` in `Object`'s table (the top level). - `module Ingest; module JSON` stores `JSON` in `Ingest`'s table, giving the path `Ingest::JSON`. - `class Parser` inside that stores `Parser` in `Ingest::JSON`'s table. That is all a **namespace** is in Ruby: a module used as a container for constants. A data-import gem can ship `Ingest::JSON::Parser` and `Ingest::CSV::Parser` side by side, and neither collides with a `Parser` defined by the application or by another gem. ## The :: scope operator `::` reads a constant out of the module named on its left. `Ingest::CSV::Parser` means: find `Ingest`, read `CSV` from it, then read `Parser` from that. | Expression | Where Ruby looks | |---|---| | `Parser` (bare) | the lexical nesting at that line, then the ancestors of the innermost class or module, then `Object` | | `Ingest::CSV` | `Ingest` and its ancestors only | | `::JSON` | `Object`, the top level | | `Ingest::String` | `Ingest` only; `NameError`, because an explicit path does not fall back to top-level constants | Two details of explicit paths matter in practice: 1. The left side must be a class or module. `"text"::Foo` raises `TypeError` saying the receiver is not a class/module. 2. An explicit path respects `private_constant`. A bare name used inside the namespace does not. ## Shadowing and the leading :: Naming an inner module after a library is common and useful (`Ingest::JSON` groups everything about JSON imports), but it **shadows** the library for every bare reference written inside `Ingest`: - Inside `module Ingest; module JSON; class Parser`, a bare `JSON` resolves to `Ingest::JSON`, because the lexical nesting is searched before anything else. - `JSON.parse(raw)` then raises `NoMethodError`: `Ingest::JSON` has no `parse` method. - `::JSON.parse(raw)` starts at `Object`, finds the standard library's `JSON` module (loaded with `require "json"`), and works. The leading `::` is the fix you reach for when a namespace deliberately reuses a top-level name. It costs nothing at run time and documents the intent. ## A module is not instantiable A module has no `new` method of its own: `Ingest.new` raises `NoMethodError`. That makes a module the natural namespace for code that is never an object in its own right, such as a gem's root: - constants such as `Ingest::VERSION` or a table of supported formats; - module-level methods defined with `def self.parse_file(path)`; - nested classes that do get instantiated, such as `Ingest::CSV::Parser.new(io)`. A class can act as a namespace too (`Ingest::Parser::Error`), which is common for an error class that belongs to one class. Pick a module when the container itself should never be instantiated or inherited from. ## Mistakes interviewers listen for - Treating a module as a package that must be imported: Ruby has no import step for namespaces; once the file defining `Ingest` is loaded with `require`, every path under it is reachable. - Believing a nested module inherits anything from its parent: `Ingest::CSV` is simply stored in `Ingest`'s table. It does not include `Ingest`, does not get its methods, and does not see `Ingest`'s constants except through the lexical nesting of code written inside `module Ingest`. - Expecting `Ingest::CSV::Parser` and `Ingest::JSON::Parser` to share anything because they share a name: they are two unrelated classes stored under two different modules. - Forgetting that the name is recorded once: `Ingest::CSV::Parser.name` returns `"Ingest::CSV::Parser"`, the full path of the first constant the class was assigned to. ## Practical conventions - Give a gem one root module named after the gem and keep every constant under it. - Mirror the path in the file layout (`lib/ingest/csv/parser.rb`), which is what file-based loaders expect. - Reuse a top-level name inside your namespace only on purpose, and then write `::Name` wherever you mean the outer one. - Remember that a namespace is not a boundary: any code can reopen `module Ingest` and add constants; only `private_constant` narrows access to a constant, and only for explicit `::` paths.
- Why does Ingest::String raise NameError when a bare String inside module Ingest works?A bare `String` inside `Ingest` falls back to `Object`, where top-level constants live. An explicit `Ingest::String` searches only `Ingest` and its ancestors and deliberately does not fall back to top-level constants, so a typo in a path cannot silently resolve to an unrelated top-level class.
- When would you namespace under a class instead of a module?When the nested constant belongs to that one class, such as `Ingest::CSV::Parser::Error` for errors only the parser raises. A class works as a namespace, but it can be instantiated and subclassed, so a gem root or a pure grouping of related classes is a module.
A module namespace works like a folder: two files called Parser can live in different folders, a bare name is resolved relative to the folder you are standing in, and ::JSON is an absolute path that starts at the root.
saying these in an interview costs you the question
- A bare JSON inside module Ingest always means the standard library JSON
- The leading :: in ::JSON calls a method named JSON
- Ingest::String finds the top-level String through the namespace
- You can call Ingest.new to get an instance of a module
- Namespacing with modules makes constants private to the gem