CrackedRuby logo

CrackedRuby

Namespacing with Modules

Overview

Ruby modules serve as namespaces that group related classes, constants, and methods under a common identifier. Modules prevent naming conflicts by creating separate scopes where identically named classes or constants can coexist without interference. Ruby implements namespacing through the Module class and the :: constant resolution operator.

Modules function as containers that encapsulate code and provide organizational structure. Unlike classes, modules cannot be instantiated directly but serve as mixins through inclusion or as standalone namespaces. The Ruby constant lookup mechanism searches through the nesting hierarchy, allowing nested modules to access outer module constants while maintaining clear boundaries.

module Authentication
  class User
    def initialize(name)
      @name = name
    end
  end
  
  TOKEN_EXPIRY = 3600
end

# Accessing namespaced class
user = Authentication::User.new("alice")
puts Authentication::TOKEN_EXPIRY # => 3600

Ruby's module system supports nested namespacing, where modules contain other modules, creating hierarchical organization. The interpreter maintains a constant table for each module, enabling fast constant resolution while preserving namespace isolation.

module API
  module V1
    class UsersController
      def index
        "V1 users"
      end
    end
  end
  
  module V2  
    class UsersController
      def index
        "V2 users" 
      end
    end
  end
end

puts API::V1::UsersController.new.index # => "V1 users"
puts API::V2::UsersController.new.index # => "V2 users"

Basic Usage

Creating namespaced modules requires defining a module with the module keyword and placing classes, constants, or methods inside. Ruby resolves constants using the scope resolution operator :: which explicitly specifies the namespace path.

module Database
  class Connection
    def initialize(host, port)
      @host = host
      @port = port
    end
    
    def connect
      "Connecting to #{@host}:#{@port}"
    end
  end
  
  DEFAULT_TIMEOUT = 30
  
  def self.establish_connection
    Connection.new("localhost", 5432)
  end
end

# Creating instances through namespace
conn = Database::Connection.new("example.com", 3306)
puts conn.connect # => "Connecting to example.com:3306"

# Accessing module constants
puts Database::DEFAULT_TIMEOUT # => 30

# Calling module methods
db_conn = Database.establish_connection

Module nesting creates hierarchical namespaces where inner modules inherit the namespace context of their containers. Each module maintains its own constant table while having access to constants defined in enclosing modules.

module Company
  FOUNDED_YEAR = 2010
  
  module Engineering
    TEAM_SIZE = 50
    
    module Backend
      class ApiServer
        def self.info
          "Founded: #{FOUNDED_YEAR}, Team: #{TEAM_SIZE}"
        end
      end
    end
  end
end

puts Company::Engineering::Backend::ApiServer.info
# => "Founded: 2010, Team: 50"

Reopening modules allows adding functionality to existing namespaces across multiple files. Ruby merges the definitions, maintaining the same constant table and enabling modular code organization.

# file: user_base.rb
module UserManagement
  class User
    attr_reader :name
    
    def initialize(name)
      @name = name
    end
  end
end

# file: user_extensions.rb  
module UserManagement
  class User
    def email=(email)
      @email = email
    end
    
    def email
      @email
    end
  end
  
  class Admin < User
    def privileges
      [:read, :write, :delete]
    end
  end
end

user = UserManagement::User.new("bob")
user.email = "bob@example.com"
puts user.email # => "bob@example.com"

Advanced Usage

Nested module definitions create complex namespace hierarchies that support sophisticated organizational patterns. Ruby maintains separate constant tables for each nesting level, enabling fine-grained control over scope and access.

module Enterprise
  module Authentication
    module Providers
      class LDAP
        def self.authenticate(username, password)
          # LDAP authentication logic
          "LDAP: #{username} authenticated"
        end
      end
      
      class OAuth2
        def self.authenticate(token)
          "OAuth2: token #{token} validated"
        end
      end
      
      def self.available_providers
        constants.map { |const| const_get(const) }
      end
    end
    
    def self.authenticate_user(provider, credentials)
      provider_class = Providers.const_get(provider)
      provider_class.authenticate(*credentials)
    end
  end
end

# Dynamic provider selection
providers = Enterprise::Authentication::Providers.available_providers
puts providers.map(&:name) # => ["Enterprise::Authentication::Providers::LDAP", "Enterprise::Authentication::Providers::OAuth2"]

result = Enterprise::Authentication.authenticate_user("LDAP", ["admin", "secret"])
puts result # => "LDAP: admin authenticated"

Module constants can be dynamically defined and accessed using metaprogramming techniques. The const_set and const_get methods provide runtime manipulation of the constant table.

module Configuration
  ENVIRONMENTS = %w[development staging production]
  
  ENVIRONMENTS.each do |env|
    const_set("#{env.upcase}_CONFIG", {
      database_url: "#{env}_db_url",
      api_key: "#{env}_api_key"
    })
  end
  
  def self.get_config(environment)
    const_get("#{environment.upcase}_CONFIG")
  end
  
  def self.set_dynamic_config(name, value)
    const_set(name.to_s.upcase, value)
  end
end

puts Configuration::DEVELOPMENT_CONFIG
# => {:database_url=>"development_db_url", :api_key=>"development_api_key"}

Configuration.set_dynamic_config(:custom_setting, "custom_value")
puts Configuration::CUSTOM_SETTING # => "custom_value"

Namespace aliasing through constant assignment creates shorter references to deeply nested modules, improving code readability while maintaining the original namespace structure.

module VeryLongCompanyName
  module InternalSystems
    module LegacyDatabaseAdapters
      class PostgreSQLAdapter
        def self.query(sql)
          "Executing: #{sql}"
        end
      end
      
      class MySQLAdapter  
        def self.query(sql)
          "MySQL: #{sql}"
        end
      end
    end
  end
end

# Create aliases for convenience
DB = VeryLongCompanyName::InternalSystems::LegacyDatabaseAdapters
PgAdapter = DB::PostgreSQLAdapter
MyAdapter = DB::MySQLAdapter

puts PgAdapter.query("SELECT * FROM users") # => "Executing: SELECT * FROM users"
puts MyAdapter.query("SELECT * FROM users") # => "MySQL: SELECT * FROM users"

Module functions defined with module_function become available both as instance methods when the module is included and as module-level methods when called directly on the module.

module MathUtils
  def calculate_tax(amount, rate)
    amount * rate
  end
  
  def format_currency(amount)
    "$#{'%.2f' % amount}"
  end
  
  module_function :calculate_tax, :format_currency
  
  module Advanced
    def compound_interest(principal, rate, time)
      principal * ((1 + rate) ** time)
    end
    
    module_function :compound_interest
  end
end

# Module-level access
tax = MathUtils.calculate_tax(100, 0.08)
formatted = MathUtils.format_currency(tax)
puts formatted # => "$8.00"

# Nested module function
interest = MathUtils::Advanced.compound_interest(1000, 0.05, 10)
puts MathUtils.format_currency(interest) # => "$1628.89"

Common Pitfalls

Constant resolution in nested modules can produce unexpected results when constants are defined in multiple scopes. Ruby searches constants using lexical scoping rules, checking the current nesting before searching outer scopes.

GLOBAL_SETTING = "global"

module Outer
  GLOBAL_SETTING = "outer"
  
  module Inner
    # This will find Outer::GLOBAL_SETTING, not ::GLOBAL_SETTING
    def self.show_setting
      GLOBAL_SETTING
    end
    
    # Explicit global access required
    def self.show_global_setting
      ::GLOBAL_SETTING
    end
    
    # Local constant shadows outer scopes completely
    GLOBAL_SETTING = "inner"
    
    def self.show_inner_setting
      GLOBAL_SETTING # Now returns "inner"
    end
  end
end

puts Outer::Inner.show_inner_setting # => "inner" 
puts Outer::Inner.show_global_setting # => "global"

# Removing the local constant changes resolution
Outer::Inner.send(:remove_const, :GLOBAL_SETTING)
puts Outer::Inner.show_setting # => "outer"

Module reopening can accidentally overwrite existing constants or methods if the same names are used across different files. Ruby issues warnings for constant redefinition but allows the operation.

module UserService
  VERSION = "1.0"
  
  def self.process_user(user)
    "Processing #{user} with version #{VERSION}"
  end
end

puts UserService.process_user("alice") # => "Processing alice with version 1.0"

# Later in another file - accidentally redefines VERSION
module UserService
  VERSION = "2.0" # Warning: already initialized constant UserService::VERSION
  
  def self.enhanced_process(user)
    "Enhanced processing #{user} with version #{VERSION}"  
  end
end

puts UserService.process_user("bob") # => "Processing bob with version 2.0" (unexpected!)
puts UserService::VERSION # => "2.0"

Circular dependencies between namespaced modules can cause loading issues when modules reference each other during class definition. Ruby's autoloading mechanism may fail to resolve dependencies correctly.

# problematic_circular.rb
module Services
  class UserService
    def create_user
      # References PaymentService during class loading
      PaymentService.setup_account
    end
  end
  
  class PaymentService  
    def self.setup_account
      # References UserService during class loading
      UserService.new.validate_user
    end
  end
end

Better approach using delayed resolution:

module Services
  class UserService
    def create_user
      # Resolve at runtime, not class definition time
      Services::PaymentService.setup_account
    end
  end
  
  class PaymentService
    def self.setup_account
      # Use dependency injection or delayed lookup
      user_service_class = Services.const_get(:UserService)
      user_service_class.new.validate_user
    end
  end
end

Namespace pollution occurs when including modules that define constants or methods conflicting with the including class's namespace. Module inclusion merges the module's constants into the class's constant table.

module Utilities
  VERSION = "util-1.0"
  
  def helper_method
    "Utility helper"
  end
end

class Application  
  VERSION = "app-2.0"
  
  include Utilities # This creates a conflict
end

# The class constant shadows the included module constant
puts Application::VERSION # => "app-2.0"

# But the included method is available
app = Application.new  
puts app.helper_method # => "Utility helper"

# Access to module's VERSION requires explicit path
puts Application.const_get("Utilities")::VERSION # Complex and error-prone

Reference

Module Definition Syntax

Pattern Description Example
module Name Define top-level module module Utils
module A::B Define nested module directly module Company::Auth
module A; module B Define through nesting module Company; module Auth
Module.new Create anonymous module mod = Module.new

Constant Access Methods

Method Parameters Returns Description
:: Module::CONSTANT Object Scope resolution operator
const_get(name) name (String/Symbol) Object Dynamic constant lookup
const_set(name, value) name, value Object Dynamic constant definition
const_defined?(name) name (String/Symbol) Boolean Check constant existence
const_missing(name) name (Symbol) Object Called when constant not found
constants None Array List all constants in module

Module Introspection Methods

Method Parameters Returns Description
nesting None Array Current module nesting stack
name None String/nil Module name or nil for anonymous
to_s None String String representation of module
ancestors None Array Module hierarchy including self
included_modules None Array Modules included in this module

Constant Resolution Rules

Context Search Order Description
Lexical scope Current → Outer → Global Based on nesting at definition
Instance method Class → Included → Super → Global Method context determines search
Class method Eigenclass → Class → Global Singleton class context
const_get Module only Searches only specified module
::CONST Global only Forces global constant lookup

Module Callbacks

Callback Triggered When Parameters Usage
included(mod) Module included in class mod (Module) Setup code for inclusion
extended(obj) Module extends object obj (Object) Setup code for extension
prepended(mod) Module prepended to class mod (Module) Setup code for prepending
const_missing(name) Undefined constant accessed name (Symbol) Autoloading or dynamic constants

Common Module Patterns

# Namespace container
module MyApp
  class User; end
  class Admin; end
end

# Configuration module
module Config
  DATABASE_URL = ENV.fetch('DATABASE_URL')
  API_VERSION = 'v1'
end

# Factory module  
module UserFactory
  def self.create(type)
    case type
    when :admin then AdminUser.new
    when :guest then GuestUser.new  
    end
  end
end

# Utility module with module functions
module StringUtils
  def titleize(str)
    str.split.map(&:capitalize).join(' ')
  end
  
  module_function :titleize
end

# Nested namespace with delegation
module API
  module V1
    def self.router
      @router ||= Router.new
    end
  end
  
  def self.current_version
    V1
  end
end

Error Classes

Error Cause Resolution
NameError Undefined constant Check constant name and nesting
TypeError Invalid constant assignment Ensure proper constant naming
ArgumentError Invalid module operation Verify method parameters
NoMethodError Missing module method Check method availability in context

Performance Considerations

Operation Complexity Notes
Constant lookup O(1) to O(n) Depends on nesting depth
Module inclusion O(n) Linear with number of methods
const_get O(1) Direct hash lookup
constants enumeration O(n) Scans entire constant table
Deep nesting access O(n) Each :: adds lookup cost