Skip to main content

Constants and Namespaces in Ruby

Created: September 3, 2017 4 min read

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

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:
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

Share this article

Scan to read on mobile