Introduction
In Ruby, methods are the primary way objects communicate. Understanding the different ways to define methods โ and the distinction between instance methods, class methods, and singleton methods โ is fundamental to writing idiomatic Ruby.
Three Ways to Define Instance Methods
Way 1: Standard def in a Class Body
The most common approach โ defines a method available to all instances:
class Foo
def baz
"instance method on #{self.class}"
end
end
Foo.new.baz # => "instance method on Foo"
Way 2: attr_accessor (Generated Methods)
attr_accessor, attr_reader, and attr_writer generate getter/setter instance methods:
class Foo
attr_accessor :baz
end
foo = Foo.new
foo.baz = "instance method"
puts foo.baz # => "instance method"
These are real methods โ you can verify:
Foo.instance_methods(false) # => [:baz, :baz=]
Way 3: Singleton Method on a Specific Object
A singleton method is defined on a single object, not its class. Only that object has the method:
class Foo; end
foo = Foo.new
bar = Foo.new
def foo.greet
"Hello from a singleton method"
end
foo.greet # => "Hello from a singleton method"
bar.greet # => NoMethodError: undefined method 'greet' for #<Foo:...>
The general syntax for singleton methods:
def object.method_name
# body
end
Where object can be:
- An object reference (
foo) - A constant class name (
String,MyClass) self(inside a class body โ this is how class methods work)
Method Definition Syntax Summary
# Instance method (on all instances)
class Dog
def speak
"Woof!"
end
end
# Class method (singleton method on the class object)
class Dog
def self.species
"Canis lupus familiaris"
end
end
# Singleton method on a specific instance
rex = Dog.new
def rex.fetch(item)
"#{item} fetched!"
end
Dog.new.speak # => "Woof!"
Dog.species # => "Canis lupus familiaris"
rex.fetch("ball") # => "ball fetched!"
Dog.new.fetch("ball") # => NoMethodError
Inspecting Methods
class Calculator
def add(a, b); a + b; end
def subtract(a, b); a - b; end
private
def internal_helper; end
end
calc = Calculator.new
# Instance methods defined directly on the class (not inherited)
Calculator.instance_methods(false)
# => [:add, :subtract]
# All public instance methods (including inherited)
calc.public_methods(false)
# => [:add, :subtract]
# Private methods
Calculator.private_instance_methods(false)
# => [:internal_helper]
# Check if an object responds to a method
calc.respond_to?(:add) # => true
calc.respond_to?(:internal_helper) # => false (private)
calc.respond_to?(:internal_helper, true) # => true (include private)
Method Objects
Methods are first-class objects in Ruby. You can get a reference to a method and call it later:
class Greeter
def hello(name)
"Hello, #{name}!"
end
end
g = Greeter.new
# Get a bound method object
m = g.method(:hello)
puts m.class # => Method
puts m.call("Alice") # => "Hello, Alice!"
# Pass as a block
["Alice", "Bob", "Charlie"].map(&g.method(:hello))
# => ["Hello, Alice!", "Hello, Bob!", "Hello, Charlie!"]
Unbound Methods
An UnboundMethod is a method detached from its object. You can bind it to another object of the same class:
class Animal
def breathe
"#{self.class} is breathing"
end
end
unbound = Animal.instance_method(:breathe)
puts unbound.class # => UnboundMethod
dog = Animal.new
bound = unbound.bind(dog)
puts bound.call # => "Animal is breathing"
Dynamic Method Definition
define_method
define_method creates instance methods dynamically โ useful for generating methods from data:
class Report
METRICS = [:revenue, :cost, :profit, :margin]
METRICS.each do |metric|
define_method(metric) do
instance_variable_get("@#{metric}") || 0
end
define_method("#{metric}=") do |value|
instance_variable_set("@#{metric}", value)
end
end
end
r = Report.new
r.revenue = 100_000
r.cost = 60_000
r.profit = r.revenue - r.cost
puts r.profit # => 40000
method_defined? and define_method with Closures
define_method captures the surrounding scope (closure), unlike def:
class EventEmitter
EVENTS = [:click, :hover, :focus, :blur]
EVENTS.each do |event|
define_method("on_#{event}") do |&block|
@handlers ||= {}
@handlers[event] = block
end
define_method("trigger_#{event}") do
@handlers ||= {}
@handlers[event]&.call
end
end
end
emitter = EventEmitter.new
emitter.on_click { puts "Clicked!" }
emitter.trigger_click # => "Clicked!"
Method Visibility
Methods can be public, private, or protected:
class BankAccount
def initialize(balance)
@balance = balance
end
# Public โ callable from anywhere
def deposit(amount)
validate_amount!(amount)
@balance += amount
end
def balance
@balance
end
protected
# Protected โ callable from same class or subclasses
def transfer_to(other, amount)
@balance -= amount
other.receive(amount)
end
def receive(amount)
@balance += amount
end
private
# Private โ only callable within the class, no explicit receiver
def validate_amount!(amount)
raise ArgumentError, "Amount must be positive" unless amount > 0
end
end
Method Aliasing
Create alternative names for existing methods:
class String
alias_method :contains?, :include?
end
"hello world".contains?("world") # => true
Removing Methods
class MyClass
def temporary_method
"I'll be removed"
end
# remove_method: removes from this class, superclass version still accessible
remove_method :temporary_method
# undef_method: completely blocks the method, even from superclass
# undef_method :temporary_method
end
Practical: Method Hooks
Ruby provides hooks that fire when methods are added or removed:
class Observable
def self.method_added(method_name)
return if method_name == :initialize
puts "Method added: #{method_name}"
end
def greet; "hello"; end # => Method added: greet
def farewell; "bye"; end # => Method added: farewell
end
Summary
| Pattern | Syntax | Scope |
|---|---|---|
| Instance method | def method_name inside class |
All instances |
| Class method | def self.method_name |
The class object |
| Singleton method | def object.method_name |
One specific object |
| Generated method | attr_accessor, define_method |
All instances |
| Method object | object.method(:name) |
Callable reference |
Resources
- Ruby Docs: Method
- Ruby Docs: UnboundMethod
- Ruby Docs: Module#define_method
- Metaprogramming Ruby โ Paolo Perrotta
Comments