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 |