Ruby Language Behaviors

This document describes various Ruby language behaviors, compiled from observations in code analysis and testing.

Table of Contents

  1. Namespace Qualification

  2. Lexical Scoping and Constant Resolution

  3. Definitions vs Declarations

  4. Method Parameters

  5. Method Aliasing

  6. Attribute Methods

  7. Variable Scoping

  8. Mixins

  9. Visibility

  10. Constant References

  11. Constant Aliases

  12. Singleton Classes

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

Qualified names include namespace separators:

Top-level references start with :::

Rooted qualified names combine both (starting with :: and including namespace separators):

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:

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:

  1. If the name starts with ::, it refers to a top-level constant

  2. Otherwise, the name is qualified relative to the current namespace

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

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:

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:

  1. Module definition for Foo

  2. Class definition for Foo::Bar (inside Foo)

  3. Class definition for Foo::Bar (at top level)

2 distinct declarations:

  1. The module Foo

  2. The class Foo::Bar

Key Points:

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:

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:

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:

  1. No second parameter: Creates only reader

ruby class Foo attr :foo # Creates only foo (getter) end

  1. Second parameter is false: Creates only reader

ruby class Foo attr :foo, false # Creates only foo (getter) end

  1. Second parameter is true: Creates both reader and writer

ruby class Foo attr :foo, true # Creates both foo and foo= (getter and setter) end

  1. 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 “‘

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

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:

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

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]

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)

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:

  1. What constants are defined

  2. The lexical scope chain

  3. 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):

  1. Current namespace (Foo::Bar::String)

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

  1. Prepended modules (in reverse order of prepending)

  2. Current class

  3. Included modules (in reverse order of inclusion)

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

  1. Lexical scopes (current namespace, then each enclosing scope)

  2. Ancestor chain of the innermost enclosing class or module

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

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

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: FooFoo.singleton_classFoo.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]