Skip to main content
โšก Calmops

Constants and Namespaces in Ruby

Introduction

Constants .

Defining Constants

MAX_RETRIES = 3
PI          = 3.14159
APP_NAME    = "MyApp"

class Config
  DEFAULT_TIMEOUT = 30
  DEFAULT_RETRIES = 3
end

puts MAX_RETRIES          # => 3
puts Config::DEFAULT_TIMEOUT  # => 30

Constants defined at the top level belong to Object.

Accessing Top-Level Constants

Top-level constants are defined in Object. You can access them with the :: prefix:

MY_CONST = "top level"

from a class that includes A
end

# 3. Mutable constant
SETTINGS = { debug: false }
SETTINGS[:debug] = true  # works but shouldn't โ€” freeze it!
SETTINGS = { debug: false }.freeze

Resources


Modern Rails uses Zeitwerk for autoloading, which is more robust.

## Common Pitfalls Summary

```ruby
# 1. Shadowed built-in class
class MyApp
  class String  # shadows ::String inside MyApp
    def initialize(s)
      @value = ::String.new(s)  # must use :: to get built-in String
    end
  end
end

# 2. Constant defined in wrong scope
module A
  X = 1
end

module B
  include A
  # X is NOT accessible here via lexical lookup
  # You need A::X or to inherit puts "Refunding transaction #{transaction_id}"
    end
  end
end

gateway = Payments::Gateway.new
gateway.process(100, "USD")

puts Payments::Gateway::SUPPORTED_CURRENCIES.inspect
# => ["USD", "EUR", "GBP", "JPY"]

Autoloading Constants

Rails uses const_missing (similar to method_missing) to autoload files when a constant is first referenced:

module MyFramework
  def self.const_missing(name)
    file = name.to_s.underscore + ".rb"
    if File.exist?(file)
      require file
      const_get(NFIG = { timeout: 30, retries: 3 }.freeze
CONFIG[:timeout] = 60  # => FrozenError

Module as Namespace

Modules are the standard way to namespace constants and classes:

module Payments
  class Gateway
    SUPPORTED_CURRENCIES = %w[USD EUR GBP JPY].freeze

    def process(amount, currency)
      raise ArgumentError, "Unsupported currency" unless SUPPORTED_CURRENCIES.include?(currency)
      puts "Processing #{amount} #{currency}"
    end
  end

  class Refund
    def process(transaction_id)
        end
  end
end

This is a subtle but important distinction. The lexical scope at the point of class definition matters.

Freezing Constants

Ruby allows you to reassign constants (with a warning). To prevent this, freeze the value:

MAX_SIZE = 100
MAX_SIZE = 200  # warning: already initialized constant MAX_SIZE

# Freeze to prevent mutation of the object
ALLOWED_ROLES = ["admin", "user", "guest"].freeze
ALLOWED_ROLES << "superuser"  # => FrozenError: can't modify frozen Array

# For hashes
COe
      ROLE = "staff"
    end
  end
end

puts Company::HR::Employee::ROLE  # => "staff"

Reopening Classes and Constant Scope

When you reopen a class, the constant lookup context changes:

class Foo
  BAR = "foo's bar"
end

# Reopening with full path โ€” BAR is in Foo's scope
class Foo
  def show_bar
    BAR  # => "foo's bar"
  end
end

# Reopening with nesting โ€” BAR might not be in scope
module MyApp
  class Foo
    def show_bar
      BAR  # NameError! BAR is not in MyApp::Foo's lexical scope
  #{TIMEOUT}"
  end
end

puts Child.new.config
# => "retries=3, timeout=60"

The :: Operator

:: is the scope resolution operator. It navigates namespaces:

# Access a constant in a module
puts Math::PI       # => 3.141592653589793
puts Math::E        # => 2.718281828459045

# Access a nested class
puts Net::HTTP      # => Net::HTTP

# Access top-level constant from anywhere
puts ::String       # => String
puts ::Array        # => Array

# Chain namespaces
module Company
  module HR
    class Employedef self.where_am_i?
      LOCATION  # => "Inner" (lexical scope wins)
    end
  end

  def self.where_am_i?
    LOCATION  # => "Outer"
  end
end

puts Outer::Inner.where_am_i?  # => "Inner"
puts Outer.where_am_i?         # => "Outer"

Constant Lookup in Inheritance

module Defaults
  TIMEOUT = 60
end

class Base
  include Defaults
  RETRIES = 3
end

class Child < Base
  def config
    # Finds RETRIES in Base (inheritance), TIMEOUT in Defaults (via Base's ancestors)
    "retries=#{RETRIES}, timeout=def to_s
      @disks.join(", ")
    end
  end
end

puts Cluster::Array.new(3).to_s
# => "disk0, disk1, disk2"

How Ruby Resolves Constants

Ruby’s constant lookup follows this order:

  1. Lexical scope โ€” the enclosing class/module bodies at the point of reference
  2. Inheritance hierarchy โ€” superclasses and included modules
  3. Top-level (Object) โ€” constants defined at the top level
LOCATION = "top-level"

module Outer
  LOCATION = "Outer"

  module Inner
    LOCATION = "Inner"

    e:

```ruby
class Cluster
  class Array
    def initialize(n)
      # We want the built-in Array here, but Ruby finds Cluster::Array first!
      @disks = Array.new(n) { |i| "disk#{i}" }
      # => SystemStackError: infinite recursion (Array calls itself)
    end
  end
end

Fix: Use :: to explicitly reference the top-level constant:

class Cluster
  class Array
    def initialize(n)
      @disks = ::Array.new(n) { |i| "disk#{i}" }
      # => ["disk0", "disk1", ..., "disk(n-1)"]
    end

    CONST    # => "module level" (local lookup first)
    puts ::MY_CONST  # => "top level"    (explicit top-level)
    puts Object::MY_CONST  # => "top level" (equivalent)
  end
end

MyModule.show

The Namespace Collision Problem

A classic Ruby gotcha: when you define a class inside a namespace that shadows a top-level class name, Ruby resolves the constant to the inner onmodule MyModule MY_CONST = “module level”

def self.show puts MY_

Comments