Introduction
Ruby is a purely object-oriented language โ everything is an object, including numbers, strings, booleans, and even classes themselves. This article explores Ruby’s object model: what objects and classes really are, how the method lookup chain works, and how this design enables Ruby’s powerful metaprogramming capabilities.
This is the first part of a series. Part 2 will cover modules, mixins, and the full inheritance hierarchy.
What is an Object in Ruby?
In Ruby, an object is essentially two things:
- A set of instance variables (its state)
- A reference to its class (which holds its methods)
class Dog
def initialize(name, breed)
@name = name # instance variable
@breed = breed # instance variable
end
def speak
"#{@name} says: Woof!"
end
end
rex = Dog.new("Rex", "Labrador")
rex is an object. It holds @name and @breed as its state. The method speak is not stored in rex โ it’s stored in the Dog class. When you call rex.speak, Ruby looks up the method in Dog.
# Inspect an object's instance variables
rex.instance_variables # => [:@name, :@breed]
# Inspect an object's class
rex.class # => Dog
# Every object has a unique ID
rex.object_id # => some integer
What is a Class in Ruby?
Here’s the key insight: a class is also an object. Specifically, a class is an instance of the Class class.
Dog.class # => Class
Class.class # => Class (Class is an instance of itself)
Dog.is_a?(Object) # => true
Dog.is_a?(Class) # => true
Dog.is_a?(Module) # => true (Class inherits from Module)
A class object holds:
- A table of instance methods (shared by all instances)
- A reference to its superclass
Dog.instance_methods(false) # => [:speak] (methods defined directly on Dog)
Dog.superclass # => Object
Object.superclass # => BasicObject
BasicObject.superclass # => nil (top of the hierarchy)
The Method Lookup Chain
When you call a method on an object, Ruby searches for it in a specific order โ the ancestor chain:
class Animal
def breathe
"breathing..."
end
end
class Dog < Animal
def speak
"Woof!"
end
end
rex = Dog.new
rex.speak # found in Dog
rex.breathe # not in Dog โ found in Animal
rex.class # not in Dog or Animal โ found in Object
rex.freeze # found in Object (via Kernel module)
You can inspect the full lookup chain:
Dog.ancestors
# => [Dog, Animal, Object, Kernel, BasicObject]
Ruby walks this list left to right until it finds the method. If it reaches BasicObject without finding it, it calls method_missing.
Classes are Objects: Practical Implications
Because classes are objects, you can:
Store a class in a variable
klass = Dog
obj = klass.new("Buddy", "Poodle")
obj.speak # => "Buddy says: Woof!"
Pass a class as an argument
def create_animal(klass, name)
klass.new(name, "Unknown")
end
create_animal(Dog, "Max").speak # => "Max says: Woof!"
Define methods on a class dynamically
Dog.define_method(:fetch) do |item|
"#{@name} fetches the #{item}!"
end
rex.fetch("ball") # => "Rex fetches the ball!"
Class Methods vs Instance Methods
Instance methods are defined with def inside the class body. Class methods are defined with def self.method_name or inside class << self:
class Dog
# Instance method โ called on an instance
def speak
"#{@name} says: Woof!"
end
# Class method โ called on the class itself
def self.species
"Canis lupus familiaris"
end
# Alternative: class << self block
class << self
def create_puppy(name)
new(name, "Mixed")
end
end
end
rex = Dog.new("Rex", "Lab")
rex.speak # => "Rex says: Woof!"
Dog.species # => "Canis lupus familiaris"
Dog.create_puppy("Spot").speak # => "Spot says: Woof!"
Instance Variables vs Class Variables
class Counter
# Class variable โ shared across ALL instances and subclasses
@@total_count = 0
# Class instance variable โ belongs to the class object itself
@default_step = 1
def initialize
@@total_count += 1
@count = 0 # instance variable โ unique per instance
end
def increment(step = self.class.default_step)
@count += step
end
def count
@count
end
def self.total_count
@@total_count
end
def self.default_step
@default_step
end
end
a = Counter.new
b = Counter.new
a.increment
a.increment
b.increment(5)
puts a.count # => 2
puts b.count # => 5
puts Counter.total_count # => 2 (both instances share @@total_count)
Key difference:
@@class_variableis shared across the class and all subclasses โ behaves like a global variable within the hierarchy@class_instance_variablebelongs to the class object itself and is not shared with subclasses
class Animal
@@count = 0
@type = "generic"
def self.count; @@count; end
def self.type; @type; end
end
class Dog < Animal
@@count = 10 # modifies the SAME @@count as Animal
@type = "dog" # separate @type โ does not affect Animal
end
puts Animal.count # => 10 (changed by Dog!)
puts Animal.type # => "generic" (unchanged)
puts Dog.type # => "dog"
The Singleton Class
Every object in Ruby has a hidden singleton class (also called eigenclass or metaclass) that can hold methods defined only for that specific object:
rex = Dog.new("Rex", "Lab")
# Define a method only on rex, not on all Dogs
def rex.roll_over
"#{@name} rolls over!"
end
rex.roll_over # => "Rex rolls over!"
Dog.new("Spot", "Lab").roll_over # => NoMethodError
Class methods are actually instance methods of the class’s singleton class:
# These are equivalent:
def Dog.species; "Canis lupus familiaris"; end
class Dog
class << self # opens Dog's singleton class
def species; "Canis lupus familiaris"; end
end
end
Comparing Ruby’s Object Model to Python and Go
Python
Python also has a rich object model, but with some differences:
# Python: classes are objects too
class Dog:
def speak(self):
return "Woof!"
print(type(Dog)) # => <class 'type'>
print(Dog.__mro__) # Method Resolution Order (like Ruby's ancestors)
Python’s MRO uses the C3 linearization algorithm, similar to Ruby’s ancestor chain.
Go
Go takes a completely different approach โ no classes, no inheritance:
// Go: structs + interfaces, no class hierarchy
type Dog struct {
Name string
}
func (d Dog) Speak() string {
return d.Name + " says: Woof!"
}
// Interfaces are satisfied implicitly
type Speaker interface {
Speak() string
}
Go’s simplicity avoids the complexity of deep inheritance hierarchies, at the cost of less flexibility.
Key Takeaways
- In Ruby, everything is an object โ including classes, modules, and nil
- An object is a set of instance variables + a reference to its class
- A class is an object (instance of
Class) that holds instance methods and a superclass reference - Method lookup follows the ancestor chain: class โ superclass โ modules โ Object โ Kernel โ BasicObject
- Every object has a singleton class for object-specific methods
- Class methods are instance methods of the class’s singleton class
@@class_variablesare shared across the hierarchy;@class_instance_variablesare per-class
Resources
- Ruby Metaprogramming (Paolo Perrotta)
- Ruby Object Model โ Ruby Docs
- Understanding Ruby’s Object Model
- Ruby Under a Microscope (Pat Shaughnessy)
Comments