CrackedRuby logo

CrackedRuby

Namespace Design

Ruby namespace design organizes code through modules, constant scoping, and hierarchical naming to prevent conflicts and create logical code boundaries.

Patterns and Best Practices Code Organization
11.3.2

Overview

Ruby namespace design structures applications through modules that act as containers for constants, methods, and classes. The module system creates hierarchical namespaces that isolate code, prevent naming conflicts, and establish logical boundaries within applications.

Ruby resolves constants through lexical scoping, searching from the current nesting level outward to the top-level namespace. This resolution mechanism enables nested module definitions where inner constants shadow outer ones, and qualified constant access using the :: operator.

module Authentication
  SECRET_KEY = "auth_secret"
  
  module OAuth
    SECRET_KEY = "oauth_secret"
    
    def self.authenticate(token)
      # Uses OAuth::SECRET_KEY ("oauth_secret")
      validate_token(token, SECRET_KEY)
    end
  end
  
  def self.basic_auth(username, password)
    # Uses Authentication::SECRET_KEY ("auth_secret")
    encrypt_password(password, SECRET_KEY)
  end
end

The include and extend methods modify namespace behavior by adding module methods to classes or instances. Include adds instance methods, while extend adds class methods, both affecting method lookup and constant resolution.

module Loggable
  LOG_LEVEL = "INFO"
  
  def log_message(msg)
    puts "[#{LOG_LEVEL}] #{msg}"
  end
end

class ApiClient
  include Loggable  # Adds log_message as instance method
end

class DatabaseConnection
  extend Loggable   # Adds log_message as class method
end

Module nesting creates namespace hierarchies where constants defined in outer modules become accessible to inner modules without qualification. This scoping enables organized code structures that mirror domain boundaries.

Basic Usage

Creating namespaced modules requires defining module containers that group related functionality. Constants, classes, and methods defined within modules exist in that namespace scope.

module PaymentProcessing
  TIMEOUT_SECONDS = 30
  
  class CreditCardProcessor
    def initialize(gateway)
      @gateway = gateway
      @timeout = TIMEOUT_SECONDS  # Accesses PaymentProcessing::TIMEOUT_SECONDS
    end
    
    def charge(amount, card)
      # Implementation
    end
  end
  
  class PayPalProcessor
    def charge(amount, account)
      # Implementation  
    end
  end
end

# Usage
processor = PaymentProcessing::CreditCardProcessor.new("stripe")

Accessing namespaced elements requires the fully qualified constant path or working within the module's scope. The :: operator provides explicit constant access from any context.

# Explicit access
payment_processor = PaymentProcessing::CreditCardProcessor.new(gateway)
timeout_value = PaymentProcessing::TIMEOUT_SECONDS

# Working within namespace scope
module PaymentProcessing
  def self.create_processor(type)
    case type
    when :credit_card
      CreditCardProcessor.new("default")  # No qualification needed
    when :paypal
      PayPalProcessor.new
    end
  end
end

Nested modules create deeper namespace hierarchies for complex domain modeling. Each level adds specificity while maintaining access to parent namespace constants.

module ECommerce
  SITE_NAME = "MyStore"
  
  module Catalog
    DEFAULT_CURRENCY = "USD"
    
    module Pricing
      TAX_RATE = 0.08
      
      class Calculator
        def calculate_total(subtotal)
          # Accesses all parent constants
          tax = subtotal * TAX_RATE
          puts "Calculating for #{SITE_NAME} in #{DEFAULT_CURRENCY}"
          subtotal + tax
        end
      end
    end
  end
end

Including modules adds their constants to the includer's namespace, creating shortcuts for accessing nested functionality while maintaining namespace boundaries.

module DatabaseOperations
  CONNECTION_POOL_SIZE = 10
  
  def execute_query(sql)
    # Database logic
  end
end

class UserRepository
  include DatabaseOperations
  
  def find_users
    puts "Pool size: #{CONNECTION_POOL_SIZE}"  # Direct access
    execute_query("SELECT * FROM users")      # Direct method access
  end
end

Advanced Usage

Metaprogramming with namespaces enables dynamic module creation and modification. The const_set and const_get methods manipulate constants programmatically, while define_method creates methods within namespace contexts.

module DynamicServices
  def self.create_service(name, endpoints)
    service_module = Module.new do
      endpoints.each do |endpoint, url|
        define_method(endpoint) do |params = {}|
          make_request(url, params)
        end
      end
      
      def make_request(url, params)
        # HTTP request implementation
        "Response from #{url} with #{params}"
      end
    end
    
    const_set("#{name.capitalize}Service", service_module)
  end
end

# Creates DynamicServices::UserService with get_profile and update_profile methods
DynamicServices.create_service(:user, {
  get_profile: "/api/users/profile",
  update_profile: "/api/users/profile/update"
})

client = Object.new.extend(DynamicServices::UserService)
client.get_profile(id: 123)

Anonymous modules provide namespacing without polluting the constant space. These modules exist at runtime without permanent constant assignments, useful for temporary scoping or dynamic behavior injection.

module FeatureFactory
  def self.create_feature_module(features)
    Module.new do
      features.each do |feature_name, implementation|
        define_method(feature_name, &implementation)
      end
      
      def enabled_features
        self.class.instance_methods(false)
      end
    end
  end
end

# Create anonymous module with specific features
reporting_features = FeatureFactory.create_feature_module({
  generate_pdf: -> { "Generating PDF report" },
  send_email: -> { "Sending email notification" },
  log_activity: -> { "Logging activity" }
})

class ReportGenerator
  include reporting_features  # No constant pollution
end

generator = ReportGenerator.new
generator.generate_pdf
generator.enabled_features  # => [:generate_pdf, :send_email, :log_activity]

Module composition creates complex namespace hierarchies through strategic include and extend operations. This pattern combines multiple namespace concerns into cohesive interfaces.

module Cacheable
  def self.included(base)
    base.extend(ClassMethods)
  end
  
  module ClassMethods
    def cache_key_prefix
      @cache_key_prefix ||= name.downcase.gsub('::', '_')
    end
  end
  
  def cache_key(id)
    "#{self.class.cache_key_prefix}_#{id}"
  end
end

module Serializable
  def to_hash
    instance_variables.each_with_object({}) do |var, hash|
      key = var.to_s.delete('@')
      hash[key] = instance_variable_get(var)
    end
  end
end

module Trackable
  def track_changes
    @changes ||= {}
  end
end

module DataAccess
  module ActiveRecord
    include Cacheable
    include Serializable  
    include Trackable
    
    def self.included(base)
      super
      base.extend(Cacheable::ClassMethods)
    end
  end
end

class User
  include DataAccess::ActiveRecord
  
  def initialize(name, email)
    @name, @email = name, email
  end
end

user = User.new("John", "john@example.com")
user.cache_key(123)    # => "user_123"
user.to_hash          # => {"name"=>"John", "email"=>"john@example.com"}

Prepend operations modify method lookup order within namespaces, enabling method decoration and aspect-oriented programming patterns within namespace boundaries.

module Instrumentation
  def self.prepended(base)
    base.extend(ClassMethods)
  end
  
  module ClassMethods
    def instrument_methods(*method_names)
      @instrumented_methods = method_names
    end
    
    def instrumented_methods
      @instrumented_methods || []
    end
  end
  
  def method_missing(method_name, *args, &block)
    if self.class.instrumented_methods.include?(method_name)
      start_time = Time.now
      result = super
      duration = Time.now - start_time
      puts "#{self.class.name}##{method_name} executed in #{duration}s"
      result
    else
      super
    end
  end
end

module BusinessLogic
  module OrderProcessing
    prepend Instrumentation
    
    instrument_methods :calculate_total, :apply_discounts
    
    def calculate_total(items)
      items.sum { |item| item[:price] * item[:quantity] }
    end
    
    def apply_discounts(total, discount_rate)
      total * (1 - discount_rate)
    end
  end
end

Common Pitfalls

Constant resolution confusion occurs when Ruby's lexical scoping rules produce unexpected results. Constants resolve based on nesting context, not inheritance or include relationships.

GLOBAL_CONFIG = "global"

module OuterModule
  GLOBAL_CONFIG = "outer"
  
  class BaseService
    def get_config
      GLOBAL_CONFIG  # Returns "outer" due to lexical scope
    end
  end
end

class MyService < OuterModule::BaseService
  GLOBAL_CONFIG = "my_service"
  
  def get_specific_config
    GLOBAL_CONFIG  # Returns "my_service"
  end
  
  def get_inherited_config
    super  # Still returns "outer", not "my_service"
  end
end

# Unexpected behavior
service = MyService.new
service.get_config          # => "outer" (not "my_service")
service.get_specific_config # => "my_service"
service.get_inherited_config # => "outer"

This pitfall requires explicit constant qualification to access intended values:

class MyService < OuterModule::BaseService
  def get_intended_config
    self.class::GLOBAL_CONFIG  # Explicit class constant access
  end
  
  def get_top_level_config
    ::GLOBAL_CONFIG  # Explicit top-level constant access
  end
end

Autoloading conflicts arise when namespace structure doesn't match file organization. Rails autoloading expects specific file paths that correspond to constant nesting.

# File: app/services/payment_processing.rb
# INCORRECT: Doesn't match expected namespace structure
class PaymentProcessor
  module CreditCard
    def self.process(amount)
      # Implementation
    end
  end
end

# File: app/services/payment_processing/credit_card.rb  
# CORRECT: Matches autoloading expectations
module PaymentProcessing
  module CreditCard
    def self.process(amount)
      # Implementation
    end
  end
end

Module inclusion order affects method lookup and can cause surprising behavior when multiple modules define the same method names.

module A
  def shared_method
    "From A"
  end
end

module B
  def shared_method
    "From B"
  end
end

class TestClass
  include A
  include B  # B's methods take precedence
end

TestClass.new.shared_method  # => "From B"

# Ancestry shows inclusion order
TestClass.ancestors  # => [TestClass, B, A, Object, BasicObject]

Namespace pollution occurs when modules expose unnecessary constants or methods to including classes. This problem grows severe in large applications with many mixed-in modules.

# PROBLEMATIC: Pollutes includer's namespace
module DatabaseHelpers
  CONNECTION_TIMEOUT = 30  # Becomes available to all includers
  DEBUG_MODE = true        # Unintended exposure
  
  def execute_query(sql)
    # Implementation
  end
  
  def log_query(sql)  # Private helper exposed publicly
    puts sql if DEBUG_MODE
  end
end

# BETTER: Encapsulate private constants and methods
module DatabaseHelpers
  def self.included(base)
    base.extend(ClassMethods)
  end
  
  module ClassMethods
    def connection_timeout
      30  # Encapsulated configuration
    end
  end
  
  def execute_query(sql)
    log_query(sql) if debug_enabled?
    # Implementation
  end
  
  private
  
  def debug_enabled?
    ENV['DB_DEBUG'] == 'true'  # External configuration
  end
  
  def log_query(sql)
    puts sql
  end
end

Circular dependency issues emerge in complex namespace hierarchies where modules attempt to reference each other during load time.

# user.rb
module Models
  class User
    has_many :orders, class_name: 'Models::Order'  # Forward reference
  end
end

# order.rb  
module Models
  class Order
    belongs_to :user, class_name: 'Models::User'   # Circular reference
  end
end

# Solution: Use string class names and lazy loading
module Models
  class User
    def orders
      @orders ||= Order.where(user_id: id)  # Runtime resolution
    end
  end
  
  class Order
    def user
      @user ||= User.find(user_id)  # Runtime resolution  
    end
  end
end

Production Patterns

Large application namespace organization follows hierarchical domain boundaries that reflect business logic and team ownership. This structure prevents naming conflicts while maintaining clear code boundaries.

# Domain-based organization
module UserManagement
  module Authentication
    class TokenValidator
      def validate(token)
        # Authentication logic
      end
    end
    
    class SessionManager
      def create_session(user_id)
        # Session creation
      end
    end
  end
  
  module Authorization
    class RoleChecker
      def authorized?(user, resource)
        # Authorization logic
      end
    end
  end
  
  module Profiles
    class ProfileUpdater
      def update(user_id, attributes)
        # Profile update logic
      end
    end
  end
end

module PaymentProcessing
  module Gateways
    class StripeGateway
      def charge(amount, card)
        # Stripe integration
      end
    end
    
    class PayPalGateway
      def charge(amount, account)
        # PayPal integration
      end
    end
  end
  
  module Reconciliation
    class TransactionMatcher
      def match_transactions(bank_data, system_data)
        # Reconciliation logic
      end
    end
  end
end

Configuration namespaces centralize application settings while maintaining environment-specific overrides. This pattern supports different deployment environments without code changes.

module Configuration
  module Database
    DEFAULT_POOL_SIZE = 5
    DEFAULT_TIMEOUT = 30
    
    def self.pool_size
      ENV['DB_POOL_SIZE']&.to_i || DEFAULT_POOL_SIZE
    end
    
    def self.timeout
      ENV['DB_TIMEOUT']&.to_i || DEFAULT_TIMEOUT  
    end
    
    def self.url
      ENV['DATABASE_URL'] || "postgresql://localhost/myapp_#{environment}"
    end
    
    def self.environment
      ENV['RAILS_ENV'] || 'development'
    end
  end
  
  module Redis  
    DEFAULT_URL = "redis://localhost:6379"
    
    def self.url
      ENV['REDIS_URL'] || DEFAULT_URL
    end
    
    def self.namespace
      "myapp:#{Database.environment}"
    end
  end
  
  module Features
    def self.enabled?(feature_name)
      ENV["FEATURE_#{feature_name.to_s.upcase}"] == 'true'
    end
  end
end

# Usage in application
if Configuration::Features.enabled?(:new_payment_flow)
  processor = PaymentProcessing::Gateways::StripeGateway.new
else
  processor = PaymentProcessing::LegacyProcessor.new
end

Service layer namespaces isolate business logic from framework dependencies, enabling testing and framework-agnostic code organization.

module Services
  module Users
    class RegistrationService
      def initialize(email_service: EmailServices::WelcomeMailer, 
                     validator: Validators::UserValidator.new)
        @email_service = email_service
        @validator = validator
      end
      
      def register(user_attributes)
        validation_result = @validator.validate(user_attributes)
        return validation_result unless validation_result.success?
        
        user = create_user(user_attributes)
        @email_service.send_welcome_email(user.email)
        
        Result.success(user)
      end
      
      private
      
      def create_user(attributes)
        # User creation logic
      end
    end
    
    class ProfileUpdateService  
      def update_profile(user_id, attributes)
        user = find_user(user_id)
        return Result.error("User not found") unless user
        
        updated_user = user.update(sanitize_attributes(attributes))
        Result.success(updated_user)
      end
      
      private
      
      def sanitize_attributes(attrs)
        attrs.slice(:name, :email, :phone)  # Whitelist approach
      end
    end
  end
  
  module Orders
    class CheckoutService
      include PaymentProcessing  # Namespace inclusion
      
      def process_order(cart, payment_info)
        total = calculate_total(cart.items)
        payment_result = charge_payment(total, payment_info)
        
        return payment_result unless payment_result.success?
        
        order = create_order(cart, payment_result.transaction_id)
        Result.success(order)
      end
    end
  end
end

Framework integration namespaces provide clean interfaces between application code and external frameworks like Rails or Sinatra.

module WebInterface
  module Controllers
    module Api
      class BaseController
        include AuthenticationHelpers
        include ErrorHandling
        
        private
        
        def authenticate_request
          token = request.headers['Authorization']
          @current_user = UserManagement::Authentication::TokenValidator
            .new
            .validate(token)
        end
      end
      
      class UsersController < BaseController
        def create
          result = Services::Users::RegistrationService
            .new
            .register(user_params)
          
          if result.success?
            render json: result.data, status: :created
          else
            render json: { errors: result.errors }, status: :unprocessable_entity
          end
        end
        
        def update
          result = Services::Users::ProfileUpdateService
            .new  
            .update_profile(params[:id], user_params)
            
          render json: result.data
        end
      end
    end
  end
  
  module Serializers
    class UserSerializer
      def initialize(user)
        @user = user
      end
      
      def to_json
        {
          id: @user.id,
          name: @user.name,
          email: @user.email,
          created_at: @user.created_at
        }
      end
    end
  end
end

Reference

Core Namespace Methods

Method Parameters Returns Description
Module.new(&block) Optional block Module Creates anonymous module with optional block evaluation
#include(module) Module self Adds module methods as instance methods
#extend(module) Module self Adds module methods as singleton methods
#prepend(module) Module self Adds module methods with higher precedence in lookup
#const_get(name, inherit=true) String/Symbol, Boolean Object Retrieves constant by name with optional inheritance
#const_set(name, value) String/Symbol, Object Object Sets constant in current namespace
#const_defined?(name, inherit=true) String/Symbol, Boolean Boolean Checks constant existence with optional inheritance
#constants(inherit=true) Boolean Array<Symbol> Lists constants in namespace with optional inheritance
#ancestors None Array<Module> Returns method lookup chain including modules
#included_modules None Array<Module> Lists directly included modules

Constant Resolution Rules

Context Resolution Order Example
Lexical nesting Inside-out scope traversal Module::InnerModule::CONSTANT
Class hierarchy Superclass chain lookup Parent class constants accessible
Include/Prepend Module ancestry chain Included module constants accessible
Top-level access ::CONSTANT syntax Explicit top-level namespace access
Dynamic access const_get with qualification "Namespace::Class".constantize

Module Hook Methods

Hook Method Timing Parameters Use Case
included(base) After include Including class/module Setup class methods, configuration
extended(base) After extend Extended object Initialize extended object state
prepended(base) After prepend Prepended class/module Method decoration, aspect-oriented setup
const_missing(name) Constant lookup failure Constant name symbol Autoloading, dynamic constant creation
method_missing(name, *args, &block) Method call failure Method name, arguments, block Dynamic method handling

Namespace Organization Patterns

Pattern Structure Benefits Use Cases
Domain-based Domain::Subdomain::Class Business logic alignment Large applications, team boundaries
Layer-based Controllers::Models::Services Architectural separation MVC frameworks, clear responsibilities
Feature-based Authentication::UserManagement Feature isolation Microservice preparation, modular design
Environment-based Production::Staging::Development Environment-specific code Configuration management, deployment

Common Constants and Namespaces

Constant Type Description Example Usage
::Object Class Top-level object class Default superclass, constant resolution
::Module Class Module class for metaprogramming Module.new, dynamic module creation
::Class Class Class class for metaprogramming Class.new, dynamic class creation
::Kernel Module Core Ruby methods module Method availability across objects
::BasicObject Class Minimal object implementation Proxy objects, method delegation

Error Types

Exception Trigger Condition Example Resolution
NameError Undefined constant/variable access UndefinedConstant Define constant or fix typo
NoMethodError Method call on object without method obj.undefined_method Define method or fix call
ArgumentError Wrong number of arguments method(arg1, arg2, arg3) Match expected parameters
TypeError Wrong object type for operation "string".length(5) Use correct object type
LoadError Failed require/load operation require 'nonexistent' Install gem or fix path