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
- Ruby Docs: Constants
- Ruby Constant Lookup โ Simone Carletti
- Ruby Style Guide: Constants name) else super end end end
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:
- Lexical scope โ the enclosing class/module bodies at the point of reference
- Inheritance hierarchy โ superclasses and included modules
- 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