Ruby Language Behaviors
This document describes various Ruby language behaviors, compiled from observations in code analysis and testing.
Table of Contents
Namespace Qualification
Ruby uses :: as the namespace separator and supports various ways of referencing namespaced entities.
Name Qualification Context
The same unqualified name refers to different entities depending on context:
class Bar # Top-level Bar end module Foo class Bar # Foo::Bar end puts Bar # Inside Foo, "Bar" means "Foo::Bar" end puts Bar # Outside Foo, "Bar" means "Bar" (top-level)
Unqualified vs Qualified Names
Unqualified names are simple identifiers:
-
Foo -
Bar -
String
Qualified names include namespace separators:
-
Foo::Bar -
ActiveRecord::Base -
Rails::Application::Configuration
Top-level references start with :::
-
::Foo(explicitly top-level) -
::Array(explicitly top-level)
Rooted qualified names combine both (starting with :: and including namespace separators):
-
::ActiveRecord::Base(explicitly top-level ActiveRecord, then Base within it) -
::Rails::Application::Configuration(navigates from top level through the namespace chain)
Singleton Method Naming
Singleton methods (class methods) can use the same name as instance methods:
class Foo def bar; end # Instance method Foo#bar def self.bar; end # Singleton method Foo.bar (or Foo::bar) end
Both methods can coexist because they operate on different receivers:
-
Foo.new.barcalls the instance method -
Foo.barcalls the singleton method
Lexical Scoping and Constant Resolution
Fully Qualified Name Resolution Rules
Ruby resolves names according to the current scope:
class Bar; end class Qux; end module Foo class Bar; end # Defines Foo::Bar # "Bar" => "Foo::Bar" # "::Bar" => "Bar" (top-level) class Baz class Qux; end # Defines Foo::Baz::Qux # "Bar" => "Foo::Bar" # "Qux" => "Foo::Baz::Qux" # "::Qux" => "Qux" (top-level) end end
Rules:
-
If the name starts with
::, it refers to a top-level constant -
Otherwise, the name is qualified relative to the current namespace
-
At the top level, unqualified names remain unqualified
Top-Level References with ::
Ruby’s top-level constant resolution operator :: resets the namespace to the root, but lexical nesting is still preserved for constant lookup. This is a critical and subtle behavior.
Example:
module Foo CONST = 42 class ::Bar CONST # resolves to Foo::CONST because we're inside the `Foo` namespace end end class Bar CONST # NameError because this `Bar` is not lexically inside `Foo` end
Key Points:
-
The class
::Bardefined insideFoohas the fully qualified nameBar(notFoo::Bar) -
However, it is still lexically connected to
Foofor constant resolution -
The second
Bardefinition (outsideFoo) has no lexical connection toFoo, therefore needs to accessCONSTviaFoo::CONST
Compact Namespace Notation
Ruby supports compact namespace notation like class Foo::Bar::Baz, which creates multiple nesting levels in one declaration.
Example:
class Foo::Bar class Baz::Qux; end class ::Quuux; end end
This creates:
-
Foo::Bar -
Foo::Bar::Baz::Qux(nested underFoo::Bar) -
Quuux(top-level, but lexically insideFoo::Bar)
Important: Foo::Bar::Baz::Qux::Quuux does not exist because ::Quuux resets to top level.
Definitions vs Declarations
A fundamental concept in Ruby is that classes and modules can be reopened and defined multiple times.
Example:
module Foo class Bar; end end class Foo::Bar; end
This code creates:
3 separate definitions:
-
Module definition for
Foo -
Class definition for
Foo::Bar(insideFoo) -
Class definition for
Foo::Bar(at top level)
2 distinct declarations:
-
The module
Foo -
The class
Foo::Bar
Key Points:
-
A definition is a single definition occurrence of a class, module, method, etc. in a specific file at a specific location
-
A declaration represents the named entity that may have multiple definitions
Method Parameters
Ruby has a rich and expressive parameter system with multiple parameter types.
Parameter Types
def method_name( a, # Required positional parameter b = 42, # Optional positional parameter (with default) *c, # Rest positional parameter (splat) d, # Post parameter (required positional after rest) e:, # Required keyword parameter f: 42, # Optional keyword parameter (with default) **g, # Rest keyword parameter (double splat) &h # Block parameter ) end
Complete Example:
def foo(a, b = 42, *c, d, e:, g: 42, **i, &j); end
This demonstrates:
-
a: required positional parameter -
b = 42: optional positional parameter with default value -
*c: rest positional (captures remaining positional args as an array) -
d: post parameter (required positional after rest) -
e:: required keyword parameter -
g: 42: optional keyword parameter with default value -
**i: rest keyword (captures remaining keyword args as a hash) -
&j: block parameter
Forward Parameters
def foo(...) # Forwards all arguments to bar bar(...) end
This captures and forwards all positional, keyword, and block arguments.
Singleton Methods
Methods can be defined on individual objects or as class methods:
class Foo def bar; end # Instance method def self.baz; end # Singleton method (class method) end
Results in:
-
Foo#bar- instance method -
Foo.bazorFoo::baz- singleton method
Method Aliasing
Ruby provides two ways to create method aliases: the alias keyword and the alias_method method.
The alias Keyword
The alias keyword creates a new name for an existing method:
class Foo def bar "bar" end alias baz bar # baz is now an alias for bar end Foo.new.baz # => "bar"
Aliases can use symbols or bare method names:
class Foo def original; end alias :new_name :original # symbol syntax alias another_name original # bare name syntax end
alias_method
alias_method is a method from Module that creates aliases at runtime:
class Foo def bar; end alias_method :baz, :bar # symbol syntax alias_method "qux", "bar" # string syntax end
Unlike alias, alias_method requires symbols or strings, not bare method names.
Top-Level Aliases
Aliases can be defined at the top level but it only works with the alias keyword:
def foo; end alias bar foo # Creates top-level method alias alias_method :bar, :foo # NoMethodError
Global Variable Aliases
The alias keyword can also create aliases for global variables:
$foo = 123 alias $bar $foo # $bar is now an alias for $foo $bar # => 123 $bar = 456 $foo # => 456 (they share the same value)
This is different from simply assigning $bar = $foo, which would copy the value rather than create an alias.
Attribute Methods
Ruby provides special methods for creating getter and setter methods automatically.
Important: Attribute methods (attr, attr_accessor, attr_reader, attr_writer) can only be used inside class or module definitions. They are not allowed at the top level.
Basic Attribute Methods
class Foo attr_accessor :foo # Creates both `foo` and `foo=` methods attr_reader :bar # Creates only `bar` method (getter) attr_writer :baz # Creates only `baz=` method (setter) end # NOT allowed: # attr_accessor :top_level # Error: undefined method `attr_accessor'
Multiple Attributes
All attribute methods accept multiple symbols:
class Foo attr_accessor :bar, :baz end
Creates: bar, bar=, baz, baz=
Receiver Context
Attribute methods only work when called on self (implicit or explicit):
class Foo attr_accessor :foo # Works (implicit self) self.attr_accessor :qux # Works (explicit self) foo.attr_accessor :ignored # Does NOT work (different receiver) end
The attr Method with Parameters
The attr method has special behavior based on its arguments.
Rules:
-
No second parameter: Creates only reader
ruby class Foo attr :foo # Creates only foo (getter) end
-
Second parameter is
false: Creates only reader
ruby class Foo attr :foo, false # Creates only foo (getter) end
-
Second parameter is
true: Creates both reader and writer
ruby class Foo attr :foo, true # Creates both foo and foo= (getter and setter) end
-
Multiple symbols/strings without second parameter: Creates only readers for all arguments
“‘ruby class Foo attr :foo, :bar, :baz # Creates only: foo, bar, baz (readers only)
attr :a, "b", "c" # Creates only: a, b, c (readers only)
end “‘
-
Invalid second parameter: Raises runtime error
ruby class Foo attr :foo, 123 # Raises `123 is not a symbol nor a string (TypeError)` end
Variable Scoping
Ruby has three types of variables distinguished by sigils: global ($), instance (@), and class (@@).
Global Variables
Global variables are not namespaced and exist globally regardless of where they’re defined:
$foo = 1 class Foo $bar = 2 end
Both $foo and $bar are accessible globally. The class definition doesn’t affect the scope of $bar.
Instance Variables
Instance variables are scoped to instances of classes. When defined at the class level, they belong to the class itself (as the class is an object):
@foo = 1 # Top-level instance variable (belongs to <main>) class Foo @bar = 2 # Class instance variable (belongs to Foo object) def initialize @baz = 3 # Instance variable (belongs to instances of Foo) end end
Class Variables
Class variables are shared across a class and all its instances:
class Foo @@bar = 2 # Class variable for Foo def self.bar @@bar end def bar @@bar # Same @@bar accessible in instance methods end end # NOT allowed: # @@foo = 1 # Error: class variable access from toplevel
Multi-Assignment (Destructuring)
All variable types support multi-assignment:
# Constants FOO, BAR::BAZ = 1, 2 # Global variables $foo, $bar, $baz = 1, 2, 3 # Instance variables @foo, @bar = 1, 2 # Class variables @@foo, @@bar = 1, 2 # Mixed with top-level references FOO, BAR::BAZ, ::BAZ = 3, 4, 5
Instance Variables vs Class Variables: Receiver vs Lexical Scoping
A critical difference between instance variables (@) and class variables (@@) is how they determine ownership when defined inside a method with an explicit receiver:
-
Instance variables follow the receiver (
selfat runtime) -
Class variables follow lexical scope (where the code is written)
class Foo; end class Bar # This defines a singleton method on Foo, but lexically inside Bar def Foo.demo @ivar = "instance var" # Belongs to <Foo> (the receiver) @@cvar = "class var" # Belongs to Bar (lexical scope) end end Foo.demo Foo.instance_variables # => [:@ivar] Bar.instance_variables # => [] Foo.class_variables # => [] Bar.class_variables # => [:@@cvar]
This behavior has important implications for static analysis:
| Variable Type | Scoping Rule | In def Foo.demo inside class Bar |
|---|---|---|
@ivar |
Receiver | Belongs to Foo |
@@cvar |
Lexical | Belongs to Bar |
The same applies to class << Foo blocks defined inside another class:
class Foo; end class Bar class << Foo def another_demo @ivar2 = 1 # Belongs to Foo (receiver is Foo's singleton) @@cvar2 = 2 # Belongs to Bar (lexical scope) end end end Foo.another_demo Foo.instance_variables # => [:@ivar2] Bar.class_variables # => [:@@cvar2]
Mixins
Ruby provides three ways to mix modules into classes or other modules: include, prepend, and extend.
Include
include inserts the module into the ancestor chain after the class, making the module’s constants, methods, and module variables available:
module Foo; end class Bar include Foo end Bar.ancestors # => [Bar, Foo, Object, Kernel, BasicObject]
Prepend
prepend inserts the module into the ancestor chain before the class:
module Foo; end class Bar prepend Foo end Bar.ancestors # => [Foo, Bar, Object, Kernel, BasicObject]
Extend
extend adds module methods as singleton methods (class methods when used in a class):
module Foo def bar; end end class Baz extend Foo end Baz.bar # Works (class method) Baz.new.bar # NoMethodError (not an instance method)
Extend Self Pattern
A common pattern is extend self to make module methods callable on the module itself:
module Foo extend self def bar "bar" end end Foo.bar # => "bar"
Extend vs Include in Singleton Class
extend Foo is functionally equivalent to class << self; include Foo; end - both add methods to the singleton class. However, they trigger different hooks:
module Foo def self.included(base) puts "included hook called on #{base}" end def self.extended(base) puts "extended hook called on #{base}" end end class UsingExtend extend Foo # Triggers: "extended hook called on UsingExtend" end class UsingSingletonInclude class << self include Foo # Triggers: "included hook called on #<Class:UsingSingletonInclude>" end end
Both classes get the same methods, but:
-
extendcalls theextendedhook -
includein singleton class calls theincludedhook
Multiple Mixins
Multiple modules can be mixed in a single call:
class Foo include Bar, Baz # Baz is included first, then Bar prepend Qux, Quux # Quux is prepended first, then Qux end
Mixin Deduplication
Ruby removes duplicate modules from the ancestor chain:
module A; end module B include A end class Foo include A include B end Foo.ancestors # => [Foo, B, A, Object, ...] # A appears only once, not twice
Indirect duplicates are also removed:
module A; end module B include A end module C include A end module Foo include B include C end Foo.ancestors # => [Foo, C, B, A] # A appears only once despite being in both B and C
When both parent and child include the same module, Ruby deduplicates it - the module appears once in the ancestor chain:
module A; end module B include A end class Parent include B end class Child < Parent include B end Child.ancestors # => [Child, Parent, B, A, Object, ...] # B and A appear once (child's include is deduplicated against parent)
Top-Level Mixins
Mixins at the top level affect Object:
include Foo # Makes Foo's methods available everywhere
Note: include self, prepend self, or extend self at the top level is invalid and will produce an error:
include self # => TypeError: wrong argument type Object (expected Module) prepend self # => NoMethodError: undefined method 'prepend' for main extend self # => TypeError: wrong argument type Object (expected Module)
Cyclic Mixins
include self and prepend self inside a module are rejected as cyclic:
module Foo include self # => ArgumentError: cyclic include detected end module Bar prepend self # => ArgumentError: cyclic prepend detected end
extend self inside a module is valid (see Extend Self Pattern above).
Visibility
Ruby provides three visibility levels for methods: public, private, and protected.
Default Visibility
Methods defined at the top level default to private:
def foo; end # private by default at top level public def bar; end # now public (after public modifier)
Methods defined inside a class or module default to public:
class Foo def bar; end # public by default end
Visibility Modifiers
Visibility can be changed using public, private, or protected inside class and module bodies. At the top level, only public and private are available; calling protected on main raises NameError.
class Foo def m1; end # public (default) private def m2; end # private (after modifier) protected def m3; end # protected (inline modifier) end
Access Rules
-
public: No restrictions — callable from anywhere with any receiver. -
private: A private method defined anywhere in the ancestor chain can be called onself— either implicitly or explicitly asself.method. Calling it on any other object, even an instance of the same class, raisesNoMethodError. -
protected: Likeprivate, but also allows calls on other objects as long as bothselfand the receiver have the defining class or module in their ancestor chain.
The key distinction between private and protected is cross-instance access:
class Account def initialize(balance) @balance = balance end def >(other) balance > other.balance # works — other is also an Account end protected def balance; @balance; end end a = Account.new(100) b = Account.new(50) a > b # => true (cross-instance protected call succeeds)
With private, the same pattern fails because other.balance uses an explicit receiver that isn’t self:
class Wallet def initialize(amount) @amount = amount end def >(other) amount > other.amount # NoMethodError — private method called on other end private def amount; @amount; end end
Protected access extends to subclasses:
class SavingsAccount < Account def compare(other) balance > other.balance # works — both are in the Account hierarchy end end
An unrelated class cannot call protected methods, even with a reference to the object:
class Auditor def inspect_balance(account) account.balance # NoMethodError — Auditor is not in Account's hierarchy end end
Protected access also works between unrelated classes that share a module in their ancestors:
module HasBalance protected def balance; @balance; end end class Account include HasBalance def initialize(balance) = @balance = balance def >(other) = balance > other.balance end class Wallet include HasBalance def initialize(balance) = @balance = balance def >(other) = balance > other.balance end Account.new(100) > Account.new(50) # => true (same class) Wallet.new(100) > Account.new(50) # => true (unrelated classes, but both include HasBalance)
Account and Wallet share no inheritance relationship, but both include HasBalance. Since the defining module is in both ancestor chains, protected cross-instance calls work between them.
Visibility Resets in Nested Scopes
Important: Visibility modifiers do NOT propagate into nested class or module definitions:
private class Foo def m1; end # public (resets in new scope) private module Bar def m2; end # public (resets again) end def m3; end # private (still in Foo's private scope) end
Visibility Does NOT Apply to def self.method
Visibility modifiers have no effect on singleton methods defined with def self.method:
class Foo private def self.bar; end # STILL PUBLIC (visibility ignored) # private_class_method :bar can turn `bar` private end Foo.bar # Works fine
Important: This does NOT apply inside class << self blocks - visibility works normally there:
class Foo class << self private def bar; end # Actually private end end Foo.bar # private method 'bar' called for class Foo (NoMethodError)
Retroactive Visibility (private :method_name)
Visibility can be changed retroactively by passing a method name as a symbol to private, protected, or public:
class Foo def foo; end def bar; end private :foo # retroactively makes foo private end Foo.public_instance_methods(false) # => [:bar] Foo.private_instance_methods(false) # => [:foo]
-
Accepts multiple symbols:
private :a, :b -
Returns the symbol(s):
private :fooreturns:foo,private :a, :breturns[:a, :b] -
protected :fooandpublic :foowork the same way
private is a private method on Module:
Foo.private(:foo) # => NoMethodError: private method 'private' called for class Foo Foo.send(:private, :foo) # works (bypasses visibility check)
Unlike private_constant and private_class_method which are public methods, private/protected/public cannot be called with an explicit receiver from outside.
Works across reopened classes:
class Foo; def foo; end; end class Foo; private :foo; end # works — retroactive across reopen Foo.private_instance_methods(false) # => [:foo]
private :inherited_method creates an implicit copy:
class Parent def inherited_method; "parent"; end end class Child < Parent private :inherited_method end Parent.new.respond_to?(:inherited_method) # => true (parent unaffected) Child.new.respond_to?(:inherited_method) # => false (private on child) Child.instance_method(:inherited_method).owner # => Child (NOT Parent!)
Ruby creates an implicit method entry on the child class. The owner is Child, not Parent. This means private :inherited_method effectively creates a definition, not just modifying one. The parent is unaffected.
private_class_method / public_class_method
private_class_method makes singleton methods (class methods) private:
class Foo def self.hidden; end private_class_method :hidden end Foo.hidden # => NoMethodError: private method 'hidden' called for class Foo
Inline form:
class Foo private_class_method def self.secret; "secret"; end end Foo.secret # => NoMethodError
Common pattern — private_class_method :new:
class Singleton private_class_method :new def self.create; new; end # internal access works end Singleton.new # => NoMethodError Singleton.create # => #<Singleton:0x...>
public_class_method reverses private_class_method. Both are public methods on Module, callable from anywhere with a receiver.
Note: private_singleton_method does not exist in Ruby. The correct API is private_class_method.
Retroactive module_function :method_name
module_function can be called with a method name to retroactively apply the module_function behavior, but only inside module bodies (or via send on a module). It is not available at the top level or inside class bodies:
module Foo def foo; "foo"; end module_function :foo end Foo.foo # => "foo" (public singleton) Foo.public_instance_methods(false) # => [] Foo.private_instance_methods(false) # => [:foo] (instance becomes private) Foo.singleton_methods(false) # => [:foo]
Creates a copy, not a reference:
module Foo def foo; "v1"; end module_function :foo def foo; "v2"; end # redefines instance method end Foo.foo # => "v1" (singleton is an independent copy of v1)
Constant Visibility
Ruby provides private_constant and public_constant to control constant visibility.
private_constant
class Foo CONST = 1 private_constant :CONST def use_const; CONST; end # works — internal access OK end Foo::CONST # => NameError: private constant Foo::CONST referenced Foo.constants # => [] (private constants hidden from .constants)
-
Accepts symbols (
:CONST) and strings ("CONST") -
Accepts multiple args:
private_constant :A, :B -
Works on classes AND modules, including nested classes:
private_constant :Inner -
Foo.constantsexcludes private constants -
Foo.const_defined?(:PRIV)returns true even for private constants — visibility doesn’t affectconst_defined? -
Foo.const_get(:PRIV)bypasses private constant visibility and returns the value — only the::operator enforcesprivate_constant
public_constant
Reverses private_constant:
class Foo CONST = 1 private_constant :CONST public_constant :CONST end Foo::CONST # => 1 (accessible again)
Scoping: Targets the Receiver
private_constant is a public method on Module — callable from anywhere with a receiver. It targets the receiver, not the lexical scope:
module Bar CONST = 1 class Foo CONST = 2 private_constant(:CONST) # targets Foo::CONST (self is Foo) Bar.private_constant(:CONST) # targets Bar::CONST (explicit receiver) end end Bar::CONST # => NameError (blocked by explicit receiver call) Bar::Foo::CONST # => NameError (blocked by bare call inside Foo)
NOT Available at Top Level
private_constant :FOO # => NoMethodError: undefined method 'private_constant' for main public_constant :FOO # => NoMethodError: undefined method 'public_constant' for main
self at top level is main (an Object instance), not a Module, so neither API is available there.
Constant Visibility and Inheritance
Private constants block external access but NOT internal or inherited access:
class Parent CONST = 1 private_constant :CONST def use_const; CONST; end end class Child < Parent def try_const; CONST; end end Parent.new.use_const # => 1 (internal access works) Child.new.try_const # => 1 (inherited access through method works) Child::CONST # => NameError (direct external access blocked)
Private constants from included modules are also blocked externally:
module Mod CONST = 1 private_constant :CONST end class Foo; include Mod; end Foo::CONST # => NameError
Method Visibility Modifiers Do Not Affect Constants
The private/protected visibility modifiers have no effect on constants. Only private_constant controls constant visibility:
class Foo private CONST = 42 end Foo::CONST # => 42 (still public) Foo.constants # => [:CONST]
Alias Visibility
Both alias and alias_method copy the source method’s visibility at the time of aliasing, regardless of the current visibility stack:
class Foo def pub; end # public private alias copy pub # copy is PUBLIC (copies source, ignores stack) end Foo.public_instance_methods(false) # => [:copy, :pub] Foo.private_instance_methods(false) # => []
class Foo def foo; end private alias_method :bar, :foo # bar is PUBLIC (same behavior as alias) end Foo.public_instance_methods(false) # => [:bar, :foo] Foo.private_instance_methods(false) # => []
Aliases don’t track later visibility changes:
class Foo def foo; end alias bar foo # bar is public (same as foo at this point) private :foo # foo becomes private end Foo.public_instance_methods(false) # => [:bar] Foo.private_instance_methods(false) # => [:foo]
Constant References
Constants in Ruby can be referenced before they’re defined, and resolution depends on lexical scope.
Unresolved References
A constant reference may not immediately resolve to a definition:
module Foo BAR # Could be Foo::BAR or top-level BAR - depends on what exists end
Resolution depends on:
-
What constants are defined
-
The lexical scope chain
-
Whether it’s a top-level reference (
::BAR)
Top-Level Constant References
Constants prefixed with :: are resolved by searching the ancestors of Object. This means they can find constants defined in Kernel or other modules included into Object, not just constants owned by Object directly:
module Kernel FOO = 1 end module Foo ::FOO # Found through Object's ancestors (Kernel) ::Bar # Always defined through the ancestors of `Object` Baz # Could be defined in `Foo` or in the ancestors of `Object` end
This works even inside classes that don’t inherit from Object:
module Kernel FOO = 1 end class Bar < BasicObject ::FOO # Resolves through Object's ancestors, not through Bar's end
Resolution Examples
module Foo class Bar ::Bar # Resolves to top-level Bar (if it exists) ::Baz # Resolves to top-level Baz (if it exists) String # Searches: Foo::Bar::String, Foo::String, ::String ::Object # Resolves to top-level Object end end class Bar ::Foo::Bar # Explicit path to Foo::Bar end
Lexical scope resolution for unqualified constants (like String above):
-
Current namespace (Foo::Bar::String)
-
Each enclosing lexical scope (Foo::String)
After lexical scopes are exhausted, Ruby searches the ancestor chain. See Constant Resolution Through Inheritance for the full algorithm.
Inheritance Chain Resolution
Ruby builds ancestors through the following steps recursively:
-
Prepended modules (in reverse order of prepending)
-
Current class
-
Included modules (in reverse order of inclusion)
-
Parent class
And then it continues up the inheritance chain.
module PrependedInPrepended end module Prepended prepend PrependedInPrepended end module PrependedInIncluded prepend PrependedInPrepended end module IncludedInIncluded end module Included include IncludedInIncluded end class Foo end class Bar < Foo prepend Prepended include Included end puts Bar.ancestors.to_s #=> [PrependedInPrepended, Prepended, Bar, Included, IncludedInIncluded, Foo, Object, Kernel, BasicObject]
Constant Resolution Through Inheritance
Ruby also searches the ancestor chain when resolving constants:
class Parent CONST = "from parent" end class Child < Parent def show CONST # Resolves to Parent::CONST through inheritance end end Child.new.show # => "from parent"
Resolution order:
-
Lexical scopes (current namespace, then each enclosing scope)
-
Ancestor chain of the innermost enclosing class or module
-
For modules only: ancestors of
Object
There is no separate “top-level fallback” for classes. Top-level constants like String are owned by Object, which appears in the ancestor chain of most classes. This can be confirmed because a class inheriting from BasicObject (which does not have Object in its ancestors) cannot resolve top-level constants:
class Foo < BasicObject String # NameError: Foo doesn't have Object in its ancestor chain end
When using constants at the top level of a script, they resolve because <main> is an instance of Object:
String # Works because <main> is Object. Resolved through inheritance
The module fallback searches the full ancestor chain of Object, not just Object directly:
module Kernel FOO = 1 end module Bar FOO # Resolves through Object's ancestors (Kernel) end
Example with modules:
module M CONST = "from module" end class Parent CONST = "from parent" end class Child < Parent include M def show CONST # Resolves to M::CONST (included module takes precedence) end end
Constant Paths
Ruby supports qualified constant paths:
module Foo class Bar Object::String # Explicit path: Object must exist, then String within it end end
Constant paths are resolved left-to-right: Object is resolved first, then String is looked up within Object.
Constant Aliases
Constants can be assigned to reference other constants, creating aliases:
module Foo CONST = 123 end ALIAS = Foo ALIAS::CONST # Resolves to Foo::CONST (123)
Chained Aliases
Aliases can reference other aliases, forming chains:
module Foo CONST = 123 end ALIAS1 = Foo ALIAS2 = ALIAS1 ALIAS2::CONST # Resolves through ALIAS2 -> ALIAS1 -> Foo -> Foo::CONST
Scoped Aliases
Aliases can be defined within namespaces:
module Foo CONST = 1 end module Bar MyFoo = Foo # Bar::MyFoo is an alias for Foo end Bar::MyFoo::CONST # Resolves to Foo::CONST
Aliases can also be assigned using qualified paths:
module Foo; end module Bar; end Bar::ALIAS = Foo # Creates Bar::ALIAS pointing to Foo
Conditional Aliases
The ||= operator can create conditional aliases:
ALIAS ||= Foo # Only assigns if ALIAS is not already defined
Defining Classes Or Modules Under Aliased Path
Ruby allows defining classes or modules under aliases. The defined class/module will have its fully qualified name based on the alias target, not the alias itself. For example:
class Foo; end ALIAS = Foo class ALIAS::Bar; end ALIAS::Bar.name # Foo::Bar, not ALIAS::Bar
Singleton Classes
Ruby can create a singleton class for most objects, but it usually materializes that class lazily the first time singleton-specific syntax/methods are used.
-
Defining singleton behavior (
def self.*,class << Foo,extend, etc.) forces Ruby to create the singleton class if it was not created already. -
Calling
Foo.singleton_classalso forces creation and returns the singleton class object. -
Plain objects behave the same way:
obj.singleton_classordef obj.specialallocates one lazily for that object. -
Classes and modules that never reference their singleton class may run indefinitely without allocating it.
-
Certain objects can never have singleton classes: integers, floats, symbols, and frozen strings. Calling
singleton_classon them raisesTypeError. -
Ruby boots with singleton classes for
true,false, andnilalready created.
Calling Foo.new does not force Foo’s singleton class to exist. The new method is defined on Class itself, so every class object inherits it automatically. Unless you override Foo.new via def self.new (which would then create the singleton class) Ruby can instantiate Foo instances without ever materializing Foo‘s singleton class.
Class.instance_method(:new).owner # => Class
The singleton class is the receiver for def self.*, class << self, and it is where modules mixed in via extend actually insert their methods.
class Foo class << self # reopens (or creates) Foo's singleton class A = 1 def bar; A; end end def self.baz; end # adds the method to Foo's singleton class @ivar = 1 # assigns a class instance variable on Foo's singleton class (NOT a class variable) end
Lexical Scope vs Singleton Receiver
-
Inside the class body the current receiver (
self) isFoo, so@ivarwrites to the singleton class anddef self.bazdefines a singleton method. -
Lexical constant lookup still follows the surrounding constant scope, so constants created inside
class << selfdo not becomeFoo::CONST.
class Foo @counter = 0 class << self A = 1 def bar @counter += 1 # valid: singleton ivar puts A # valid: constant defined in this singleton scope end end def self.baz puts @counter # valid: reads the singleton's @counter puts A # NameError: Foo::A is not defined end end
If a constant must be visible as Foo::CONST, define it in the regular class body instead of inside a class << self block.
Reopening Singleton Class Explicitly
The singleton class can be reopened anywhere with class << Foo as long as the constant already exists:
# file: foo.rb class Foo; end # file: foo_singleton.rb class << Foo def bar! puts "bar" end end # file: my_class.rb class MyClass # This also reopens Foo's singleton class class << Foo def baz! puts "baz" end end end Foo.bar! Foo.baz!
Class Variables and Singleton Classes
Class variables (@@var) bypass singleton classes entirely. They always belong to the base class or module, regardless of where they are defined:
class Foo @@in_class_body = 1 class << self @@in_singleton_scope = 2 def set_var @@in_method = 3 end end end Foo.set_var # All class variables belong to Foo, not its singleton class Foo.class_variables # => [:@@in_class_body, :@@in_singleton_scope, :@@in_method] Foo.singleton_class.class_variables # => [:@@in_class_body, :@@in_singleton_scope, :@@in_method] # inherited view # The singleton class itself owns no class variables Foo.singleton_class.class_variables(false) # => []
This is fundamentally different from instance variables (@var), which do belong to the singleton class when defined in class scope:
class Foo @class_ivar = 1 # belongs to Foo's singleton class @@class_var = 2 # belongs to Foo itself end
Top-level class variable access (@@foo = 1 outside any class) raises RuntimeError: class variable access from toplevel.
Nested Singleton Classes
class << self can be nested. Each nesting opens the singleton class of the current self:
class Foo class << self # self is now Foo.singleton_class def on_foo_singleton; end class << self # self is now Foo.singleton_class.singleton_class def on_singleton_singleton; end end end end # on_foo_singleton is callable on Foo Foo.on_foo_singleton # on_singleton_singleton is NOT callable on Foo Foo.on_singleton_singleton # => NoMethodError # It's callable on Foo's singleton class Foo.singleton_class.on_singleton_singleton
The ownership chain is: Foo → Foo.singleton_class → Foo.singleton_class.singleton_class.
Anonymous Classes And Modules
Class.new and Module.new create anonymous class/module objects. When assigned to a constant, that constant names the new namespace.
Method receivers and self
Inside the block, self is the newly created class/module.
Foo = Class.new do def self.foo; end # class method on Foo def bar; end # instance method on Foo instances alias_method :baz, :bar end
You can still access those methods even when the anonymous class isn’t assigned to a constant:
c = Class.new do def self.foo; end def bar; end end c.foo c.new.bar
Instance variables
Instance variables behave the same as in regular class definitions:
Foo = Class.new do @class_ivar = 1 # instance variable of the Foo class def initialize @ivar = 2 # instance variable of Foo instances end end
Class variables and constants
Class variables and constants follow lexical scope (Module.nesting). Class.new/Module.new blocks do not change lexical scope, so unqualified constants and class variables are defined in the outer scope, not on the new class/module.
class Outer Foo = Class.new do @@cvar = 1 # this belongs to Outer, not Foo end end
Defining class variables in a top-level anonymous class/module results in an error:
Foo = Class.new do @@cvar = 1 # RuntimeError: class variable access from toplevel end
Similar to class variables, constants are attached to the closest lexical scope, not the anonymous class/module:
Foo = Class.new do CONST = 2 end defined?(Foo::CONST) # => nil defined?(::CONST) # => "constant"
If you want constants under the new class/module, use self::CONST or define them after the assignment.
Foo = Class.new do self::CONST = 2 end Foo::CONST
Namespaces and lexical nesting
The block does not introduce lexical nesting for class/module definitions:
class Foo Bar = Class.new do class Baz; end end end # Baz is Foo::Baz, not Foo::Bar::Baz
You cannot reliably reference the assigned constant inside the block because the assignment happens after the block runs. Bar::Baz inside the block refers to an outer Bar (if any) or raises NameError. Using self::Bar as a superclass also fails because self is the anonymous class, not the outer scope:
class Foo Bar = Class.new do class Baz < Bar; end # NameError: uninitialized constant Foo::Bar class Baz < self::Bar; end # NameError: uninitialized constant #<Class:...>::Bar end end
To define classes or constants under the new class, use class self::Baz or self::Baz = Class.new (or const_set) inside the block, or define them after the assignment:
class Foo Bar = Class.new do class self::Baz; end # defines Foo::Bar::Baz self::Qux = Class.new # defines Foo::Bar::Qux end end # or class Foo Bar = Class.new Bar::Baz = Class.new end
Nested anonymous Class.new/Module.new
The rules above apply to nested anonymous class/module definitions as well.
For example, constants defined in nested anonymous classes are still attached to the closest lexical scope:
Foo = Class.new do Class.new do CONST = 1 end end # CONST is defined on top-level, the closest lexical scope, instead of Foo defined?(CONST) #=> "constant" defined?(Foo::CONST) #=> nil
Singleton class blocks inside anonymous classes
Although Class.new/Module.new blocks do not create lexical scopes, class << self blocks inside them still do. Constants and class variables defined inside such a singleton class block are attached to the singleton class:
Foo = Class.new do class << self CONST = 1 end end Foo.singleton_class.constants # => [:CONST] Foo.constants # => []
This also applies to nested anonymous classes within the singleton class block:
Foo = Class.new do class << self Class.new do CONST = 1 end end end Foo.singleton_class.constants # => [:CONST]