CrackedRuby logo

CrackedRuby

Constant Queries

Ruby's constant query methods provide introspection and dynamic access to constants within modules and classes.

Overview

Ruby's constant query system enables runtime introspection of constants defined within modules and classes. The core functionality revolves around methods like const_defined?, const_get, const_set, and constants, which operate on Module instances to examine, retrieve, and manipulate constant definitions.

Constants in Ruby exist within lexical scopes defined by modules and classes. Each Module maintains its own constant table, and Ruby provides mechanisms to query these tables programmatically. The constant lookup follows Ruby's standard resolution rules, searching through the inheritance hierarchy and lexical nesting when resolving constant references.

module Database
  HOST = "localhost"
  PORT = 5432
  
  class Connection
    TIMEOUT = 30
  end
end

# Check if constants exist
Database.const_defined?(:HOST)        # => true
Database.const_defined?(:MISSING)     # => false
Database::Connection.const_defined?(:TIMEOUT)  # => true

The constant query methods operate at the Module level, meaning any class or module can examine its own constants or those of other modules. This capability forms the foundation for many metaprogramming techniques, dynamic configuration systems, and reflection-based tools.

class Configuration
  def self.load_from_constants
    constants.each do |const_name|
      value = const_get(const_name)
      puts "#{const_name}: #{value}"
    end
  end
end

Configuration::DATABASE_URL = "postgresql://localhost/app"
Configuration::REDIS_URL = "redis://localhost:6379"
Configuration.load_from_constants
# DATABASE_URL: postgresql://localhost/app
# REDIS_URL: redis://localhost:6379

Ruby's constant queries support inheritance-aware lookups by default. When querying a constant on a class, Ruby searches the class itself, then its superclass chain, and finally any included modules. This behavior can be controlled through optional parameters that restrict searches to specific scopes.

Basic Usage

The primary interface for constant queries consists of four core methods available on all Module instances: const_defined?, const_get, constants, and const_set. Each method serves a specific purpose in constant introspection and manipulation.

const_defined? determines whether a constant exists within a module's scope. The method accepts a constant name as a symbol or string and returns a boolean value. By default, it searches through the inheritance hierarchy, but this behavior can be restricted to the receiver module only.

class Animal
  KINGDOM = "Animalia"
end

class Mammal < Animal
  CLASS_NAME = "Mammalia"
end

# Inheritance-aware queries
Mammal.const_defined?(:CLASS_NAME)  # => true
Mammal.const_defined?(:KINGDOM)     # => true (inherited)

# Restrict to current module only
Mammal.const_defined?(:KINGDOM, false)  # => false
Mammal.const_defined?(:CLASS_NAME, false)  # => true

const_get retrieves the value of a named constant. Like const_defined?, it follows inheritance rules by default and accepts an optional parameter to restrict the search scope. The method raises a NameError if the constant does not exist.

module Config
  DATABASE_HOST = "db.example.com"
  DATABASE_PORT = 5432
end

# Retrieve constant values
host = Config.const_get(:DATABASE_HOST)  # => "db.example.com"
port = Config.const_get("DATABASE_PORT") # => 5432

# Dynamic access with computed names
setting = "DATABASE_HOST"
value = Config.const_get(setting)  # => "db.example.com"

The constants method returns an array of symbols representing all constants defined in a module. This method supports the same inheritance behavior as the other query methods, allowing examination of constants from the entire hierarchy or just the receiver module.

module Logger
  DEBUG = 1
  INFO = 2
  WARN = 3
  ERROR = 4
  
  class FileLogger
    DEFAULT_PATH = "/var/log/app.log"
    MAX_SIZE = 1024 * 1024
  end
end

# Get all constants in module
Logger.constants  # => [:DEBUG, :INFO, :WARN, :ERROR, :FileLogger]

# Get constants from class including inherited
Logger::FileLogger.constants  # => [:DEFAULT_PATH, :MAX_SIZE]

const_set defines or reassigns constants dynamically. While Ruby constants are intended to remain unchanged after initial assignment, const_set allows runtime modification, though Ruby issues warnings when reassigning existing constants.

class DynamicConfig
  # Set constants at runtime
  const_set(:API_VERSION, "v2")
  const_set(:MAX_RETRIES, 3)
end

# Verify constants exist
DynamicConfig::API_VERSION  # => "v2"
DynamicConfig::MAX_RETRIES  # => 3

# Reassignment generates warnings but succeeds
DynamicConfig.const_set(:API_VERSION, "v3")  # warning: already initialized constant

Nested constant names can be resolved using string paths with const_get. Ruby interprets double colons in constant names as namespace separators, enabling deep constant lookup from any starting module.

module Application
  module Database
    module Adapters
      POSTGRESQL = "postgresql"
      MYSQL = "mysql2"
    end
  end
end

# Nested constant access
Application.const_get("Database::Adapters::POSTGRESQL")  # => "postgresql"

# Equivalent to direct access
Application::Database::Adapters::POSTGRESQL  # => "postgresql"

Advanced Usage

Advanced constant query patterns involve metaprogramming techniques that dynamically examine and manipulate constant structures. These patterns commonly appear in framework code, configuration systems, and domain-specific languages where constant definitions drive program behavior.

Constant introspection enables automatic discovery and processing of defined constants. This technique works particularly well for registry patterns where constants represent available options, plugins, or configuration values that should be processed uniformly.

module PaymentProcessor
  STRIPE = "stripe_processor"
  PAYPAL = "paypal_processor"  
  SQUARE = "square_processor"
  
  def self.available_processors
    constants.select do |const_name|
      const_name.to_s.end_with?('_PROCESSOR') || 
      const_get(const_name).to_s.end_with?('_processor')
    end.map do |const_name|
      {
        name: const_name.to_s.downcase,
        value: const_get(const_name),
        processor: const_name
      }
    end
  end
  
  def self.processor_for(name)
    processor_constant = name.to_s.upcase.to_sym
    if const_defined?(processor_constant)
      const_get(processor_constant)
    else
      raise ArgumentError, "Unknown processor: #{name}"
    end
  end
end

PaymentProcessor.available_processors
# => [{:name=>"stripe", :value=>"stripe_processor", :processor=>:STRIPE}, ...]

PaymentProcessor.processor_for(:stripe)  # => "stripe_processor"

Hierarchical constant queries combine inheritance with conditional logic to build sophisticated constant resolution systems. These systems can implement fallback mechanisms, environment-specific overrides, and contextual constant selection.

class Configuration
  DEFAULT_TIMEOUT = 30
  DEFAULT_RETRIES = 3
  DEFAULT_HOST = "localhost"
  
  class Development < Configuration
    HOST = "dev.internal"
    DEBUG = true
  end
  
  class Production < Configuration  
    TIMEOUT = 60
    RETRIES = 5
    HOST = "prod.example.com"
    SSL_ENABLED = true
  end
  
  def self.setting_for(key, environment_class = self)
    setting_constant = key.to_s.upcase.to_sym
    
    # Try environment-specific constant first
    if environment_class != self && 
       environment_class.const_defined?(setting_constant, false)
      return environment_class.const_get(setting_constant)
    end
    
    # Fall back to base configuration
    if const_defined?(setting_constant, false)
      const_get(setting_constant)
    else
      raise ArgumentError, "Unknown setting: #{key}"
    end
  end
  
  def self.all_settings_for(environment_class)
    base_settings = constants(false).each_with_object({}) do |const_name, hash|
      hash[const_name.to_s.downcase.to_sym] = const_get(const_name)
    end
    
    if environment_class != self
      environment_settings = environment_class.constants(false).each_with_object({}) do |const_name, hash|
        hash[const_name.to_s.downcase.to_sym] = environment_class.const_get(const_name)
      end
      base_settings.merge(environment_settings)
    else
      base_settings
    end
  end
end

# Environment-specific resolution
Configuration.setting_for(:host, Configuration::Development)  # => "dev.internal"
Configuration.setting_for(:timeout, Configuration::Development)  # => 30 (fallback)

# Comprehensive environment settings
Configuration.all_settings_for(Configuration::Production)
# => {:default_timeout=>30, :default_retries=>3, :default_host=>"localhost", 
#     :timeout=>60, :retries=>5, :host=>"prod.example.com", :ssl_enabled=>true}

Dynamic constant namespacing uses string manipulation and const_set to create organized constant hierarchies at runtime. This approach works well for plugin systems, auto-generated constants, and modular architectures where constant organization reflects runtime structure.

module FeatureFlags
  def self.define_feature(name, config = {})
    feature_module_name = name.to_s.split('_').map(&:capitalize).join
    
    if const_defined?(feature_module_name)
      feature_module = const_get(feature_module_name)
    else
      feature_module = Module.new
      const_set(feature_module_name, feature_module)
    end
    
    config.each do |key, value|
      constant_name = key.to_s.upcase
      feature_module.const_set(constant_name, value)
    end
    
    # Define query method
    define_singleton_method("#{name}_enabled?") do
      feature_module.const_get(:ENABLED) rescue false
    end
    
    feature_module
  end
  
  def self.feature_status(feature_name)
    feature_module_name = feature_name.to_s.split('_').map(&:capitalize).join
    
    if const_defined?(feature_module_name)
      feature_module = const_get(feature_module_name)
      feature_module.constants.each_with_object({}) do |const_name, status|
        status[const_name.to_s.downcase.to_sym] = feature_module.const_get(const_name)
      end
    else
      {}
    end
  end
end

# Define features with configuration
FeatureFlags.define_feature(:user_profiles, enabled: true, max_size: 1024)
FeatureFlags.define_feature(:advanced_search, enabled: false, timeout: 30)

# Access generated constants
FeatureFlags::UserProfiles::ENABLED  # => true
FeatureFlags::UserProfiles::MAX_SIZE  # => 1024

# Query feature status
FeatureFlags.user_profiles_enabled?  # => true
FeatureFlags.feature_status(:advanced_search)  # => {:enabled=>false, :timeout=>30}

Constant proxies implement lazy evaluation and computed constants using constant query methods combined with method_missing. This technique creates constants that behave like regular constants but compute their values dynamically.

module DatabaseConfig
  class << self
    def const_missing(const_name)
      case const_name
      when :DATABASE_URL
        "#{ADAPTER}://#{USERNAME}:#{PASSWORD}@#{HOST}:#{PORT}/#{DATABASE}"
      when :POOL_SIZE
        ENV['RAILS_MAX_THREADS'] || 5
      when :TIMEOUT
        production? ? 30 : 10
      else
        super
      end
    end
    
    private
    
    def production?
      ENV['RAILS_ENV'] == 'production'
    end
  end
  
  # Base configuration constants
  ADAPTER = ENV['DB_ADAPTER'] || 'postgresql'
  HOST = ENV['DB_HOST'] || 'localhost'
  PORT = ENV['DB_PORT'] || 5432
  DATABASE = ENV['DB_NAME'] || 'app_development'
  USERNAME = ENV['DB_USERNAME'] || 'app'
  PASSWORD = ENV['DB_PASSWORD'] || 'secret'
end

# Computed constants work like regular constants
ENV['RAILS_ENV'] = 'development'
DatabaseConfig::DATABASE_URL  # => "postgresql://app:secret@localhost:5432/app_development"
DatabaseConfig::TIMEOUT       # => 10

ENV['RAILS_ENV'] = 'production'  
DatabaseConfig::TIMEOUT       # => 30

Error Handling & Debugging

Constant query operations generate specific exception types that require targeted handling strategies. The primary exception type, NameError, occurs when accessing undefined constants, while ArgumentError exceptions arise from invalid constant names or namespace violations.

const_get raises NameError when the requested constant does not exist within the searched scope. This exception includes detailed information about the missing constant and the context where the lookup failed.

module ApiClient
  VERSION = "2.1.0"
end

begin
  ApiClient.const_get(:MISSING_CONSTANT)
rescue NameError => e
  puts "Constant lookup failed: #{e.message}"
  puts "Missing constant name: #{e.name}"
  puts "Receiver: #{e.receiver}"
  # Constant lookup failed: uninitialized constant ApiClient::MISSING_CONSTANT
  # Missing constant name: MISSING_CONSTANT  
  # Receiver: ApiClient
end

Safe constant access patterns use const_defined? to check existence before retrieval, preventing NameError exceptions in uncertain scenarios. This approach works well when constant presence varies based on configuration, environment, or conditional loading.

module ConfigLoader
  def self.safe_get_constant(module_name, const_name, default = nil)
    if Object.const_defined?(module_name)
      mod = Object.const_get(module_name)
      if mod.const_defined?(const_name)
        mod.const_get(const_name)
      else
        default
      end
    else
      default
    end
  end
  
  def self.require_constant(module_name, const_name)
    constant_value = safe_get_constant(module_name, const_name)
    if constant_value.nil?
      raise ArgumentError, 
        "Required constant #{module_name}::#{const_name} is not defined"
    end
    constant_value
  end
end

# Safe access with fallbacks
database_url = ConfigLoader.safe_get_constant('Rails', 'DATABASE_URL', 'sqlite3:memory:')

# Required constants with clear error messages  
begin
  api_key = ConfigLoader.require_constant('Secrets', 'API_KEY')
rescue ArgumentError => e
  puts "Configuration error: #{e.message}"
  exit 1
end

Constant validation ensures that retrieved constants meet expected criteria before use. This validation becomes critical when constants drive application behavior or when external code defines constants that must conform to specific contracts.

module PluginLoader
  ValidationError = Class.new(StandardError)
  
  def self.load_plugin_constant(plugin_module, constant_name, validator = nil)
    unless plugin_module.const_defined?(constant_name)
      raise ValidationError, 
        "Plugin #{plugin_module} missing required constant #{constant_name}"
    end
    
    constant_value = plugin_module.const_get(constant_name)
    
    if validator && !validator.call(constant_value)
      raise ValidationError,
        "Plugin constant #{constant_name} failed validation: #{constant_value.inspect}"
    end
    
    constant_value
  rescue NameError => e
    raise ValidationError, "Plugin constant access failed: #{e.message}"
  end
  
  def self.validate_plugin_interface(plugin_module)
    required_constants = {
      NAME: ->(v) { v.is_a?(String) && !v.empty? },
      VERSION: ->(v) { v.match?(/\A\d+\.\d+\.\d+\z/) },
      HANDLER: ->(v) { v.respond_to?(:call) }
    }
    
    errors = []
    required_constants.each do |const_name, validator|
      begin
        load_plugin_constant(plugin_module, const_name, validator)
      rescue ValidationError => e
        errors << e.message
      end
    end
    
    unless errors.empty?
      raise ValidationError, "Plugin validation failed:\n#{errors.join("\n")}"
    end
    
    true
  end
end

# Plugin validation example
module SamplePlugin
  NAME = "Sample Plugin"
  VERSION = "1.0.0"
  HANDLER = ->(data) { process_data(data) }
  
  def self.process_data(data)
    data.transform_values(&:upcase)
  end
end

PluginLoader.validate_plugin_interface(SamplePlugin)  # => true

Debugging constant resolution issues requires understanding Ruby's lookup algorithm and the scope hierarchy. Diagnostic methods can trace constant resolution paths and identify where lookups fail in complex inheritance hierarchies.

module ConstantDebugger
  def self.trace_constant_lookup(receiver, const_name)
    lookup_path = []
    
    # Check receiver module directly
    if receiver.const_defined?(const_name, false)
      lookup_path << { module: receiver, found: true, source: :direct }
      return lookup_path
    else
      lookup_path << { module: receiver, found: false, source: :direct }
    end
    
    # Check inheritance chain for classes
    if receiver.is_a?(Class)
      receiver.ancestors.each do |ancestor|
        next if ancestor == receiver
        
        if ancestor.const_defined?(const_name, false)
          lookup_path << { module: ancestor, found: true, source: :inheritance }
          return lookup_path
        else
          lookup_path << { module: ancestor, found: false, source: :inheritance }
        end
      end
    end
    
    # Check lexical nesting
    nesting = receiver.name ? receiver.name.split('::') : []
    while nesting.any?
      nesting.pop
      parent_name = nesting.join('::')
      
      begin
        parent = parent_name.empty? ? Object : Object.const_get(parent_name)
        if parent.const_defined?(const_name, false)
          lookup_path << { module: parent, found: true, source: :lexical }
          return lookup_path
        else
          lookup_path << { module: parent, found: false, source: :lexical }
        end
      rescue NameError
        lookup_path << { module: parent_name, found: false, source: :lexical_failed }
      end
    end
    
    lookup_path
  end
  
  def self.diagnose_constant_error(receiver, const_name)
    puts "Constant lookup diagnosis for #{receiver}::#{const_name}"
    puts "-" * 50
    
    trace = trace_constant_lookup(receiver, const_name)
    trace.each_with_index do |step, index|
      status = step[:found] ? "✓ FOUND" : "✗ not found"
      puts "#{index + 1}. #{step[:module]} (#{step[:source]}): #{status}"
    end
    
    if trace.none? { |step| step[:found] }
      puts "\nConstant #{const_name} not found in lookup path"
      puts "Available constants in #{receiver}:"
      puts receiver.constants.sort.join(', ')
    end
  end
end

# Usage example
class Parent
  SHARED_CONFIG = "parent_config"
end

class Child < Parent
  CHILD_CONFIG = "child_config"  
end

# Debug missing constant
ConstantDebugger.diagnose_constant_error(Child, :MISSING_CONSTANT)

Common Pitfalls

Constant query operations contain subtle behaviors that frequently cause confusion, particularly around inheritance semantics, scoping rules, and the distinction between lexical and dynamic constant access. Understanding these pitfalls prevents common debugging scenarios and unexpected runtime behavior.

The inheritance parameter in constant query methods creates a major source of confusion. By default, const_defined? and const_get search through the inheritance hierarchy, but the inherit parameter can restrict searches to the receiver module only. This distinction becomes critical when dealing with module hierarchies where parent and child modules define constants with the same name.

module BaseConfig
  TIMEOUT = 30
  RETRIES = 3
end

module DatabaseConfig
  include BaseConfig
  TIMEOUT = 60  # Override parent constant
  HOST = "localhost"
end

# Default behavior includes inheritance
DatabaseConfig.const_defined?(:TIMEOUT)        # => true (finds local)
DatabaseConfig.const_defined?(:RETRIES)        # => true (finds inherited)

# Restrict to local module only  
DatabaseConfig.const_defined?(:TIMEOUT, false) # => true (local override)
DatabaseConfig.const_defined?(:RETRIES, false) # => false (not local)

# This creates problems when checking for local definitions
unless DatabaseConfig.const_defined?(:RETRIES, false)
  DatabaseConfig.const_set(:RETRIES, 5)  # Sets local value
end

DatabaseConfig::RETRIES  # => 5 (local), BaseConfig::RETRIES still 3

Lexical versus dynamic constant resolution represents another common pitfall. Direct constant access follows lexical scoping rules, while const_get performs dynamic lookup that ignores lexical context. This difference causes issues when constants exist in multiple scopes.

module Outer
  SETTING = "outer"
  
  module Inner
    SETTING = "inner"
    
    def self.get_via_lexical
      SETTING  # Lexical lookup finds Inner::SETTING
    end
    
    def self.get_via_dynamic  
      const_get(:SETTING)  # Dynamic lookup finds Inner::SETTING
    end
    
    def self.get_outer_via_dynamic
      # This doesn't work as expected - finds Inner::SETTING, not Outer::SETTING
      const_get(:SETTING)  # Still finds local constant
    end
    
    def self.get_outer_correctly
      Outer.const_get(:SETTING)  # Explicit receiver required
    end
  end
end

Outer::Inner.get_via_lexical     # => "inner"
Outer::Inner.get_via_dynamic     # => "inner"  
Outer::Inner.get_outer_via_dynamic  # => "inner" (unexpected!)
Outer::Inner.get_outer_correctly    # => "outer"

Constant autoloading interactions with constant queries create timing-dependent bugs. Rails autoloading can cause constants to become defined between checks, leading to race conditions in constant query operations.

# Problematic pattern in Rails applications
module ServiceLoader
  def self.load_service(service_name)
    service_class_name = "#{service_name.to_s.camelize}Service"
    
    # This check might pass...
    unless Object.const_defined?(service_class_name)
      raise ArgumentError, "Service #{service_class_name} not found"
    end
    
    # But this might trigger autoloading and succeed
    Object.const_get(service_class_name)
  end
  
  # Better approach accounts for autoloading
  def self.load_service_safely(service_name)
    service_class_name = "#{service_name.to_s.camelize}Service"
    
    begin
      Object.const_get(service_class_name)
    rescue NameError
      raise ArgumentError, "Service #{service_class_name} not available"
    end
  end
end

# In Rails, these might behave differently:
# ServiceLoader.load_service(:user)        # Might raise then work
# ServiceLoader.load_service_safely(:user) # Handles autoloading correctly

String versus symbol constant names produce inconsistent behavior across Ruby versions and methods. While most constant query methods accept both strings and symbols, subtle differences exist in how they handle namespaced constant names and case sensitivity.

module TestModule
  CONSTANT_VALUE = "test"
  
  module NestedModule
    NESTED_VALUE = "nested"
  end
end

# Symbol vs string behavior
TestModule.const_defined?(:CONSTANT_VALUE)    # => true
TestModule.const_defined?("CONSTANT_VALUE")   # => true

# Nested constant access differences
TestModule.const_get("NestedModule::NESTED_VALUE")  # => "nested" (works)
TestModule.const_get(:"NestedModule::NESTED_VALUE") # => "nested" (works)

# Case sensitivity issues
TestModule.const_defined?(:constant_value)    # => false (wrong case)
TestModule.const_defined?("Constant_Value")   # => false (wrong case)

# Namespace separator confusion
TestModule.const_get("NestedModule/NESTED_VALUE")  # NameError (wrong separator)

Constant mutation through const_set creates unexpected behavior when working with frozen or immutable constants. Ruby allows constant reassignment but issues warnings, and the behavior differs between mutable and immutable constant values.

module ConfigModule
  # Immutable constant
  VERSION = "1.0.0".freeze
  
  # Mutable constant  
  FEATURES = ["authentication", "logging"]
  
  def self.update_version(new_version)
    # This generates warnings but works
    const_set(:VERSION, new_version)
  end
  
  def self.add_feature(feature)
    # This modifies the constant's content, no warnings
    FEATURES << feature
  end
end

original_version = ConfigModule::VERSION
ConfigModule.update_version("2.0.0")  # Warning: already initialized constant
ConfigModule::VERSION  # => "2.0.0" 

# But the original string might still exist elsewhere
original_version  # => "1.0.0"

# Mutable constant modification happens silently
ConfigModule.add_feature("caching")
ConfigModule::FEATURES  # => ["authentication", "logging", "caching"]

Thread safety issues arise when multiple threads simultaneously query and modify constants. While Ruby's constant table operations are generally thread-safe, the combination of query-then-set operations creates race conditions.

module ThreadSafeConfig
  @mutex = Mutex.new
  
  def self.ensure_constant(name, default_value)
    @mutex.synchronize do
      unless const_defined?(name, false)
        const_set(name, default_value)
      end
      const_get(name)
    end
  end
  
  # Unsafe version - race condition possible
  def self.unsafe_ensure_constant(name, default_value)
    unless const_defined?(name, false)  # Thread A checks
      # Thread B might set constant here
      const_set(name, default_value)    # Thread A sets, might override B's value
    end
    const_get(name)
  end
end

# Multiple threads calling unsafe version can cause conflicts
threads = 10.times.map do |i|
  Thread.new do
    ThreadSafeConfig.unsafe_ensure_constant(:COUNTER, i)
  end
end

threads.each(&:join)
# COUNTER value is unpredictable due to race conditions

Reference

Core Methods

Method Parameters Returns Description
const_defined?(name, inherit=true) name (String/Symbol), inherit (Boolean) Boolean Checks if constant exists in module scope
const_get(name, inherit=true) name (String/Symbol), inherit (Boolean) Object Retrieves constant value from module scope
constants(inherit=true) inherit (Boolean) Array of Symbols Lists all constants in module scope
const_set(name, value) name (String/Symbol), value (Object) Object Defines or reassigns constant in module
const_missing(name) name (Symbol) Object Called when constant lookup fails
remove_const(name) name (String/Symbol) Object Removes constant definition (private method)

Inheritance Parameters

Parameter Value Scope Behavior
true (default) Full hierarchy Searches receiver, ancestors, and included modules
false Local only Searches only the receiver module's constants

Exception Types

Exception Cause Common Scenarios
NameError Constant not found in lookup path Undefined constant access, typos in constant names
ArgumentError Invalid constant name format Empty strings, invalid characters, namespace violations
TypeError Invalid constant value type Setting constants to inappropriate object types

Constant Name Formats

Format Example Usage
Simple name :VERSION Single constant in current module
String name "VERSION" Alternative to symbol notation
Nested path "Database::HOST" Namespaced constant access
Absolute path "::Object" Root-level constant access

Lookup Resolution Order

  1. Receiver module - Constants defined directly in the target module
  2. Included modules - Constants from modules included via include
  3. Prepended modules - Constants from modules added via prepend
  4. Superclass chain - Constants from parent classes (classes only)
  5. Lexical scope - Constants from enclosing modules (lexical access only)
  6. Object constants - Top-level constants defined on Object

Method Availability

Context Available Methods Restrictions
All modules const_defined?, const_get, constants, const_set Public interface
Module owners remove_const Private method, requires explicit receiver
Subclasses All methods Inherited from Module
Eigenclass All methods Available on singleton classes

Thread Safety Characteristics

Operation Thread Safety Notes
const_defined? Safe Read-only operation
const_get Safe Read-only operation
constants Safe Read-only operation, returns snapshot
const_set Atomic Individual assignment is safe
remove_const Atomic Individual removal is safe
Check-then-set Unsafe Requires synchronization

Performance Characteristics

Operation Time Complexity Memory Impact
const_defined? O(depth) None
const_get O(depth) None
constants O(n) Creates new array
const_set O(1) Stores reference
Nested path lookup O(depth × segments) None

Common Patterns

# Safe constant access with fallback
value = Module.const_defined?(:CONST) ? Module.const_get(:CONST) : default

# Bulk constant processing  
Module.constants.each { |name| process(Module.const_get(name)) }

# Conditional constant definition
Module.const_set(:CONST, value) unless Module.const_defined?(:CONST)

# Inheritance-aware constant checking
if Module.const_defined?(:CONST, false)  # Local only
  # Handle local definition
elsif Module.const_defined?(:CONST)     # Check inheritance
  # Handle inherited definition  
end