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