Ruby Language Behaviors
July 16, 2026 · View on GitHub
This document describes various Ruby language behaviors, compiled from observations in code analysis and testing.
Table of Contents
- Namespace Qualification
- Lexical Scoping and Constant Resolution
- Definitions vs Declarations
- Method Parameters
- Method Aliasing
- Attribute Methods
- Variable Scoping
- Mixins
- Visibility
- Constant References
- Constant Aliases
- Singleton Classes
- Anonymous Classes And Modules
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:
FooBarString
Qualified names include namespace separators:
Foo::BarActiveRecord::BaseRails::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 methodFoo.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::BarFoo::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 parameterb = 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 parameterg: 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 methodFoo.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
class Foo attr :foo # Creates only foo (getter) end -
Second parameter is
false: Creates only readerclass Foo attr :foo, false # Creates only foo (getter) end -
Second parameter is
true: Creates both reader and writerclass 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
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
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 theextendedhookincludein 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 constantsFoo.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.
A class without an explicit superclass inherits from Object. Exceptions are:
Objectitself inherits fromBasicObjectBasicObjectdoes NOT have any superclass.
class Foo; end
Foo.superclass # => Object
Object.superclass # => BasicObject
BasicObject.superclass # => nil
Foo.ancestors # => [Foo, Object, Kernel, BasicObject]
Object.ancestors # => [Object, Kernel, BasicObject]
BasicObject.ancestors # => [BasicObject]
Singleton classes use a separate root rule: BasicObject.singleton_class.superclass is Class.
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]