Note: This guide works with Ruby 2.6+ through Ruby 3.x, with modern Ruby 3.x features highlighted where applicable. Core metaprogramming concepts remain consistent across Ruby versions.
This document has been collaboratively updated and modernized through an interactive process with Claude Code, revised with examples, visual diagrams, and Ruby 3.x compatibility.
- Key Concepts
- Self
- Anonymous Class (Singleton Class)
- Inheritance Chain
- Super, prepend, and delegator
- Block
- Methods for Metaprogramming
- Open Singleton Class and New scope
- Constant and instance_eval
- def vs define_method
- Binding
- Send
- Extend and Include
- DSL and yield
- method_missing and define_method
- Fiber
- Ruby Method Naming
- Rack middleware call chain
- Equality
- Direct Instance from Class
- block, lambda, and Proc
- Practical Metaprogramming Examples
- Ruby 3.x Modern Features
- References
- Additional Resources
Before diving into Ruby metaprogramming, it's important to understand these fundamental concepts:
The main
object is Ruby's special top-level execution context. When you write code outside of any class or module definition, that code executes in the context of the main
object.
# At the top level of a Ruby file
puts self # => main
puts self.class # => Object
puts self.inspect # => main
def top_level_method
puts "This method is defined on main"
end
top_level_method # => "This method is defined on main"
The main
object is an instance of Object
and includes the Kernel
module, which is why you can call methods like puts
, print
, and p
at the top level.
Every object in Ruby has its own anonymous class called a singleton class or metaclass. This class can hold methods that belong only to that specific object. Singleton classes are where class methods and per-object methods are actually held.
The path Ruby follows when searching for a method definition. Ruby starts with the receiver's singleton class, then moves up through the class hierarchy (ancestors) until it finds the method or reaches BasicObject
.
The object that receives a method call. In obj.method_name
, obj
is the receiver. When you call a method without an explicit receiver (method_name
), self
becomes the implicit receiver.
The inheritance hierarchy that Ruby follows during method lookup. You can see it with SomeClass.ancestors
. It includes the class itself, included modules, superclasses, and their modules.
A Ruby object that captures the execution context (local variables, self
, constants, etc.) at a specific point in code. Bindings are used with eval
and related metaprogramming techniques.
Opening a singleton class means accessing and modifying the anonymous class that belongs to a specific object. Ruby provides the class << obj
syntax to "open" this hidden class and define methods directly in it. This is fundamental to understanding how Ruby implements class methods and per-object behavior.
In Ruby, self
is a special keyword that always references the current object - the object that is the context for the currently executing code. Understanding self
is crucial because it determines method dispatch and variable access.
graph TD
A["self referenced at"] --> B{Code Location}
B -->|"Top level<br/>(outside classes/modules)"| C["main object"]
B -->|"Inside class definition<br/>class MyClass<br/> # self here<br/>end"| D["MyClass<br/>(Class object)"]
B -->|"Inside module definition<br/>module MyModule<br/> # self here<br/>end"| E["MyModule<br/>(Module object)"]
B -->|"Inside instance method<br/>def method_name<br/> # self here<br/>end"| F["Instance object<br/>(receiver of method)"]
B -->|"Inside class method<br/>def self.method_name<br/> # self here<br/>end"| G["Class object"]
B -->|"Inside singleton method<br/>def obj.method_name<br/> # self here<br/>end"| H["Specific instance<br/>(obj)"]
style C fill:#f9f,stroke:#333,stroke-width:2px
style D fill:#bbf,stroke:#333,stroke-width:2px
style E fill:#bfb,stroke:#333,stroke-width:2px
style F fill:#ffb,stroke:#333,stroke-width:2px
style G fill:#fbf,stroke:#333,stroke-width:2px
style H fill:#bff,stroke:#333,stroke-width:2px
When you reference self
at different places in your code:
- Top level (outside classes/modules):
self
→main
object - Inside
class ClassName
:self
→ theClassName
object itself - Inside
module ModuleName
:self
→ theModuleName
object itself - Inside
def method_name
:self
→ the instance that called the method - Inside
def self.method_name
:self
→ the class object - Inside
def object.method_name
:self
→ the specificobject
Method definitions live in classes, not in individual objects. When you call obj.method
, Ruby finds the method definition in the class and executes it.
self
as the implicit receiver:
- When you write
method_name
(without a dot), Ruby automatically treats it asself.method_name
- This means
self
determines which object will receive the method call - Example: Inside a method, calling
puts "hello"
is actuallyself.puts "hello"
Method lookup process:
- Ruby first looks for the method on
self
- If not found, Ruby searches up the method lookup chain (ancestors) until it finds the method definition
- You can use
super
to explicitly call the parent class's version of the current method
- Each object in Ruby has its own anonymous (singleton) class, a class that can have methods, but is only attached to the object itself.
The simplest way to add a singleton method - Ruby implicitly opens the class's singleton class:
class Person
end
def Person.species
"Homo sapiens"
end
Person.species
# => "Homo sapiens"
Inside the class definition, self
refers to the class object itself:
class Animal
def self.count
"7 billion"
end
end
Animal.count
# => "7 billion"
Use class << self
to explicitly open the singleton class within the class definition:
class Vehicle
class << self # Opens Vehicle's singleton class
def greeting
"Hello from #{self}"
end
end
end
Vehicle.greeting
# => "Hello from Vehicle"
instance_eval
executes code in the context of the receiver's singleton class:
class Robot
end
Robot.instance_eval do
def sound
"Beep beep!"
end
end
Robot.sound
# => "Beep beep!"
Use class << ClassName
to explicitly open any class's singleton class:
class Database
end
class << Database # Opens Database's singleton class
def connection_status
"Connected to PostgreSQL"
end
end
Database.connection_status
# => "Connected to PostgreSQL"
Understanding the difference between instance methods and singleton methods with eval:
class Computer
end
# This adds INSTANCE method to Computer class
Computer.class_eval do
define_method :cpu_cores do
8
end
end
laptop = Computer.new
laptop.cpu_cores # => 8 (instance method)
# This adds SINGLETON method to Computer class
Computer.singleton_class.class_eval do
define_method :operating_system do
"Linux"
end
end
Computer.operating_system # => "Linux" (class method)
Ruby 3.x introduces endless method syntax for concise one-line methods:
class MathUtils
end
# Traditional syntax
MathUtils.instance_eval do
def add(a, b)
a + b
end
end
# Ruby 3.x: Endless method syntax (requires Ruby 3.0+)
MathUtils.instance_eval do
def multiply(a, b) = a * b
def square(n) = n * n
def pi = 3.14159
end
MathUtils.add(2, 3) # => 5
MathUtils.multiply(4, 5) # => 20
MathUtils.square(6) # => 36
MathUtils.pi # => 3.14159
Modern Ruby emphasizes keyword arguments for better API design:
class HTTPClient
end
HTTPClient.singleton_class.class_eval do
define_method :request do |endpoint:, method: :get, timeout: 30, retries: 3|
"#{method.upcase} #{endpoint} (timeout: #{timeout}s, retries: #{retries})"
end
end
HTTPClient.request(endpoint: "/users")
# => "GET /users (timeout: 30s, retries: 3)"
HTTPClient.request(endpoint: "/posts", method: :post, timeout: 60)
# => "POST /posts (timeout: 60s, retries: 3)"
Ruby 3.x pattern matching works beautifully in dynamically defined singleton methods:
class EventHandler
end
EventHandler.singleton_class.class_eval do
define_method :handle_event do |event|
case event
in { type: "user_signup", id: Integer => user_id, email: String => email }
"New user registered: #{email} (ID: #{user_id})"
in { type: "purchase", items: Array => items, total: Numeric => total }
"Purchase completed: #{items.size} items, total: $#{total}"
in { type: "error", message: String => msg }
"System error: #{msg}"
else
"Unknown event type"
end
end
end
# Usage examples
EventHandler.handle_event({ type: "user_signup", id: 123, email: "[email protected]" })
# => "New user registered: [email protected] (ID: 123)"
EventHandler.handle_event({ type: "purchase", items: ["laptop", "mouse"], total: 999.99 })
# => "Purchase completed: 2 items, total: $999.99"
EventHandler.handle_event({ type: "error", message: "Database timeout" })
# => "System error: Database timeout"
Singleton methods can leverage Ruby 3.x's numbered parameters for cleaner code:
class TextProcessor
end
TextProcessor.instance_eval do
# Using numbered parameters (_1, _2, etc.)
def transform_words(text, &block)
text.split.map(&block).join(" ")
end
def combine_strings(str1, str2, &block)
block.call(str1, str2)
end
end
# Ruby 3.x: Numbered parameters make blocks more concise
result1 = TextProcessor.transform_words("hello world ruby") { _1.upcase.reverse }
# => "OLLEH DLROW YBUR"
result2 = TextProcessor.combine_strings("Hello", "World") { "#{_1} #{_2}!" }
# => "Hello World!"
Understanding Ruby's inheritance chain is fundamental to metaprogramming - it determines how methods are found and where singleton classes fit into the system.
Ruby's object model forms a tree with BasicObject
at the root:
graph TD
A["BasicObject<br/>(Root class)"] --> B["Object<br/>(Standard Ruby class)"]
B --> C["Module<br/>(Method container)"]
C --> D["Class<br/>(Can create instances)"]
B --> E["YourClass<br/>(Custom classes)"]
F["Kernel<br/>(Core methods module)"] -.->|"include"| B
A --- A1["Minimal interface<br/>__send__, __id__<br/>equal?"]
B --- B1["Standard methods<br/>puts, print<br/>class, is_a?, freeze, clone"]
C --- C1["Module features<br/>constants, method definitions<br/>include/extend"]
D --- D1["Class features<br/>new, allocate<br/>superclass"]
style A fill:#ffe6e6,stroke:#d63384,stroke-width:2px
style B fill:#e6f3ff,stroke:#0d6efd,stroke-width:2px
style C fill:#e6ffe6,stroke:#198754,stroke-width:2px
style D fill:#fff0e6,stroke:#fd7e14,stroke-width:2px
style E fill:#fff9e6,stroke:#ffc107,stroke-width:2px
style F fill:#e6f9ff,stroke:#20c997,stroke-width:2px
# Verify the relationships:
Class.superclass # => Module
Module.superclass # => Object
Object.superclass # => BasicObject
# Your classes inherit from Object by default:
class MyClass; end
MyClass.superclass # => Object
MyClass.ancestors # => [MyClass, Object, Kernel, BasicObject]
Singleton classes add a layer above the regular hierarchy. The key difference is what they can access:
class Person; end
person = Person.new
# Instance singleton class - limited access
person.singleton_class.ancestors
# => [`#<Class:#<Person:0x123>>`, Person, Object, Kernel, BasicObject]
# Class singleton class - full access including Class/Module methods
Person.singleton_class.ancestors
# => [`#<Class:Person>`, `#<Class:Object>`, `#<Class:BasicObject>`,
# Class, Module, Object, Kernel, BasicObject]
Why the difference? Class objects need access to Class
methods like .new
and Module
methods like .include
, but instances don't.
Method Location: Only classes and singleton classes hold methods - objects themselves don't hold methods, they delegate to their classes.
Class vs Superclass: A class object is NOT an instance of its superclass:
MyClass.class
→Class
(instance relationship)MyClass.superclass
→Object
(inheritance relationship)
Module Integration:
include
adds module methods as instance methods to the classextend
adds module methods as singleton methods (class methods)- Modules appear in
.ancestors
but not in.superclass
module Greetings
def hello; "Hello!"; end
end
class Person
include Greetings # instance methods
end
class Robot
extend Greetings # class methods
end
Person.new.hello # => "Hello!" (from include)
Robot.hello # => "Hello!" (from extend)
Person.ancestors # => [Person, Greetings, Object, Kernel, BasicObject]
Person.superclass # => Object (ignores Greetings module)
Ruby follows a systematic process to find methods when called on any object:
class Animal
def speak
"animal sound"
end
end
class Dog < Animal
def bark
"woof"
end
end
# Create instance and add singleton method
dog = Dog.new
def dog.unique_trick
"special trick"
end
# Ruby searches in this order:
dog.unique_trick # 1. Found in singleton class
dog.bark # 2. Found in Dog class
dog.speak # 3. Found in Animal superclass
dog.nonexistent # 4. Calls method_missing
graph TD
A[obj.method_call] --> B{Method in obj's singleton class?}
B -->|Yes| Z[Execute method]
B -->|No| C[Check obj.class]
C --> D{Method in obj.class?}
D -->|Yes| Z
D -->|No| E[Check included modules]
E --> F{Method in modules?}
F -->|Yes| Z
F -->|No| G[Check superclass]
G --> H{Method in superclass?}
H -->|Yes| Z
H -->|No| I[Check superclass modules]
I --> J{Method found?}
J -->|Yes| Z
J -->|No| K[Continue up chain]
K --> L{Reached BasicObject?}
L -->|No| G
L -->|Yes| M[method_missing]
style A fill:#f9f,stroke:#333,stroke-width:2px
style Z fill:#9f9,stroke:#333,stroke-width:2px
style M fill:#f99,stroke:#333,stroke-width:2px
- Singleton Class First: Ruby always checks the object's singleton class before its regular class
- Ancestors Chain: Follows the inheritance chain up through superclasses
- Module Inclusion: Included modules are inserted into the ancestry chain
- Class vs Instance: Class objects include meta-class hierarchies; instances use simpler inheritance
- Method Missing: If no method is found,
method_missing
is called
The following examples demonstrate these rules with specific scenarios:
When calling methods on a class's class (like B.class.some_method
), the lookup follows the meta-class hierarchy:
class B; end
# When you call B.class, you get the Class object
B.class # => Class
B.class.superclass # => Module
# You can call methods on Class itself
B.class.name # => "Class" (method from Class)
B.class.ancestors # => [Class, Module, Object, Kernel, BasicObject]
# The meta-class lookup chain for Class methods:
Class.singleton_class.ancestors
# => [`#<Class:Class>`, `#<Class:Module>`, `#<Class:Object>`,
# `#<Class:BasicObject>`, Class, Module, Object, Kernel, BasicObject]
# Note: `#<Class:Class>` means Class's singleton class (where Class methods are held)
# `#<Class:Module>` means Module's singleton class (where Module methods are held)
graph TD
A["B.class.some_method called<br/>(Class.some_method)"] --> B["Class's singleton class<br/>(Class:Class)"]
B --> C["Module's singleton class<br/>(Class:Module)"]
C --> D["Object's singleton class<br/>(Class:Object)"]
D --> E["BasicObject's singleton class<br/>(Class:BasicObject)"]
E --> F["Class<br/>(instance methods)"]
F --> G["Module<br/>(instance methods)"]
G --> H["Object<br/>(instance methods)"]
H --> I["Kernel<br/>(instance methods)"]
I --> J["BasicObject<br/>(instance methods)"]
style A fill:#f9f,stroke:#333,stroke-width:2px
style B fill:#ffb,stroke:#333,stroke-width:2px
style F fill:#fff0e6,stroke:#fd7e14,stroke-width:2px
When working with singleton classes of singleton classes, the chain becomes more complex:
class B; end
# Get the singleton class of B's singleton class
meta_singleton = B.singleton_class.singleton_class
# This creates a deep hierarchy
meta_singleton.ancestors
# => [`#<Class:#<Class:B>>`, `#<Class:Class>`, `#<Class:Module>`,
# `#<Class:Object>`, `#<Class:BasicObject>`, Class, Module, Object, Kernel, BasicObject]
# You can keep going deeper
B.singleton_class.singleton_class.singleton_class.ancestors
# => [`#<Class:#<Class:#<Class:B>>>`, `#<Class:#<Class:Class>>`, ...]
The key insight is that class methods search through meta-class hierarchies, while instance methods follow regular inheritance.
When a class extends a module (like B.extend V
), Ruby searches the module for methods:
class Z; end
class B < Z; end
module V
def test_method
"from module V"
end
end
B.extend V # Adds V's methods to B's singleton class
# Now B can call test_method as a class method
B.test_method # => "from module V"
# Check the lookup chain
B.singleton_class.ancestors
# => [`#<Class:B>`, V, `#<Class:Z>`, `#<Class:Object>`,
# `#<Class:BasicObject>`, Class, Module, Object, Kernel, BasicObject]
graph TD
A["B.some_method called"] --> B["B's singleton class<br/>(Class:B)"]
B --> V["Module V<br/>(extended methods)"]
V --> C["Z's singleton class<br/>(Class:Z)"]
C --> D["Object's singleton class<br/>(Class:Object)"]
D --> E["BasicObject's singleton class<br/>(Class:BasicObject)"]
E --> F["Class<br/>(instance methods)"]
F --> G["Module<br/>(instance methods)"]
G --> H["Object<br/>(instance methods)"]
H --> I["Kernel<br/>(instance methods)"]
I --> J["BasicObject<br/>(instance methods)"]
style A fill:#f9f,stroke:#333,stroke-width:2px
style B fill:#bbf,stroke:#333,stroke-width:2px
style V fill:#bfb,stroke:#333,stroke-width:2px
style F fill:#fff0e6,stroke:#fd7e14,stroke-width:2px
When calling methods on an instance, Ruby follows this search order:
class Z
def z_method
"from Z"
end
end
class B < Z
def b_method
"from B"
end
end
instance = B.new
# Add a singleton method to this specific instance
def instance.singleton_method
"from singleton"
end
# Method lookup order
instance.singleton_method # => "from singleton" (found first)
instance.b_method # => "from B" (found in class)
instance.z_method # => "from Z" (found in superclass)
# Check the search path
instance.singleton_class.ancestors
# => [`#<Class:#<B:0x123>>`, B, Z, Object, Kernel, BasicObject]
graph TD
A["instance.some_method called"] --> B["Instance's singleton class<br/>(Class:#B:0x123)"]
B --> C["Class B<br/>(instance methods)"]
C --> D["Class Z<br/>(instance methods)"]
D --> E["Object<br/>(instance methods)"]
E --> F["Kernel<br/>(instance methods)"]
F --> G["BasicObject<br/>(instance methods)"]
style A fill:#f9f,stroke:#333,stroke-width:2px
style B fill:#ffb,stroke:#333,stroke-width:2px
style C fill:#bbf,stroke:#333,stroke-width:2px
When a class includes modules, Ruby also searches those modules for methods:
class Z
def z_method
"from Z"
end
end
module M
def module_method
"from module M"
end
end
class B < Z
include M # Adds M's methods to B instances
def b_method
"from B"
end
end
instance = B.new
# Method lookup includes the module
instance.module_method # => "from module M" (found in included module)
instance.b_method # => "from B" (found in class)
instance.z_method # => "from Z" (found in superclass)
# Check the search path with module
instance.class.ancestors
# => [B, M, Z, Object, Kernel, BasicObject]
graph TD
A["instance.some_method called"] --> B["Instance's singleton class<br/>(Class:#B:0x123)"]
B --> C["Class B<br/>(instance methods)"]
C --> M["Module M<br/>(included methods)"]
M --> D["Class Z<br/>(instance methods)"]
D --> E["Object<br/>(instance methods)"]
E --> F["Kernel<br/>(instance methods)"]
F --> G["BasicObject<br/>(instance methods)"]
style A fill:#f9f,stroke:#333,stroke-width:2px
style B fill:#ffb,stroke:#333,stroke-width:2px
style C fill:#bbf,stroke:#333,stroke-width:2px
style M fill:#bfb,stroke:#333,stroke-width:2px
Let's trace exactly how Ruby finds methods with a concrete example:
module Greetings
def hello
"Hello from module"
end
end
class Animal
include Greetings
def speak
"Generic animal sound"
end
end
class Dog < Animal
def bark
"Woof!"
end
end
# Add singleton method to specific dog instance
rex = Dog.new
def rex.special_trick
"Rex can roll over!"
end
#####################################
# Method Lookup Demonstration
#####################################
# Show the exact lookup path
rex.class.ancestors
# => [Dog, Animal, Greetings, Object, Kernel, BasicObject]
# Show singleton class exists and its hierarchy
rex.singleton_class
# => `#<Class:#<Dog:0x00007f8b8c0a1234>>`
rex.singleton_class.ancestors
# => [`#<Class:#<Dog:0x00007f8b8c0a1234>>`, Dog, Animal, Greetings, Object, Kernel, BasicObject]
# Different lookup scenarios:
rex.special_trick # 1. Found in rex's singleton class
rex.bark # 2. Found in Dog class
rex.speak # 3. Found in Animal class
rex.hello # 4. Found in Greetings module
# Show where method is actually defined
rex.method(:hello).owner # => Greetings
rex.method(:bark).owner # => Dog
# Method not found - triggers method_missing
begin
rex.nonexistent
rescue NoMethodError => e
puts "NoMethodError: #{e.message}"
end
Step-by-step method lookup for rex.hello
:
graph TD
A["rex.hello called"] --> B["1. Check rex's singleton class"]
B --> C["Instance singleton class<br/>(Class:#Dog:0x123)"]
C --> D{Method 'hello' found?}
D -->|No| E["2. Check Dog class"]
E --> F{Method 'hello' found?}
F -->|No| G["3. Check Animal class"]
G --> H{Method 'hello' found?}
H -->|No| I["4. Check included modules"]
I --> J["Greetings module"]
J --> K{Method 'hello' found?}
K -->|Yes!| L["Execute Greetings#hello"]
L --> M["Return: 'Hello from module'"]
style A fill:#f9f,stroke:#333,stroke-width:2px
style L fill:#9f9,stroke:#333,stroke-width:2px
style M fill:#9f9,stroke:#333,stroke-width:2px
This example demonstrates how singleton_methods
differs from singleton_class.singleton_methods
and explores the nested structure of singleton classes:
class V; end
v = V.new
# 1. Add a singleton method to instance v
def v.singleton_method; "I'm a singleton method"; end
v.singleton_methods # => [:singleton_method] (methods on v itself)
v.singleton_class.instance_methods(false) # => [:singleton_method] (only new methods, not inherited)
# 2. Open v's singleton class's singleton class
class << v.singleton_class
def g; end # Instance method of v.singleton_class.singleton_class
def self.a; end # Singleton method of v.singleton_class.singleton_class
end
v.singleton_class.singleton_methods # => [:g] (g becomes singleton method of v.singleton_class)
v.singleton_class.singleton_class.singleton_methods # => [:a, :constants, :nesting, :used_modules]
# 3. Open v's singleton class directly
class << v
def h; end # Instance method of v.singleton_class (becomes singleton method of v)
def self.e; end # Singleton method of v.singleton_class (NOT a method of v)
end
v.singleton_methods # => [:h, :singleton_method] (direct singleton methods of v)
v.singleton_class.singleton_methods # => [:e, :g] (singleton methods of v's singleton class)
- Instance Methods → Singleton Methods: When you define an instance method inside
class << obj
, it becomes a singleton method ofobj
- Self Methods in Singleton Classes:
def self.method
creates a singleton method of the singleton class itself, NOT the original object - Different Method Levels:
obj.singleton_methods
shows methods callable onobj
, whileobj.singleton_class.singleton_methods
shows methods callable on the singleton class - Deep Nesting: Singleton classes have their own singleton classes, creating multiple meta-levels
- Method Accessibility: Methods defined with
self
are only accessible at the singleton class level, not the original object
This section consolidates all singleton method concepts from throughout this guide for easy reference.
Singleton methods are methods that belong to a specific object rather than all instances of a class. Ruby places these in each object's singleton class (also called metaclass or eigenclass).
# Regular instance method - available to ALL instances
class Dog
def bark; "woof"; end
end
# Singleton method - available to ONE specific instance
dog1 = Dog.new
def dog1.special_trick; "roll over"; end
dog1.special_trick # => "roll over"
dog2 = Dog.new
dog2.special_trick # NoMethodError - not available to other instances
obj = Object.new
# Method 1: Define outside object
def obj.method_name; "result"; end
# Method 2: Use singleton class syntax
class << obj
def method_name; "result"; end
end
# Method 3: Use define_method
obj.singleton_class.define_method(:method_name) { "result" }
class MyClass
# Method 1: Using self
def self.class_method; "class method"; end
# Method 2: Using class name
def MyClass.class_method; "class method"; end
# Method 3: Using singleton class
class << self
def class_method; "class method"; end
end
end
Understanding how singleton classes relate to each other:
class Animal; end
class Dog < Animal; end
dog = Dog.new
# Instance singleton class
dog.singleton_class.ancestors
# => [`#<Class:#<Dog:0x123>>`, Dog, Animal, Object, Kernel, BasicObject]
# Class singleton class
Dog.singleton_class.ancestors
# => [`#<Class:Dog>`, `#<Class:Animal>`, `#<Class:Object>`,
# `#<Class:BasicObject>`, Class, Module, Object, Kernel, BasicObject]
# Meta-class singleton class
Dog.singleton_class.singleton_class.ancestors
# => [`#<Class:#<Class:Dog>>`, `#<Class:Class>`, `#<Class:Module>`, ...]
The notation #<Class:Something>
means "the singleton class of Something":
# What the notation means:
`#<Class:Dog>` # → Dog's singleton class (where Dog's class methods live)
`#<Class:Class>` # → Class's singleton class (where Class's class methods live)
`#<Class:Module>` # → Module's singleton class (where Module's class methods live)
`#<Class:#<Dog:0x123>>` # → singleton class of Dog instance at memory address 0x123
# Practical demonstration:
Dog.singleton_class.to_s # => "`#<Class:Dog>`"
Class.singleton_class.to_s # => "`#<Class:Class>`"
Module.singleton_class.to_s # => "`#<Class:Module>`"
# Why this hierarchy exists:
# - Dog is an instance of Class, so Dog has access to Class methods (like .new)
# - Class is an instance of Class, so Class has access to Class methods
# - Class inherits from Module, so Class has access to Module methods (like .include)
# - This creates the chain: Dog → Class → Module → Object
Key Insight: Every Ruby object (including classes) has a singleton class. Even Class
and Module
have their own singleton classes where their class methods are held.
-
Instance Method Calls (
obj.method
):- obj's singleton class → obj.class → included modules → superclass chain
-
Class Method Calls (
Class.method
):- Class's singleton class → superclass singleton classes → Class → Module → Object
-
Module Class Methods:
- Only accessible via constant lookup:
Module.method
orModule::method
- NOT mixed in by
include
orextend
- Only accessible via constant lookup:
class Test; end
test = Test.new
# Different method types and where they live:
def test.direct_method; end # → test's singleton class
test.singleton_methods # => [:direct_method]
class << test
def regular_def; end # → test's singleton class (becomes singleton method of test)
def self.self_def; end # → test's singleton class's singleton class
end
test.singleton_methods # => [:direct_method, :regular_def] (methods callable on test)
test.singleton_class.singleton_methods # => [:self_def] (methods callable on test's singleton class)
module Greetings
def hello; "Hello!"; end
def self.module_method; "Module method"; end
end
class Person1
include Greetings # Adds instance methods
end
class Person2
extend Greetings # Adds class methods (singleton methods)
end
Person1.new.hello # => "Hello!" (instance method)
Person2.hello # => "Hello!" (class method)
# Module class methods require explicit access
Greetings.module_method # => "Module method" (only way to access)
- Singleton methods override instance methods in lookup chain
- Each object has its own singleton class, even if empty
- Class methods are actually singleton methods of the class object
- Method missing is called when no method found in entire chain
- Module class methods don't participate in include/extend mixing
This reference covers the essential singleton method concepts used throughout Ruby metaprogramming.
The super
keyword calls the next method in the method lookup chain, enabling method chaining and extension:
class Vehicle
def drive
"driving"
end
end
class Car < Vehicle
def drive
super + " fast" # Calls Vehicle#drive
end
end
Car.new.drive # => "driving fast"
Different ways to mix modules affect the method lookup chain and super
behavior:
module SpeedBoost
def drive
super + " fast" # Uses super to extend existing behavior
end
end
class Vehicle
def drive
"driving"
end
end
# 1. Prepend - module comes BEFORE class in lookup chain
class PrependCar < Vehicle
prepend SpeedBoost
end
# 2. Include - module comes AFTER class in lookup chain
class IncludeCar < Vehicle
include SpeedBoost
end
# 3. Extend - module methods become class methods
class ExtendCar < Vehicle
extend SpeedBoost
end
# === Ancestors Chain Comparison ===
PrependCar.ancestors # => [SpeedBoost, PrependCar, Vehicle, Object, Kernel, BasicObject]
IncludeCar.ancestors # => [IncludeCar, SpeedBoost, Vehicle, Object, Kernel, BasicObject]
ExtendCar.ancestors # => [ExtendCar, Vehicle, Object, Kernel, BasicObject]
# SpeedBoost is in ExtendCar's singleton class:
ExtendCar.singleton_class.ancestors
# => [`#<Class:ExtendCar>`, SpeedBoost, `#<Class:Vehicle>`, ...]
# === Method Call Results ===
PrependCar.new.drive # => "driving fast" (SpeedBoost#drive calls super → Vehicle#drive)
IncludeCar.new.drive # => "driving fast" (SpeedBoost#drive calls super → Vehicle#drive)
ExtendCar.drive # => ERROR: super: no superclass method `drive' for ExtendCar:Class
- Prepend: Module methods run before class methods (can intercept and modify)
- Include: Module methods run after class methods (can extend behavior)
- Extend: Module methods become class methods (singleton methods)
Prepend behavior:
SpeedBoost
is inserted beforePrependCar
in the ancestors chain- When
PrependCar.new.drive
is called, Ruby findsSpeedBoost#drive
first SpeedBoost#drive
callssuper
, which looks up the chain and findsVehicle#drive
- Result:
SpeedBoost#drive
wraps/intercepts the original method
Include behavior:
SpeedBoost
is inserted afterIncludeCar
in the ancestors chain- Since
IncludeCar
doesn't definedrive
, Ruby continues up the chain - Ruby finds
SpeedBoost#drive
and executes it SpeedBoost#drive
callssuper
, which continues up and findsVehicle#drive
- Result:
SpeedBoost#drive
extends the original method
Extend failure:
SpeedBoost#drive
becomes a class method onExtendCar
- Class methods look for class methods in the superclass hierarchy
Vehicle
only has an instance methoddrive
, not a class methodsuper
fails because there's no class method to call in the hierarchy
Objects can also extend modules at runtime, adding methods to their singleton class:
module Turbo
def drive
super + " with turbo!"
end
end
class Car
def drive
"driving"
end
end
# Extend specific instance
car = Car.new
car.extend(Turbo)
# Check the singleton class hierarchy
car.singleton_class.ancestors
# => [`#<Class:#<Car:0x123>>`, Turbo, Car, Object, Kernel, BasicObject]
car.drive # => "driving with turbo!"
# Other Car instances are unaffected
Car.new.drive # => "driving"
The main
object has special behavior with include
and extend
that demonstrates Ruby's delegation patterns:
# At top level (main object context)
self.respond_to?(:include) # => false (private method)
self.respond_to?(:extend) # => true
module Greeting
def hello
"Hello from module!"
end
end
module ClassMethods
def version
"1.0"
end
end
# Include affects Object class (all objects get the method)
include Greeting
self.hello # => "Hello from module!"
Object.new.hello # => "Hello from module!" (all objects have it)
# Extend affects main object only
extend ClassMethods
self.version # => "1.0"
Object.new.respond_to?(:version) # => false (only main has it)
The main
object delegates method calls through Ruby's method lookup chain:
- Include: Adds module to
Object
class (affects all objects) - Extend: Adds module to
main
's singleton class (affects onlymain
) - Method Missing: Handles unknown methods by delegating up the chain
# Main object's effective delegation pattern:
def method_missing(method_name, *args, &block)
# Delegates to Object class methods when appropriate
super
end
This delegation allows main
to act as both a regular object and a special top-level context.
Ruby's delegation pattern allows objects to forward method calls to other objects, commonly used in decorators and proxies:
class DelegateWrapper
def initialize(target_object)
@target = target_object
# Dynamically define methods to delegate to target
@target.class.instance_methods(false).each do |method_name|
self.class.define_method(method_name) do |*args, &block|
@target.send(method_name, *args, &block)
end
end
end
def inspect
"#{self.class}(#{@target.inspect})"
end
end
class SimplePrinter
def print_message(msg)
"Printing: #{msg}"
end
def status
"Ready"
end
end
# Usage example
printer = SimplePrinter.new
wrapper = DelegateWrapper.new(printer)
wrapper.print_message("Hello") # => "Printing: Hello" (delegated)
wrapper.status # => "Ready" (delegated)
wrapper.inspect # => "DelegateWrapper(#<SimplePrinter:0x...>)" (own method)
The decorator pattern uses delegation to add behavior to objects dynamically:
class Character
def describe
"You are a dashing, rugged adventurer."
end
end
class BaseDecorator
def initialize(target)
@target = target
# Delegate all target methods
@target.class.instance_methods(false).each do |method_name|
self.class.define_method(method_name) do |*args, &block|
@target.send(method_name, *args, &block)
end
end
end
end
class BowlerHatDecorator < BaseDecorator
def describe
super + " A jaunty bowler cap sits atop your head."
end
end
# Usage
character = Character.new
decorated = BowlerHatDecorator.new(character)
character.describe # => "You are a dashing, rugged adventurer."
decorated.describe # => "You are a dashing, rugged adventurer. A jaunty bowler cap sits atop your head."
This demonstrates how delegation enables dynamic behavior composition without inheritance.
For more details, see [1] [2].
- Blocks in Ruby are closures that capture the binding (local variables, methods, constants) from their surrounding scope.
- Blocks can be passed to methods using
yield
or converted toProc
objects with&block
. - Understanding blocks is essential for Ruby metaprogramming and DSL creation.
# example from:
# http://ruby-metaprogramming.rubylearning.com/html/ruby_metaprogramming_3.html
def who
person = "Matz"
yield("rocks")
end
person = "Matsumoto"
who do |y|
puts("#{person}, #{y} the world") # => Matsumoto, rocks the world
city = "Tokyo"
end
# puts city # => undefined local variable or method 'city' for main:Object (NameError)
This example demonstrates creating a memoization wrapper using dynamic class generation:
def mem_result(klass, method)
mem = {} # Memoization cache
Class.new(klass) do # Create new class inheriting from klass
define_method(method) do |*args| # Override the specified method
if mem.has_key?(args) # Check cache first
mem[args] # Return cached result
else
mem[args] = super # Call original method and cache result
end
end
end
end
This creates a new class that inherits from the original class but adds memoization to the specified method. The memoization cache is shared across all instances of the generated class.
-
When you define a block, it simply grabs the bindings that are there at that moment, then it carries those bindings along when you pass the block into a method.
-
Observe that the code in the block sees the
person
variable that was around when the block was defined, not the method'sperson
variable. Hence a block captures the local bindings and carries them along with it. You can also define additional bindings inside the block, but they disappear after the block ends. -
Passing
lambda
or->()
as block parameter will still be alambda
forreturn
. -
return
in lambda will treat as method and return back to current method scope.return
in block will return the result for current method. Usebreak
,continue
,next
instead. -
For procs created using
lambda
or->()
an error is generated if the wrong number of parameters are passed to aProc
with multiple parameters. For procs created usingProc.new
orKernel.proc
, extra parameters are silently discarded.
For more details, see [3].
The following examples demonstrate how return
behaves differently in lambdas, procs, and blocks, plus how different callable objects maintain their identity:
class G
# Lambda with literal syntax - return exits lambda only
def lambda_literal
[1,2,3,4].each(&->(x) { return false})
true # This line executes because return only exits lambda
end
# Lambda with lambda keyword - return exits lambda only
def lambda_test
[1,2,3,4].each(&lambda { |c| return false})
true # This line executes because return only exits lambda
end
# Block - return exits enclosing method
def block_test
[1,2,3,4].each do |i|
return false # This exits the entire block_test method
end
true # This line never executes
end
# Proc - return exits enclosing method
def proc_test
[1,2,3,4].each(&Proc.new do
return false # This exits the entire proc_test method
end)
true # This line never executes
end
def check &block
p block
end
end
# Test return behavior:
G.new.lambda_test # => true (lambda return doesn't exit method)
G.new.lambda_literal # => true (lambda return doesn't exit method)
G.new.block_test # => false (block return exits method)
G.new.proc_test # => false (proc return exits method)
# Test type inspection - how different callable objects maintain their identity:
G.new.check &->() {} # => #<Proc:0x0000010309b100@(irb):31 (lambda)>
G.new.check &lambda {} # => #<Proc:0x00000103092730@(irb):32 (lambda)>
G.new.check do; end # => #<Proc:0x00000103089fe0@(irb):33>
G.new.check &(Proc.new do; end) # => #<Proc:0x0000010306b7e8@(irb):35>
class B
def self.b &block
block.call
end
end
B.b &->{ p "hi"} # => "hi"
-
When Ruby does a method look-up and can't find a particular method, it calls a method named
method_missing()
on the original receiver. TheBasicObject#method_missing()
responds by raising aNoMethodError
. -
The methods
class_variable_get
(this takes a symbol argument representing the variable name and it returns the variable's value) andclass_variable_set
(this takes a symbol argument representing a variable name and a second argument which is the value to be assigned to the variable) can be used. -
Use the
class_variables
method to obtain a list of class variables. -
Use the
instance_variable_get
andinstance_variable_set
to obtain list of instance variables.const_get
with symbols andconst_set
to get constants -
Entities like local variables, instance variables,
self
... are basically names bound to objects. We call them bindings. -
eval
,module_eval
, andclass_eval
operate onClass
orModule
rather than instance. Instance which is a specific object as well can callinstance_eval
to operate executions and call instance variables. -
instance_eval
can also be used to add class methods in class object.class_eval
is able to define an instance/singleton method in class or module,instance_eval
creates instance or class methods depending on whether its receiver is either an instance or class. -
The
module_eval
andclass_eval
methods can be used to add and retrieve the values of class variables from outside a class. -
The
Module#define_method()
is a private instance method of the classModule
. Thedefine_method
is only defined on classes and modules. You can dynamically define an instance method in the receiver withdefine_method()
. You just need to provide a method name and a block, which becomes the method body. -
Leveraging by
respond_to?()
,class()
,instance_methods()
,instance_variables()
, we can check object information at run-time. -
You can call any methods with
send()
, including private methods. Usepublic_send()
to call public methods only. -
To remove existing methods, use the
remove_method
within the scope of a given class. If a method with the same name is defined for an ancestor of that class, the ancestor class method is not removed. Alternatively,undef_method
prevents specific class from calling that method even though the ancestor has the same method.
# 1.
# First open the class H.
# Thorugh << to set self as singleton class.
# Because it is in singleton class scope so it creates new scope.
# << will set self to singleton class, and open it.
x = 5
class H
# open H's singleton class
class << self
p x
p self.name
end
end
# directly open singleton class, set self to singleton class;
# create new scope because its in singleton class scope.
class << H
p x
p self.name # <Class:H> which is singleton class.
# define instance methods, for class H, which is H's singleton methods.
def h
p self # H.h => because receiver self is set to H, so will return H,
# rather than <Class:H>.
end
# define instance method in H.singleton_class' singleton class.
# which is H.singleton_class' singleton method
def self.g # H.singleton_class.g
p self # return <Class:H>.
end
end
# 2.
# This open class H, not in singleton class, so not create new scope.
# It is able to use local variable x.
# defines method in class H, but not in singleton class.
H.class_eval { p x; p self.name }
# instance_eval breaks apart the self into two parts.
# - the self that is used to execute methods
# - the self that is used when new methods are defined.
# When instance_eval is used:
# - new methods are defined on the singleton class.
# - but the self is always set the object itself. [Either class or instance]
# -> No matter inside or outside of new methods block.
# while method defined in singleton class,
# -> Ruby let it able to access outside local variable x
# -> Because it sets self as receiver's class.
Person.instance_eval do
def species
"Homo Sapien"
end
self.name #=> "Person"
end
# 3.
# This open singleton class for H class, will create new scope.
# The self will still assoicate to 'H' because for method s, H is the recevier.
# And because << is not used in here which will set self to singleton class
# So it is not able to call local variable x in a singleton method.
def H.s
p x
p self.name
end
# same as 3, here we still set self as H, then define s method in H's singleton class.
# so not able to use local variable outside.
class H
def self.s
p x
p self.name
end
end
mechanism | method resolution | method definition | new scope? |
---|---|---|---|
class Person | Person | Person | yes |
class << Person | Person’s metaclass | Person’s metaclass | yes |
Person.class_eval | Person | Person | no |
Person.instance_eval | Person | Person’s metaclass | no |
define new scope are not able to access outside local variable
-
In ruby 1.8.7 case 1 cannot call class_variable_get to get class variables.
-
In ruby 2.0.0 case 1 can call class_variable_get to get class variables though it is in the new scope.
-
<< not only open snigleton class, also set current self to self's singleton class.
-
Ruby uses the notation
#<Class:#<String:...>>
to denote a singleton class. Example:#<Class:H>
-
Each instance object have its own singleton class.
-
class_eval
open object class, and deirectly add method definition for it, so we can adddef self.method
for add class mehthods. -
instance_eval
will set self to receiver, and evaluate all methods in the block as singleton methods. If receiver is a class, then it define new singleton method in singleton class. If receiver is an instance, then it deine new instance method in instance's singleton class. -
Singleton class holds instance methods, but when it attached to an object which either class or instance, those instance methods convert to singleton methods for that class or instance.
-
You can’t create a new instance of a singleton class.
-
methods only defined in class or singleton classs, not instance object.
For more details, see [27].
module One
CONST = "Defined in One"
def self.eval_block(&block)
instance_eval(&block)
end
end
module Two
CONST = "Defined in Two"
def self.call_eval_block
One.eval_block do
CONST
end
end
end
Two.call_eval_block # => "Defined in Two" (modern Ruby behavior)
# Note: older Ruby versions behaved differently
-
In modern Ruby, calling a constant in
instance_eval
will get the constant in the lexical scope which it refers to. -
Note: older Ruby versions had different behavior where constants were resolved in the receiver's class.
-
instance_eval
open receiver's singleton class and bind each method's receiver if it is not specified(instance method, or so-called UnboundMethod). So methods all are binded. -
class_eval
open class and define it as usual. Methods without explicitly receiver areUnboundMethod
.
class T; end
T.class_eval do
def um; end
end
x = T.instance_method :um
# <UnboundMethod: T#um>
# To bind unbound method :um
t = T.new
x.bind t
# <Method: T#um>
# instance_eval automatically bind methods to its receiver.
T.instance_eval do
def m; end
end
s = T.method :m
# <Method: T.m>
-
In block of
instance_eval
, self be set and hijack to current receiver. Need caution for DSL. -
class
definition changes the default definee but method definition does not. -
When you give a receiver to a method definition, the method will be adde into the eigenclass of the receiver.
-
if you define a method with the normal method definition syntax, the default definee will have the method as an instance method.
-
class_eval
andinstance_eval
:self default definee class_eval the receiver the receiver instance_eval the receiver eigenclass of the receiver -
class_eval
andinstance_eval
can accept string as code, both setsself
to receiver during evaluation. We can useHere
doc or string to make codes rather than use block. When we callclass_eval
andinstance_eval
with block,self
will be set to receiver inside the block. And forinstnace_eval
the method definee will bind to receiver fromunbound method
tobound method
.
module Migration
class Base
def self.create_table name, &block
t = Table.new(name)
t.evaluate &block
t.create
end
end
end
class Table
attr_reader :name, :columns
def initialize(name)
@name = name.to_s
@columns = []
end
def evaluate &block
instance_eval &block
end
def string(*columns)
@columns += columns
end
def create
puts "creating the #{@name}, with columns #{columns.inspect}"
end
end
class Mardel < Migration::Base
def self.change
create_table :table do
string name("c1"), name("c2")
end
end
def self.set_name(name)
"#{name}_column"
end
end
Mardel.change
# ArgumentError: wrong number of arguments (1 for 0)
# This is cause error because, self has set to Table due to instance_eval.
# So `name("c1")` will get error because attr_reader :name not accept any argument.
# We make changes as below:
class Mardel < Migration::Base
def self.change
create_table :table do
string set_name("c1"), set_name("c2")
end
end
def self.set_name(name)
"#{name}_column"
end
end
Mardel.change
# NoMethodError: undefined method `set_name' for #<Table:0x0000010184c928>
# In here, we need passing block's binding, so can call Mardel.set_name method.
# Fix as below:
class Table
def method_missing(method, *args, &block)
@self_in_block.send(method, *args, &block)
end
def evaluate &block
@self_in_block = eval "self", block.binding
instance_eval &block
end
end
Mardel.change
# creating the table, with columns ["c1_column", "c2_column"]
# now works.
class_eval
only works on class object, which open that class and evaluate it. Not prepend receiver for it.eval
evalute string as expression. ex:eval "'p' + 'pp'"
- Inside the block of
clas_eval
orinstance_eval
, don't directly make method definition, instead, usedefine_method
to let closure works, which will enable to use variables outside of block. This is becausedefine_method
is not a keyword, so method definition will be resolve at runtime. If we usedef
, method definition will be directly scanned in lexical parsing. The same idea asalias
v.s.alias_method
.
class A; end
class B
def b
x = 10
A.class_eval do
# x can be used inside block due to closure feature.
p x
# def is a keyword, the method definition will be parsed in lexical parsing
# so x is actually some local var.
def g
x
end
end
# define_method not a key word.
# so variable x will be parse until run time.
A.class_eval do
define_method :z do
p self
x
end
end
end
end
A.new.z
# => 10
A.new.g
# NameError: undefined local variable or method `x' for #<A:0x0000010126d408>
- Methods in Ruby are not objects, but
Method
class can represent the given method. Methods are bind with symbol, so we can provide a symbol tosend
which calls the method bind with its name.
# First call to eval, the context is binding to the main object
# -> and call local variable str.
# Second call, the context moves inside the getBinding method.
# And the local value of str is now that of the
# -> str argument or variable within that method.
class MyClass
@@x = " x"
def initialize(s)
@mystr = s
end
def getBinding
return binding()
end
end
class MyOtherClass
@@x = " y"
def initialize(s)
@mystr = s
end
def getBinding
return binding()
end
end
@mystr = self.inspect
@@x = " some other value"
ob1 = MyClass.new("ob1 string")
ob2 = MyClass.new("ob2 string")
ob3 = MyOtherClass.new("ob3 string")
puts(eval("@mystr << @@x", ob1.getBinding))
puts(eval("@mystr << @@x", ob2.getBinding))
puts(eval("@mystr << @@x", ob3.getBinding))
puts(eval("@mystr << @@x", binding))
# Modern Ruby output:
# ob1 string some other value
# ob2 string some other value
# ob3 string some other value
# main some other value
# Modern Ruby evaluates class variables within a binding.
# However, it gives preference to class variables
# if they exist in the current binding (main object).
# This differs from older versions, which always bound context to receiver's context.
-
use
Kernel::eval
to evalute string expression with binding in main scope. -
Local and instance variables can captured by calling
binding
. You can access any local and instance variables by passing a reference of a binding context and callingeval
on it. For more details, see [7].
@x = 40
class B
def initialize
@x = 20
end
def get_binding
binding
end
end
class A
attr_accessor :x
def initialize
@x = 99
end
def a b
s = eval "@x", b
puts "A's @x: #{@x}"
puts "Binding's @x: #{s}"
@x = s
puts @x
@x
end
def ev b, binding
eval "a #{b}", binding
end
end
obj = A.new
b = B.new
# use Kernel::eval, which is in main scope
# send context, variables to obj
# obj.ev "binding", binding
# NoMethodError: undefined method `a' for main:Object.
# It is because obj.ev binding to main scope, but main scope dont have method a.
def a b
puts 'in Main scope'
end
obj.ev "binding", binding
# in Main scope
obj.a binding
# A's @x: 99
# Binding's @x: 40
# 40
# => 40
obj.a b.get_binding
# A's @x: 40
# Binding's @x: 20
# 20
# => 20
For more details, see [8].
# A block cannot exist alone; it needs a method to be attached to it.
# We can convert a block to Proc object.
# a = Proc.new{|x| x = x*10; puts(x) }
# b = lambda{|x| x = x*10; puts(x) }
# c = proc{|x| x.capitalize! }
# send does not take block as params.
def method_missing( methodname, *args )
self.class.send( :define_method, methodname,
# => A Proc object pass into send *args array
lambda{ |*args| puts( args.inspect) }
)
end
graph TD
subgraph "include ModuleA"
A1[Class] --> A2[ModuleA] --> A3[Superclass]
A4[Instance] -.->|can call| A2
A5[ModuleA methods become<br/>instance methods]
end
subgraph "extend ModuleA"
B1[Class] --> B3[Superclass]
B2[Class's Singleton Class] --> B6[ModuleA] --> B7[Superclass Singleton]
B4[Class] -.->|can call| B6
B5[ModuleA methods become<br/>class/singleton methods]
end
style A2 fill:#bfb,stroke:#333,stroke-width:2px
style B6 fill:#fbf,stroke:#333,stroke-width:2px
Key Differences:
-
Include: Adds module methods as instance methods
-
Extend: Adds module methods as class/singleton methods
-
The extend method will mix a module's instance methods at the class level. The instance method defined in the Math module can be used as a class/static method.
Important: Module class methods (defined with self.method_name
) can only be called through constant lookup (e.g., Module::method_name
or Module.method_name
). They are not mixed in through include
or extend
.
module A
module B
def b; end
def self.bb; end
end
end
class G
include A
end
G::B
# => A::B, Object G change to namespace A and do constant Lookup.
class V
extend A
end
V.singleton_class::B
# => A::B
-
The include method will mix a module’s methods at the instance level, meaning that the methods will become instance methods.
-
The
Module.append_features
on each parameter forextend
orinclude
in reverse order. -
If we include multiple module in one line, it will look up by its declaring order. If we include or extend in multiple times, then it will look up from the reverse declaring order.
module A
def say; puts "In A"; end
end
module B
def say; puts "In B"; end
end
class Parent
def say; puts "In Parent"; end
end
class Child
include A, B
end
p Child.ancestors
# [Child, A, B, Object, Kernel, BasicObject]
Child.new.say
# In A
class ChildToo
include A
include B
end
p ChildToo.ancestors
# [ChildToo, B, A, Object, Kernel, BasicObject]
ChildToo.new.say
# In B
-
The difference is that
include
will add the included class to the ancestors of the including class, whereasextend
will add the extended class to the ancestors of the extending classes' singleton class. -
Recall that for each instance inherits from
Object
,Object
provides aextend
method which add instance method from the module. It is different as declareextend
in class definition, which add methods to object's singleton class. For more details, see [9]. -
extend a module A in another module B will add A to singleton class of B, include a module A in module B will add A to B's class.
-
class method
defined in module is prepare for receiver's singleton class to use,instance method
is prepare for receiver's instance. -
Class.singleton_class.ancestors
to track inheritance chain from singleton class. -
Module cannot be instantiate with
new
for itself.
# Namespace will transfer to module's namespace.
module A
module B
module C
def a(val = nil)
!val ? super val : puts "IN C"
end
end
def a(val = nil)
puts "IN B"
end
end
module D
def self.d
puts "IN D"
end
end
end
module A
include B
extend D
# D will not be found in ancestors output, but in singleton class' chain.
end
class G
include A
end
class T
extend A
end
T.singleton_class.ancestors
# [`#<Class:T>`, A, A::B, `#<Class:Object>`,
# `#<Class:BasicObject>`, Class, Module, Object, Kernel, BasicObject]
T.singleton_class.included_modules
# [A, A::B, Kernel]
T.singleton_class::D
# A::D
# Because ruby cannot find namespace in T class, so go module A to find namespace D.
T.singleton_class::D.d
# IN D
# now call singleton method d in moudle D.
# We cannot call instance method here, because receiver is a class.
G.ancestors
# [G, A, A::B, Object, Kernel, BasicObject]
# Include or extend will not transfer namespace to for host module.
# For class or singleton_class, it will check include or extended module namespace for finding methods.
G::B
# A::B
# Because cannot find namespace in G, so go to namespace A then go module B and fount it.
G::C
# A::B::C, ruby go deeper to find namespace C under module B.
G::D.d
# In D
G::B.a
# NoMethodError
G.new.a("str")
# In B
# because we not include C in A, so it will only call B::a
module A
include C
end
class G
include A
end
G.include A
# Now we add a new include in module A.
# But class G will load previous module A's included modules.
# So we need to reopen the class G and include module A for reloading again.
# Or just one line G.include A for same thing above.
G.ancestors
# [G, A, A::B::C, A::B, Object, Kernel, BasicObject]
G.new.a("str")
# In C
G.new.a
# In B
# finally check moudle A's namspaces.
A.included_modules
# [A::B::C, A::B]
A.singleton_class.included_modules
# [A::D, Kernel]
module W
class O
def o
puts "OO"
end
def self.o
puts "Singleton OO"
end
end
end
# class dont have to include in module. Just namespace for it.
class L
include W
end
L::O
# W::O
L::O.new.o
# "OO"
L::O.o
# "Singleton OO"
- In module, define class method such
self.method
will be in module level methods. Which only be access in that namespace explicitly or implicitly. - For more details on constant lookup and module namespaces, see [10].
- Include: Module instance methods → Class instance methods
- Extend: Module instance methods → Class singleton methods (class methods)
Key Point: Module class methods must be called via constant lookup (e.g.,
Module.class_method
). When usingextend
, you only get the module's instance methods as class methods - the module's class methods still require explicit access.
module G
def g; end
def self.gg; end
end
class A
extend G
end
A.singleton_methods
# have :g
A.include G
A.instance_methods
# have :g
module H
extend G
end
# because extend send instance method of G to H's singleton class.
H.singleton_methods.include? :g
# true
For comprehensive coverage of include/extend patterns and detailed constant lookup mechanics, see [11] [12].
# rails c
module A
module S
extend ActiveSupport::Concern
included do
def self.n
@@n
end
def self.n= (str)
@@n = str
end
end
end
module D
def d
puts "IN D"
puts "name: #{n}"
end
end
module Z
def pi; end
# This made :ty in A::Z namespace only.
def self.ty; end
end
module ZZ
def pizz; end
# This made :ty in A::ZZ namespace only.
def self.tyzz; end
end
end
module A
extend D
include S
include Z
extend ZZ
end
A.ancestors
# => [A, A::Z, A::S]
A.singleton_class.ancestors
# => [`#<Class:A>`, A::ZZ, A::D, Module,
ActiveSupport::Dependencies::ModuleConstMissing,
Object, PP::ObjectMixin, ActiveSupport::Dependencies::Loadable,
JSON::Ext::Generator::GeneratorMethods::Object, Kernel, BasicObject]
A::Z.ty
# => nil
A::ZZ.tyzz
# => nil
# self.method in module will make that method only in that namespace scope.
# Instace method will only be used in instance objec only.
# Include instance method will be usable. Because it include in that instance class.
# Extend instance method have no meaning in that instance.
# Because instance method is in singleton_class but cannot be called.
A::ZZ.instance_methods
# => [:pizz]
class T
include A
end
T.new.singleton_class::Z
# => A::Z
T.new.singleton_class::Z.instance_methods
# => [:pi]
T.new.singleton_class::ZZ.instance_methods
# => [:pizz]
T.module_eval do
A::ZZ.tyzz
end
# tyzz existed for module level namespace A::ZZ
T.module_eval do
A::Z.ty
end
# ty existed for module level namespace A::Z
T.module_eval do
# A::Z.pi
# no-method error
end
A.n = "str"
A.d
# IN D
# name: str
A.respond_to? :pizz
# true
# Conclude this:
# module A extend module B
# B's instance method will be treat as A's method
# B's class method will be treat as A::B's method
#
# module A include module B
# B's instance method will be in class T which include or extend A's method
# if class T include A => B's instance method in T.new.methods
# if class T extend A => B's instance method in T.new.singleton_class.methods
# B's class method will be treat as A::B's method
# if class T include A => B's class method in T.new.singleton_class::B
# if class T extend A => B's class method cannot be called directly.
# (danger way: T.singleton_class::A::B) or use module_eval
T.module_eval do
A::ZZ.tyzz
end
-
yield
expects method to carry a block and transfer evaluation from method to that block. By default method's argument list doesn't have to list block argument, but providing it explicitly with&
in argument list will convert that block toProc
object. It is more readable and avoids block chain pass arguments which may affect performance. -
Although the method did not explicitly ask for the block in its arguments list, the
yield
can call the block. This can be implemented in a more explicit way using aProc
argument. -
Whenever a block is appended to a method call, Ruby automatically implicitly converts it to a Proc object but one without an explicit name. The method, however, has a way to access this
Proc
, by means of theyield
statement. -
If
&
is provided in an argument, the block attached to this method is explicitly converted to aProc
object and gets assigned to that last argument by its argument name.
# bad
def with_tmp_dir
Dir.mktmpdir do |tmp_dir|
Dir.chdir(tmp_dir) { |dir| yield dir } # this block just passes arguments to yield
end
end
# good
def with_tmp_dir(&block)
Dir.mktmpdir do |tmp_dir|
Dir.chdir(tmp_dir, &block)
end
end
with_tmp_dir do |dir|
puts "dir is accessible as parameter and pwd is set: #{dir}"
end
Consider using explicit block argument to avoid writing block literal that just passes its arguments to another block. Beware of the performance impact, though, as the block gets converted to a Proc.
For more details, see [13] [14] [15].
method_missing
is Ruby's mechanism for handling calls to undefined methods. When Ruby cannot find a method, it calls method_missing
with the method name, arguments, and block. This enables dynamic method creation, proxies, and domain-specific languages. define_method
creates methods dynamically at runtime with proper closure access to local variables, making it ideal for metaprogramming patterns like creating accessor methods or method factories.
For more details, see [16] [17].
This example shows how to implement custom method_missing
behavior:
class A
def method_missing(name, *args, &block)
if name ~= /^ss$/ # Match method names starting with 'ss'
puts "Name: #{name}" # Show the method name that was called
puts "Args: #{args}" # Show arguments passed
puts "Block: #{block}" if block_given? # Show block if provided
else
super # Delegate to parent's method_missing
end
end
end
A.new.ss(1, 2, 10) { |x| puts x }
# Name: ss
# Args: [1, 2, 10]
# Block: <#<Proc:0x0000010215ed68@(irb)>
x = A.new
class << x
define_mehthod("mth") { |args = nil| args ? puts "#{args}" : puts "No args input." }
define_mehthod(:sym_mth) { |*args| puts "Args: #{args}" }
end
x.mth("args in here")
# args in here
x.mth
# No args input
x.sym_mth(1, 2, 3)
# Args: [1, 2, 3]
x.sym_mth
# Args:
For advanced Fiber patterns and fundamentals, see [18] [19].
-
Fiber requires a block. Inside block, put
Fiber
class object to use its class method. -
Arguments passed to
resume
will be the value of theFiber.yield
expression or will be passed as block parameters to the fiber’s block if this is the first resume. -
Alternatively, when resume is called it evaluates to the arguments passed to the next Fiber.yield statement inside the fiber’s block, or to the block value if it runs to completion without hit any further Fiber.yield.
-
Due to above statment, fiber last return result is the same as argument value passed to
resume
. -
Fiber can store context, block switch to new context every time.
class Enum
def initialize
@yielder = Fiber.new do
yield Fiber
end
end
def next
@yielder.resume
end
def mock_yield
next
end
end
e = Enum.new do |yielder|
num = 1
loop do
# Fiber.yield
yielder.yield num
num += 1
end
end
p e.mock_yield # 1
p e.mock_yield # 2
p e.mock_yield # 3
f = Fiber.new do |arg|
Fiber.yield arg + 5, arg + 6
end
f.resume 5
# [10, 11]
f.resume 5
# 5
f.resume 5
# fiber dead
require 'fiber'
f.alive?
# false
f = Fiber.new do |arg|
p 1
Fiber.yield arg + 5
p 2
Fiber.yield
p 3
end
# first resume run until hit Fiber.yield or block end.
# if hit block yield, return its control to f's scope.
f.resume 4
# 1
# => 9
f.resume 4
# 2
# => nil
# f.resume transfer control to Fiber block, finish print 3, hit the block end
f.resume 4
# 3
# => 3
z = Fiber.new do |arg|
Fiber.yield arg + 5
end
z.resume 4
# => 9
z.resume 4
# => 4
# Because resume turns control back to Fiber block, but does not hit any Fiber.yield in next expressions,
# It returns resume's argument value as block's return value.
- Ruby allows letters, underscores, and numbers (not in the first position), ending with
=
,?
,!
for naming conventions. - It also supports syntax sugar for
[]
and[]=
which is[](id)
,[](key, value)
for hash or array like methods. - Many operators can be redefined in your class, but you would not be able to process original operators if they are supported from superclass.
class MockHash
def initialize(input)
@hash = Hash(input)
end
def [](id)
@hash[id]
end
def []=(id, value)
@hash[id] = value
end
end
class MockHashII
def initialize(input)
@hash = Hash(input)
end
def to_h
@hash
end
def method_missing(mth, *args)
to_h.public_send(mth, *args)
end
end
class MockArray
def initialize(input)
@ary = Array(input)
end
def [](id)
@ary[id]
end
def []=(id, value)
@ary[id] = value
end
def to_a
puts "Loosely conversion for explicitly cal."
@ary
end
def to_ary
puts "Strct conversion for implicitly call."
@ary
end
def <<(values)
@ary << values
end
end
ary = MockArray.new [1,2,3,4,5,6]
hash = MockHash.new a: 1, b: 2, c: 3
hash2 = MockHashII.new z: 1, b: 2, c: 3
hash[:a]
# => 1
hash[:z] = 8
# => 8
ary[0]
# => 1
ary[6] = 9
#=> 9
hash2[:v] = 8
# => 8
[] + ary
# Strct conversion for implicitly call.
# => [1, 2, 3, 4, 5, 6, 9]
a = *ary
# Loosely conversion for explicitly cal.
# => [1, 2, 3, 4, 5, 6, 9]
- Ruby core class never use explicit conversions if not explicit conversion calls. But it has counter examples. Check [20] book page 58 for more info.
Counter example :
- When we use string interpolation, Ruby implicitly calls
to_s
to convert arbitrary objects to strings.
"Time class use explicit conversion: #{Time.now}"
# string interploation conver Time object by Time#to_s method
class B
def to_s
"B"
end
end
"" + B.new
# TypeError: no implicit conversion of B into String
String B.new
# "B"
# call `to_s` instead
For more details, see [21] [22] [23].
- Use implicit conversion if you want to guard the input
type
such asnil
value. If you dont care about the edge case of input, just want to run the logic, use explicitly conversion instead.
nil.to_s
# convert the result "" and kepp logic running
nil.to_str
# NoMethodError: undefined method `to_str' for nil:NilClass
class A
def initialize(app = nil)
@app = app
end
def call(env)
p env
p "A"
end
end
class B
def initialize(app)
@app = app
end
def call(env)
p env
p "B"
@app.call(env)
end
end
class C
def initialize(app)
@app = app
end
def call(env)
p env
p "C"
@app.call(env)
end
end
# simulate Rack::Builder call chain
app_a = A.new
stack = [Proc.new{ |app| B.new app}, Proc.new{ |app| C.new app}]
stack = stack.reverse.inject(app_a) { |e, a| e[a] }
# (Proc.new{ |app| B.new app }.call(Proc.new{ |app| C.new app}))
# #<B:0x000001030bff00 @app=#<C:0x000001030bff28 @app=#<A:0x000001030bff50 @app=nil>>>
# return B object
stack.call 5
# will go B.call() method, if it calls @app.call, then will pass to C.call and so on.
# In here B.call is object method, not Proc's call().
- If we override
#eql?
method then must override#hash?
in class for Hash key comparison. Otherwise ruby will back to its default implementation for#hash
in Object class. - Each hash insertion generate hash values first, then compare whether two keys is duplicate(
#eql?
return true) repeatedly.eql?
andhash
are used when insertion to some hash-based data strucutre(hash, set). Set/Hash #include?
method will get hash values first, check whether itsobecjt_id
and hash value exist in the set/hash. It shortcircuit to return true if it can find the entry, o.w. calleql?
method to determine. Check theEQUAL(table,x,y)
function in source code to prove this.
// http://rxr.whitequark.org/mri/source/st.c#396
static inline st_index_t
find_packed_index(st_table *table, st_index_t hash_val, st_data_t key)
{
st_index_t i = 0;
while (i < table->real_entries &&
// if hash_val exists and key is exist in table(use ==), break loop.
(PHASH(table, i) != hash_val || !EQUAL(table, key, PKEY(table, i)))) {
i++;
}
return i;
}
#define collision_check 0
int
st_lookup(st_table *table, register st_data_t key, st_data_t *value)
{
st_index_t hash_val;
register st_table_entry *ptr;
hash_val = do_hash(key, table);
if (table->entries_packed) {
st_index_t i = find_packed_index(table, hash_val, key);
if (i < table->real_entries) {
if (value != 0) *value = PVAL(table, i);
return 1;
}
return 0;
}
ptr = find_entry(table, key, hash_val, hash_val % table->num_bins);
if (ptr == 0) {
return 0;
}
else {
if (value != 0) *value = ptr->record;
return 1;
}
}
// EQUAL(table,x,y) function
// http://rxr.whitequark.org/mri/source/st.c#085
#define EQUAL(table,x,y) ((x)==(y) || (*(table)->type->compare)((x),(y)) == 0)
This example shows what happens when you only override eql?
without overriding hash
:
# Only override `eql?`, Hash insertion will call Object#hash for key's hash value.
class B
def eql?(c)
true # All B instances are considered equal
end
end
{B.new => 5, B.new => 7}
# Two entries - objects have different hash values despite being eql?
# {#<B:0x007f8d1c807680>=>5, #<B:0x007f8d1c807658>=>7}
# Only override #hash
# Hash insertion will call Object#eql? for key's hash value comparison.
# But Object#eql? will also compare object_id by default implementation.
class T
def hash
p "T's hash"
0
end
end
{T.new => 5, T.new => 7}
# "T's hash"
# "T's hash"
# => {#<T:0x007f8d1d86f068>=>5, #<T:0x007f8d1d86f040>=>7}
class V
def hash
p "V's hash #{self.object_id}"
0
end
def eql?(c)
p "V's eql?"
self.hash == c.hash
end
end
k = V.new
# k.object_id 70122022664000
b = V.new
# b.object_id 70122022646420
# each hash insertion generate hash values first,
# then compare whether two key is duplicate.
{ k => 5}
# "V's hash 70122022664000"
# => {#<V:0x007f8d1c80ee80>=>5}
{ k => 5, b => 7}
# "V's hash 70122022664000"
# "V's hash 70122022646420"
# "V's eql?"
# "V's hash 70122022646420"
# "V's hash 70122022664000"
=> {#<V:0x007f8d1c80ee80>=>6}
# {#<V:0x007f8d1c8bb0e0>=>7}
{ k => 5, v => 7, V.new => 8}
# "V's hash 70122022664000"
# "V's hash 70122022646420"
# "V's eql?"
# "V's hash 70122022646420"
# "V's hash 70122022664000"
# "V's hash 70122014579860"
# "V's eql?"
# "V's hash 70122014579860"
# "V's hash 70122022664000"
# => {#<V:0x007f8d1c80ee80>=>8}
- If you redefine hash method with different hash value in later(duck-typing), then the hash value of entries in set will not change, which result inconsistent hash values.
- Caveat: Always make hash method returns consistent values in all time(don't do duck typing on
#hash
method), or you have to update the hash values for each entries.
class Point
def eql?(b)
p "eql? #{b}"
#super
end
def hash
p "hash #{self}"
0
end
end
v = Point.new
p = Point.new
require 'set'
s = Set.new
s << v
s.include? p
# "hash #<Point:0x007fd21a868c58>"
# "eql? #<Point:0x007fd21a879300>"
class Point
def hash
p "hash changed #{self}"
21345
end
end
# Now the hash value for v has changed.
# But in set s, its entry still keep previous hash value for instance v.
s.include? v
# "hash changed #<Point:0x007fd21a879300>"
# false
For more details, see [24] [25] [26].
- If we create an instance through the
Class
initialization, then it is instance's class will beClass
rather than its implemented class name, this implemented class will be super class of thatClass
. So the strcitly class name comparison may not work, e.g.p2.class == p1.class
.
class Point
def initialize(x, y)
@x, @y = x, y
end
end
p = Class.new(Point) do
def to_s
"#{@x}, #{@y}"
end
end
p1 = Point.new(2,1)
p.new(2,1)
# `<#<Class:0x007f85ab8f7c70>:0x007f85aa07f2d8 @x=2, @y=1>`
p2.class
# Class
# p2 cannot equal to p1, though implementation are the same.
p2.class == p1.class
# false
p2.superclass
# Point
p2.is_a? p1.class
# false, p2 is not an instance of Point.
Feature | Block | Proc | Lambda |
---|---|---|---|
Creation | { } or do..end |
Proc.new |
lambda or -> |
Arity checking | Flexible | Flexible | Strict |
return behavior |
Returns from enclosing method | Returns from enclosing method | Returns from lambda only |
Can be held? | No (passed to methods) | Yes | Yes |
Calling syntax | yield |
.call |
.call |
graph TD
A[Need callable code?] --> B{Store for later use?}
B -->|No| C[Use Block]
B -->|Yes| D{Need strict arity?}
D -->|Yes| E[Use Lambda]
D -->|No| F{Want method-like return?}
F -->|Yes| E
F -->|No| G[Use Proc]
style C fill:#bfb,stroke:#333,stroke-width:2px
style E fill:#fbf,stroke:#333,stroke-width:2px
style G fill:#ffb,stroke:#333,stroke-width:2px
Key Differences:
block === proc
are almost the same. Both accept var args,lambda
restricts args count must match its declaration.lambda
is like a method definition,proc
is more like a code snippet that resides in some other method definition or code block.proc
acts similar to a code snippet inside method scope. So when you callproc
in that scope andproc
hasreturn
keyword, then it will return from that outer scope and stop rest of code in that outer scope.lambda
just acts as a normal method, return from itslambda
scope and continue execution for outer scope.
This exercise demonstrates how to create class-like behavior using metaprogramming techniques instead of traditional class definitions.
Challenge: Implement the following class functionality without using class MaskedString
:
# Target implementation (do not use):
# class MaskedString < String
# def tr_vowel
# tr 'aeiou', '*'
# end
# def self.tr_vowel str
# str.tr 'aeiou', '*'
# end
# end
Hints: Use instance_eval
, class_eval
, define_method
, modules, extend
, include
.
# Create anonymous class and assign to constant for naming
ClassObject = Class.new(String)
# Module to provide class methods
module AddClassMethods
self.instance_eval do
define_method :tr_vowel do |str|
str.tr 'aeiou', '*'
end
end
end
# Add instance methods using class_eval
ClassObject.class_eval do
define_method :tr_vowel do
tr 'aeiou', '*'
end
# Additional instance method
def lets
puts 'lets'
end
# Additional singleton method
def self.lets
puts 'self.lets'
end
end
# Add class methods using instance_eval and extend
ClassObject.instance_eval do
extend AddClassMethods
define_singleton_method :as do
puts "as"
end
# Another singleton method
# Note: def less === def self.less when inside instance_eval
def less
puts 'less'
end
end
# Test the implementation
puts ClassObject.tr_vowel("America") # Class method
puts ClassObject.new("ByMyWill").tr_vowel # Instance method
This exercise demonstrates several key metaprogramming concepts:
- Dynamic class creation with
Class.new
- Method definition using
define_method
vsdef
- Context switching with
class_eval
andinstance_eval
- Module extension patterns
- Singleton method creation techniques
class Model
def self.attr_accessor_with_history(*attrs)
attrs.each do |attr|
# Create instance variable to store history
define_method("#{attr}_history") do
instance_variable_get("@#{attr}_history") || []
end
# Getter method
define_method(attr) do
instance_variable_get("@#{attr}")
end
# Setter method with history tracking
define_method("#{attr}=") do |value|
history = instance_variable_get("@#{attr}_history") || []
old_value = instance_variable_get("@#{attr}")
history << { from: old_value, to: value, at: Time.now }
instance_variable_set("@#{attr}_history", history)
instance_variable_set("@#{attr}", value)
end
end
end
end
# Usage:
class User < Model
attr_accessor_with_history :name, :email
end
user = User.new
user.name = "Alice"
user.name = "Alice Smith"
puts user.name_history
# => [{:from=>nil, :to=>"Alice", :at=>...}, {:from=>"Alice", :to=>"Alice Smith", :at=>...}]
class Delegator
def self.delegate(*methods, to:)
methods.each do |method|
define_method(method) do |*args, &block|
target = instance_variable_get("@#{to}")
target.public_send(method, *args, &block)
end
end
end
end
class Car < Delegator
delegate :start, :stop, :accelerate, to: :engine
def initialize
@engine = Engine.new
end
end
class Engine
def start; puts "Engine starting..."; end
def stop; puts "Engine stopping..."; end
def accelerate; puts "Engine accelerating..."; end
end
# Usage:
car = Car.new
car.start # => "Engine starting..."
car.accelerate # => "Engine accelerating..."
class Config
def self.define_config(&block)
@config_methods = []
# Capture method definitions
singleton_class.define_method(:method_added) do |method_name|
@config_methods << method_name unless method_name == :method_added
end
# Evaluate the configuration block
class_eval(&block)
# Create accessors for all defined config methods
@config_methods.each do |method|
define_method(method) do
self.class.public_send(method)
end
end
end
end
# Usage:
class AppConfig < Config
define_config do
def self.database_url
ENV['DATABASE_URL'] || 'localhost:5432'
end
def self.cache_enabled
ENV['CACHE_ENABLED'] == 'true'
end
def self.log_level
ENV['LOG_LEVEL'] || 'info'
end
end
end
config = AppConfig.new
puts config.database_url # => "localhost:5432"
puts config.cache_enabled # => false
class QueryBuilder
def initialize(table)
@table = table
@conditions = []
@orders = []
@limit_value = nil
end
def self.create_condition_method(method_name, operator)
define_method(method_name) do |field, value|
@conditions << "#{field} #{operator} '#{value}'"
self # Return self for chaining
end
end
# Dynamically create condition methods
create_condition_method :where_equals, '='
create_condition_method :where_not, '!='
create_condition_method :where_greater, '>'
create_condition_method :where_less, '<'
def order_by(field, direction = 'ASC')
@orders << "#{field} #{direction}"
self
end
def limit(count)
@limit_value = count
self
end
def to_sql
sql = "SELECT * FROM #{@table}"
sql += " WHERE #{@conditions.join(' AND ')}" unless @conditions.empty?
sql += " ORDER BY #{@orders.join(', ')}" unless @orders.empty?
sql += " LIMIT #{@limit_value}" if @limit_value
sql
end
end
# Usage:
query = QueryBuilder.new('users')
.where_equals('active', true)
.where_greater('age', 18)
.order_by('created_at', 'DESC')
.limit(10)
.to_sql
puts query
# => "SELECT * FROM users WHERE active = 'true' AND age > '18' ORDER BY created_at DESC LIMIT 10"
module PluginSystem
def self.included(base)
base.extend(ClassMethods)
base.instance_variable_set(:@plugins, [])
end
module ClassMethods
def plugin(plugin_module)
@plugins << plugin_module
include plugin_module
# Call plugin's setup hook if it exists
if plugin_module.respond_to?(:setup)
plugin_module.setup(self)
end
end
def plugins
@plugins.dup
end
end
end
# Example plugins
module TimestampPlugin
def self.setup(klass)
klass.class_eval do
def initialize(*args)
super
@created_at = Time.now
end
attr_reader :created_at
end
end
end
module ValidatorPlugin
def self.setup(klass)
klass.extend(ValidatorMethods)
end
module ValidatorMethods
def validates(field, &block)
define_method("validate_#{field}") do
value = instance_variable_get("@#{field}")
block.call(value)
end
end
end
end
# Usage:
class User
include PluginSystem
plugin TimestampPlugin
plugin ValidatorPlugin
validates :email do |email|
email.include?('@')
end
def initialize(email)
@email = email
super()
end
attr_accessor :email
end
user = User.new("[email protected]")
puts user.created_at # => 2024-01-01 12:00:00 +0000
puts user.validate_email # => true
Ruby 3.x introduced pattern matching which can be useful in metaprogramming scenarios:
# Pattern matching with method definitions
class APIResponse
def self.handle_response(response)
case response
in { status: 200, data: data }
define_method(:success_data) { data }
in { status: 404 }
define_method(:not_found?) { true }
in { status: code, error: message }
define_method(:error_info) { "#{code}: #{message}" }
else
define_method(:unknown_response) { true }
end
end
end
# Usage examples:
response_obj = APIResponse.new
# Handle success response
APIResponse.handle_response({ status: 200, data: "user_info" })
puts response_obj.success_data # => "user_info"
# Handle error response
APIResponse.handle_response({ status: 500, error: "Internal Server Error" })
puts response_obj.error_info # => "500: Internal Server Error"
Modern Ruby supports keyword arguments in dynamically defined methods:
class ModernMetaprogramming
def self.create_method_with_keywords(name)
define_method(name) do |value:, optional: nil, **kwargs|
puts "Value: #{value}"
puts "Optional: #{optional}" if optional
puts "Extra args: #{kwargs}" unless kwargs.empty?
end
end
end
# Usage example:
ModernMetaprogramming.create_method_with_keywords(:process)
obj = ModernMetaprogramming.new
obj.process(value: "required", extra_param: "additional")
# Output:
# Value: required
# Extra args: {:extra_param=>"additional"}
obj.process(value: "test", optional: "provided", debug: true)
# Output:
# Value: test
# Optional: provided
# Extra args: {:debug=>true}
Ruby 3.x supports endless method definitions, useful in metaprogramming:
class CompactAccessors
def self.create_accessor(attr_name)
define_method(attr_name) = instance_variable_get("@#{attr_name}")
define_method("#{attr_name}=") = ->(value) { instance_variable_set("@#{attr_name}", value) }
end
end
# Usage example:
CompactAccessors.create_accessor(:name)
obj = CompactAccessors.new
obj.name = "Ruby"
puts obj.name # => "Ruby"
Ruby 3.x hash improvements work well with metaprogramming:
class HashMetaprogramming
def self.create_methods(**methods)
methods.each do |name, implementation|
case implementation
in Proc => proc_obj
define_method name, &proc_obj
in String => code
class_eval "def #{name}; #{code}; end"
else
puts "Unknown implementation type"
end
end
end
end
# Usage example:
HashMetaprogramming.create_methods(
greet: proc { puts "Hello from #{self.class}!" },
calculate: "2 + 2"
)
obj = HashMetaprogramming.new
obj.greet # => "Hello from HashMetaprogramming!"
obj.calculate # => 4
[1] Calling super from module's method - Stack Overflow discussion on super behavior in modules
[2] Delegation Pattern in Ruby - Avdi Grimm's article on delegation vs decoration
[3] Difference between proc, lambda, and block - Detailed comparison of Ruby's callable objects
[4] Ruby's Three Implicit Contexts - Ruby has three implicit contexts that affect method resolution: the current self
(method receiver), the current class/module for constant lookup, and the current binding for local variables. Understanding these contexts is essential for metaprogramming as they determine how Ruby resolves method calls, constant references, and variable access in dynamic code evaluation.
[5] DSL Patterns with instance_eval
- When building Domain Specific Languages (DSL) in Ruby, instance_eval
is commonly used to change the evaluation context. This allows DSL methods to be called directly without a receiver, creating cleaner syntax. However, care must be taken with delegation patterns to ensure the DSL can still access methods from the original context when needed.
[6] How instance_eval
Affects self
- The instance_eval
method changes the value of self
to the receiver object during block execution, but it also splits the definition context from the execution context. Methods defined inside instance_eval
are added to the receiver's singleton class, while the block can still access local variables from the outer scope. This dual behavior makes it powerful for metaprogramming but can be confusing.
[7] Ruby Bindings and Scope - A Binding
object encapsulates the execution context at a particular point in the code, including local variables, instance variables, self
, and the current class/module. Bindings can be captured using the binding
method and passed to eval
to execute code in that captured context. This is fundamental to Ruby's metaprogramming capabilities as it allows code to be evaluated with access to variables and context from different scopes.
[8] Ruby Object Model - Comprehensive guide to Ruby's object model
[9] Ruby Mixins and Modules - Mixins in Ruby are implemented through modules and provide a way to share code between classes without using inheritance. The include
keyword adds module methods as instance methods, while extend
adds them as class methods. The prepend
keyword (Ruby 2.0+) inserts the module before the class in the method lookup chain, enabling more sophisticated method wrapping patterns.
[10] Module Namespaces and Constant Lookup - Ruby performs constant lookup using lexical scoping rules. When a constant is referenced, Ruby first searches the current lexical scope (nesting), then the inheritance chain, and finally the top-level constants. Modules provide namespacing to organize constants and prevent naming conflicts. The ::
operator can force top-level constant lookup or specify a particular namespace.
[11] Include, Extend, and Module Hooks - Ruby provides several hooks that are called when modules are included or extended: included
, extended
, prepended
, and append_features
. These hooks allow modules to perform setup operations, add class methods when included, or modify the including class's behavior. This is commonly used in gems like ActiveSupport::Concern to provide both instance and class methods when a module is included.
[12] Detailed Constant Lookup Mechanics - Ruby's constant lookup follows a specific algorithm: first search the lexical scope (nesting), then the inheritance hierarchy, and finally Object. The lookup is cached for performance. Constants can be autoloaded using const_missing
hook. Understanding this mechanism is crucial for metaprogramming, especially when dynamically defining constants or working with namespaced code.
[13] Ruby Style Guide - Community Ruby style guide
[14] Building DSLs with yield
and instance_eval
- Ruby DSLs can be built using either yield
(block-based) or instance_eval
(context-switching). yield
keeps the original context and explicitly passes values, while instance_eval
changes the execution context. Each approach has trade-offs: yield
is more explicit and performance-friendly, while instance_eval
provides cleaner syntax but can complicate scope access. Choose based on your DSL's complexity and performance requirements.
[15] Ruby Method Calls and Blocks - Wikibooks guide
[16] Method missing - Official documentation
[17] Ruby's Core Metaprogramming Methods - The trinity of define_method
, method_missing
, and instance_eval
forms the foundation of Ruby metaprogramming. define_method
creates methods dynamically with proper closure access, method_missing
handles unknown method calls by providing custom behavior, and instance_eval
changes execution context. These methods work together to enable powerful dynamic programming patterns like DSLs, proxies, and decorators.
[18] Fiber and Enumerator Patterns - Fibers can be used to implement custom Enumerator behavior by creating pausable, resumable execution contexts. The Enumerator::Yielder
uses fibers internally to implement lazy evaluation. You can create similar patterns by using Fiber.new
and Fiber.yield
to pause execution and return values, then resume
to continue from where it left off. This is useful for implementing custom iteration patterns and lazy data processing.
[19] Fiber Fundamentals - Fibers are cooperative multitasking primitives in Ruby that allow you to pause and resume execution at specific points. Unlike threads, fibers don't run concurrently and must explicitly yield control. They're created with Fiber.new
, paused with Fiber.yield
, and resumed with #resume
. Fibers are useful for implementing iterators, generators, and coroutine-style programming patterns where you need fine-grained control over execution flow.
[20] Confident Ruby - Book on confident Ruby coding
[21] Ruby Conversion Method Patterns - Ruby uses two types of conversion methods: explicit (like to_s
, to_i
, to_a
) and implicit (like to_str
, to_int
, to_ary
). Explicit methods are called directly and should always work, while implicit methods are called automatically by Ruby in certain contexts and should only be implemented when the object can truly act as the target type. For example, implement to_str
only if your object can be used anywhere a String is expected.
[22] Ruby Method Naming Rules - Ruby method names can contain letters, numbers, and underscores, but cannot start with numbers. They can end with ?
(predicate methods), !
(dangerous/mutating methods), or =
(setter methods). Ruby also supports operator overloading for methods like +
, -
, []
, []=
, <<
, etc. Method names are case-sensitive and by convention use snake_case. Special methods like initialize
, method_missing
, and respond_to?
have specific meanings in Ruby's object system.
[23] Ruby Conversion Protocols - Ruby's conversion protocols allow objects to define how they should be converted to other types. The protocol includes explicit methods (to_s
, to_i
, to_f
, to_a
, to_h
) which should always return the expected type, and implicit methods (to_str
, to_int
, to_ary
, to_hash
) which indicate the object can substitute for that type. Ruby core classes use these protocols extensively - for example, string interpolation uses to_s
, while array concatenation uses to_ary
.
[24] Object#hash - Official hash method documentation
[25] Ruby Equality (Mandarin) - Equality guide in Chinese
[26] st_lookup source code - MRI hash table implementation
[27] Metaprogramming in Ruby: It's All About the Self - Yehuda Katz's foundational article on Ruby metaprogramming
- Understanding Ruby Blocks, Procs and Lambdas
- Understanding Rack Builder
- Mixins in Ruby
- Ruby Metaprogramming
- Metaprogramming in Ruby: It's All About the Self - Yehuda Katz
- Metaprogramming Ruby Presentation - InfoQ
- Ruby Object Model - Comprehensive guide
- Seeing Metaclasses Clearly
- Book of Ruby - No Starch Press
- Programming Ruby - Pragmatic Programmers
- Ruby Metaprogramming Learning
- Ruby Method Calls and Blocks - Wikibooks
WOW!