CrackedRuby logo

CrackedRuby

Module Constants

Overview

Module constants in Ruby provide a mechanism for defining immutable values within module namespaces. Ruby treats constants as any identifier beginning with an uppercase letter, storing them in a constant table associated with each module or class. The constant lookup mechanism follows a specific resolution path through the inheritance hierarchy and lexical scope.

Ruby modules serve as namespaces for organizing constants, preventing naming collisions while maintaining logical groupings. Constants defined within modules become accessible through the scope resolution operator :: or through direct reference when the module is included or prepended.

module Configuration
  DATABASE_URL = "postgresql://localhost:5432/app"
  MAX_CONNECTIONS = 100
end

Configuration::DATABASE_URL
# => "postgresql://localhost:5432/app"

Constants exhibit reference semantics rather than value semantics. Ruby prevents reassignment to constant names but does not freeze the objects they reference. This distinction creates important implications for mutable objects stored in constants.

module Settings
  FEATURES = ["authentication", "logging"]
end

Settings::FEATURES << "caching"  # Modifies the array
Settings::FEATURES
# => ["authentication", "logging", "caching"]

The constant lookup mechanism searches through multiple scopes: the current lexical scope, included modules, the inheritance chain, and finally Object and BasicObject. Understanding this resolution order prevents unexpected constant resolution behavior in complex module hierarchies.

Basic Usage

Define constants within modules by assigning values to uppercase identifiers. Ruby automatically adds these constants to the module's constant table, making them accessible through the module namespace.

module ApiClient
  BASE_URL = "https://api.example.com"
  TIMEOUT = 30
  RETRIES = 3
  
  def self.endpoint(path)
    "#{BASE_URL}#{path}"
  end
end

ApiClient::BASE_URL
# => "https://api.example.com"

ApiClient.endpoint("/users")
# => "https://api.example.com/users"

Access module constants using the scope resolution operator :: from outside the module context. Within the module, constants are accessible directly by name without qualification.

module MathConstants
  PI = 3.14159
  E = 2.71828
  
  def self.circle_area(radius)
    PI * radius * radius  # Direct access within module
  end
end

MathConstants::PI  # External access
# => 3.14159

MathConstants.circle_area(5)
# => 78.53975

Include modules to bring their constants into the current namespace. This creates a direct reference path without requiring the scope resolution operator for each access.

module Colors
  RED = "#FF0000"
  GREEN = "#00FF00"
  BLUE = "#0000FF"
end

class Button
  include Colors
  
  def initialize(color_name)
    @color = case color_name
             when :red then RED      # Direct access after include
             when :green then GREEN
             when :blue then BLUE
             end
  end
  
  attr_reader :color
end

Button.new(:red).color
# => "#FF0000"

Nest modules to create hierarchical constant namespaces. Each level maintains its own constant table while inheriting access to outer scopes through lexical scoping rules.

module Application
  VERSION = "1.0.0"
  
  module Database
    HOST = "localhost"
    PORT = 5432
    
    module Migrations
      DIRECTORY = "db/migrate"
      VERSION_FORMAT = "%Y%m%d%H%M%S"
      
      def self.version_file
        "#{DIRECTORY}/#{VERSION_FORMAT}_migration.rb"  # Accesses local constant
      end
      
      def self.app_version
        VERSION  # Accesses outer scope constant
      end
    end
  end
end

Application::Database::Migrations::DIRECTORY
# => "db/migrate"

Application::Database::Migrations.app_version
# => "1.0.0"

Advanced Usage

Constant lookup follows a sophisticated resolution algorithm that searches through lexical scope, inheritance hierarchy, and included modules. Ruby maintains a specific search order that affects which constant gets resolved when multiple definitions exist.

GLOBAL_CONSTANT = "global"

module A
  SHARED_CONSTANT = "from A"
  
  module B
    SHARED_CONSTANT = "from A::B"
    
    class C
      SHARED_CONSTANT = "from A::B::C"
      
      def self.lookup_test
        SHARED_CONSTANT  # Resolves to "from A::B::C"
      end
      
      def self.explicit_lookup
        A::SHARED_CONSTANT  # Resolves to "from A"
      end
    end
  end
end

A::B::C.lookup_test
# => "from A::B::C"

A::B::C.explicit_lookup
# => "from A"

Dynamic constant definition using const_set allows runtime constant creation and modification. This technique enables metaprogramming scenarios where constant names are determined programmatically.

module DynamicConfig
  CONFIG_DATA = {
    "database_host" => "localhost",
    "cache_size" => 1024,
    "debug_mode" => true
  }
  
  CONFIG_DATA.each do |key, value|
    const_set(key.upcase, value)
  end
end

DynamicConfig::DATABASE_HOST
# => "localhost"

DynamicConfig::CACHE_SIZE
# => 1024

DynamicConfig.constants
# => [:CONFIG_DATA, :DATABASE_HOST, :CACHE_SIZE, :DEBUG_MODE]

Constant introspection provides runtime access to constant information including existence checking, value retrieval, and constant enumeration. These methods enable dynamic behavior based on available constants.

module FeatureFlags
  AUTHENTICATION = true
  LOGGING = false
  CACHING = true
  
  def self.enabled_features
    constants.select do |const_name|
      const_get(const_name) == true
    end.map(&:to_s).map(&:downcase)
  end
  
  def self.feature_enabled?(name)
    const_name = name.to_s.upcase
    const_defined?(const_name) && const_get(const_name)
  end
end

FeatureFlags.enabled_features
# => ["authentication", "caching"]

FeatureFlags.feature_enabled?(:logging)
# => false

FeatureFlags.feature_enabled?(:nonexistent)
# => false

Module constant inheritance creates complex scenarios when modules are included into classes or other modules. Constants from included modules become accessible in the including context, but the resolution order depends on the inclusion hierarchy.

module Configurable
  DEFAULT_TIMEOUT = 30
  DEFAULT_RETRIES = 3
end

module Cacheable  
  DEFAULT_TTL = 300
  DEFAULT_TIMEOUT = 60  # Conflicts with Configurable::DEFAULT_TIMEOUT
end

class HttpClient
  include Configurable
  include Cacheable  # Included last, takes precedence for DEFAULT_TIMEOUT
  
  def initialize
    @timeout = DEFAULT_TIMEOUT   # Resolves to 60 from Cacheable
    @retries = DEFAULT_RETRIES   # Resolves to 3 from Configurable  
    @ttl = DEFAULT_TTL           # Resolves to 300 from Cacheable
  end
  
  attr_reader :timeout, :retries, :ttl
end

client = HttpClient.new
client.timeout  # => 60 (from Cacheable, not Configurable)
client.retries  # => 3
client.ttl      # => 300

Common Pitfalls

Constant reassignment generates warnings but does not prevent the operation. Ruby allows constant redefinition while issuing warnings, which can mask bugs in code that inadvertently redefines constants.

module Settings
  API_KEY = "development_key"
end

# Later in code...
module Settings
  API_KEY = "production_key"  # Warning: already initialized constant Settings::API_KEY
end

Settings::API_KEY
# => "production_key"

Autoloading conflicts arise when constant names match file paths that Rails or other autoloading systems expect to contain different definitions. This creates race conditions where the wrong constant gets loaded.

# In app/models/user.rb - Rails expects this to define User class
module User
  STATUSES = ["active", "inactive", "pending"]
end

# Attempting to access User class triggers autoload
# but finds User module instead, causing confusion
User.new  # NoMethodError: undefined method `new' for User:Module

Mutable constant objects create shared state problems when multiple parts of the application modify the same object. The constant name remains unchanged while the referenced object's state changes.

module Cache
  STORE = {}  # Mutable hash
  
  def self.set(key, value)
    STORE[key] = value
  end
  
  def self.get(key)
    STORE[key]
  end
end

Cache.set("user_123", {name: "John"})
Cache::STORE["user_456"] = {name: "Jane"}  # Direct mutation

Cache::STORE
# => {"user_123"=>{:name=>"John"}, "user_456"=>{:name=>"Jane"}}

Constant lookup ambiguity occurs when similar constant names exist in different scopes, leading to unexpected resolution results. The lexical scope rules can resolve constants from unexpected locations.

STATUS_OK = "global ok"

module ResponseCodes
  STATUS_OK = "response ok"
  
  class HttpResponse
    STATUS_OK = "http ok"
    
    def self.nested_lookup
      class << self
        STATUS_OK  # Resolves to "http ok" - class scope
      end
    end
    
    def status
      STATUS_OK  # Resolves to "http ok"
    end
    
    def global_status  
      ::STATUS_OK  # Explicit global scope
    end
  end
end

ResponseCodes::HttpResponse.new.status
# => "http ok"

ResponseCodes::HttpResponse.new.global_status  
# => "global ok"

Include order dependency creates fragile code where the order of module inclusion determines which constants are accessible and which take precedence during resolution.

module DatabaseConfig
  HOST = "db.example.com"
  PORT = 5432
end

module CacheConfig  
  HOST = "cache.example.com"
  PORT = 6379
end

class Application
  include DatabaseConfig
  include CacheConfig  # HOST and PORT now resolve to cache values
  
  def self.database_connection
    # Intends to use database config but gets cache config
    "#{HOST}:#{PORT}"  # => "cache.example.com:6379"  
  end
  
  def self.correct_database_connection
    "#{DatabaseConfig::HOST}:#{DatabaseConfig::PORT}"
  end
end

Application.database_connection
# => "cache.example.com:6379"

Application.correct_database_connection  
# => "db.example.com:5432"

Constant visibility assumptions fail when constants defined in private sections remain publicly accessible. Ruby constants ignore access modifiers, always remaining publicly accessible regardless of where they are defined.

module SecretConfig
  public
  
  PUBLIC_KEY = "public_key_value"
  
  private
  
  SECRET_KEY = "secret_key_value"  # Still publicly accessible!
  
  def self.get_secret
    SECRET_KEY  # Private method accessing constant
  end
  
  private_class_method :get_secret
end

SecretConfig::PUBLIC_KEY   # => "public_key_value" 
SecretConfig::SECRET_KEY   # => "secret_key_value" (unexpected!)

# SecretConfig.get_secret  # NoMethodError: private method

Reference

Constant Definition Methods

Method Parameters Returns Description
const_set(name, value) name (Symbol/String), value (Object) Object Defines or redefines a constant
const_defined?(name, inherit=true) name (Symbol/String), inherit (Boolean) Boolean Checks if constant exists
const_get(name, inherit=true) name (Symbol/String), inherit (Boolean) Object Retrieves constant value
const_missing(name) name (Symbol) Object Called when constant lookup fails
remove_const(name) name (Symbol/String) Object Removes constant definition

Constant Introspection Methods

Method Parameters Returns Description
constants(inherit=true) inherit (Boolean) Array<Symbol> Lists defined constants
const_source_location(name, inherit=true) name (Symbol/String), inherit (Boolean) Array<String, Integer> Returns definition location
private_constant(*names) names (Symbol/String) Module Marks constants as private
public_constant(*names) names (Symbol/String) Module Marks constants as public
deprecate_constant(*names) names (Symbol/String) Module Marks constants as deprecated

Constant Resolution Rules

Context Resolution Order Example
Inside Module Local → Lexical → Inherited → Object MyModule::CONST
Outside Module Explicit scope → Object Module::CONST
Included Module Including class → Included modules → Inherited class.include(mod)
Nested Module Inner → Outer → Lexical → Object A::B::C::CONST

Common Constants Patterns

Pattern Use Case Implementation
Configuration Application settings module Config; API_KEY = "..."; end
Enumeration Fixed value sets module Status; PENDING = 1; ACTIVE = 2; end
Namespace Logical grouping module Math; PI = 3.14159; end
Feature Flags Runtime behavior module Features; BETA_UI = true; end
Error Constants Exception hierarchies module Errors; NOT_FOUND = "404"; end

Constant Errors

Error Cause Resolution
NameError Constant not found Check spelling and scope
TypeError Invalid constant name Use uppercase identifier
ArgumentError Invalid const_get parameter Verify parameter types
FrozenError Modifying frozen constant Create new constant or unfreeze
LoadError Autoload failure Check file paths and dependencies

Performance Characteristics

Operation Time Complexity Notes
Constant lookup O(d) where d = depth Searches inheritance chain
const_defined? O(d) Same lookup cost
const_get O(d) Plus method call overhead
const_set O(1) Direct hash insertion
constants O(n) Returns all constant names

Thread Safety Considerations

Scenario Thread Safe Notes
Reading constants Yes Immutable references
Defining constants No Use synchronization
Modifying referenced objects No Object-level locking required
Constant introspection Yes Read-only operations
Autoloading No Framework-dependent