Planted on 28 Jan 2024
Last tended on 06 Aug 2024
In Ruby, everything is an object, and every object has an anonymous class, which defines the methods the object can respond to. This anonymous class is called the singleton class.
When calling a method on an object, Ruby will perform the method lookup by first checking on the object’s singleton class, before traversing the rest of the method chain.
The class methods are just instance methods on its singleton class.
class Animal
def self.all; end
end
Animal.singleton_methods
#=> [:all]
Animal.singleton_class.instance_method(:all)
#=> #<UnboundMethod: #<Class:Animal>#all()>
Ruby always holds a reference to the current class, which is called “default definee” by Yugui of the Ruby core team. Thus, if you define a method without giving an explicit receiver, the current class will have the method as an instance method.
class Animal
def weight; end
end
Animal.instance_method(:weight)
#=> #<UnboundMethod: Animal#weight()>
If you give a receiver to a method definition, the method will be added into the singleton class of the receiver.
word = "hello"
def word.spell; end
word.singleton_class.instance_method(:spell)
#=> #<UnboundMethod: #<Class:#<String:0x00007fcda4958890>>#spell()>
The class
syntax changes both self
and the current class to the class which is being defined. However, method definition doesn’t.
class Foobar
def foo
def bar; end
def self.baz; end
end
end
f = Foobar.new
f.foo
Foobar.instance_method(:foo)
#=> #<UnboundMethod: Foobar#foo()>
Foobar.instance_method(:bar)
#=> #<UnboundMethod: Foobar#bar()>
Foobar.singleton_methods
#=> []
f.singleton_methods
#=> [:baz]
In Ruby, instance_eval
and class_eval
provide that provide the ability to modify a class or an object. The names are very similar, and their behavior is counterintuitive.
ClassName#instance_eval
to define a class method (one associated with the class object but not visible to instances).ClassName#class_eval
to define an instance method (one that applies to all of the instances of ClassName
).To understand why this is true, let’s see what happens when we call the eval methods.
The instance_eval
changes self
to the receiver, the current class to its singleton class.
class Animal
def weight; end
end
Animal.instance_eval do
def all; end
end
Animal.instance_method(:all)
#=> NameError (undefined method `all' for class `Animal')
Animal.singleton_class.instance_method(:all)
#=> #<UnboundMethod: #<Class:Animal>#all()>
The class_eval
changes both self
and the current class to the receiver.
class Animal; end
Animal.class_eval do
def weight; 1 end
end
Animal.instance_method(:weight)
#=> #<UnboundMethod: Animal#weight()>
Animal.new.weight
#=> 1
Animal.weight
#=> NoMethodError (undefined method `weight' for Animal:Class)
Ruby supports a concept known as “Open classes”, which opens the object’s singleton class. This is equivalent to giving a receiver a method definition.
class Example
class << self
def foo; end
end
def self.bar; end
end
class << Example
def baz; end
end
Example.singleton_methods
#=> [:foo, :bar, :baz]
Let’s go through some examples:
class Foobar; end
Foobar.instance_eval do
self #=> Foobar
def method_by_instance_eval; end
end
Foobar.class_eval do
self #=> Foobar
def method_by_class_eval; end
end
class << Foobar
self #=> #<Class:Foobar>
def method_by_open_class; end
end
Foobar.instance_methods
#=> [:method_by_class_eval, ...]
Foobar.singleton_methods
#=> [:method_by_instance_eval, :method_by_open_class]
The above context changes can be summarized in the following table:
self | current class | |
---|---|---|
class_eval | the receiver | the receiver |
instance_eval | the receiver | singleton class of the receiver |
class « receiver | singleton class of the receiver | singleton class of the receiver |
In Ruby,
instance_eval
changes self
to the receiver, the current class to its singleton class;class_eval
changes both self
and the current class to the receiver;