Ruby objects have methods called instance_eval
and class_eval
. They both execute a block with self
referencing the object.
class C; end
C.instance_eval { puts "instance_eval: self = #{self}" }
# prints "instance_eval: self = C"
C.class_eval { puts "class_eval: self = #{self}" }
# prints "class_eval: self = C"
For a long time I thought these methods were basically synonymous, that for some reason I was simply supposed to use class_eval
for classes (and module_eval
for modules) and instance_eval
for everything else. Just accept it and don’t ask questions, I told myself.
But there is a difference, one that points to an important but seldom understood aspect of Ruby. In this article, we’ll explore the distinction and what it means for our Ruby code.
The “current object”
Let’s start with what many Ruby programmers already know.
Like many object-oriented languages, Ruby has the notion of a “current object”. This is the object that receives method calls by default, and it is the object that owns any instance variables you reference. Ruby sets the current object inside every method call, so the method can access the object’s instance variables and easily call other methods of the object.
class Greeter
def initialize(name)
@name = name
end
def greeting
# Reference "@name" from the current object
"Hello, #{@name}!"
end
def print_greeting
# Call the "greeting" method in the current object
puts greeting
end
end
You can get a reference to the current object using the self
keyword, but for the most part, it’s used implicitly when resolving methods or instance variables.
There is always a current object, even when you’re not in a method. Within a class definition, the current object is the class. And Ruby even provides a “main” object that is the current object at the top level of a Ruby script.
class Greeter
puts self
# Prints "Greeter"
end
puts self
# Prints "main"
Changing the current object with instance_eval
You can also change the current object using the instance_eval
method (or the closely related instance_exec
).
greet = Greeter.new("world")
# Change the object context within the given block.
greet.instance_eval do
# Self now references the greeter object
assert self == greet
# You can call its methods without a receiver
print_greeting
# You can even access its instance variables
puts @name
end
The instance_eval
method is commonly used for building domain-specific-languages (DSLs) because it lets us control how methods are looked up. When you write Rails routes, for example, Rails is using instance_eval
to give you a simple syntax for declaring paths and resources.
The current object has a strong effect on looking up method names, but what about defining a method? This is where the story gets a bit more complicated.
Defining methods
The def
keyword is typically used to define a new method. Normally, def
appears within a class or module definition, so it’s clear where the method is defined. But Ruby is very flexible. You can put a def
almost anywhere: outside any class, in a block, even within another method.
What do you think happens when a method is defined inside another method?
class Greeter
def greeting
def dismissal
"Bye, world!"
end
"Hello, world!"
end
end
No, Ruby doesn’t have any weird notion of “nested” methods. All that’s happening here is that defining the dismissal
method is part of the functionality of the greeting
method. dismissal
is defined when you call greeting
.
So what class is dismissal
defined on? One might guess that it’s also defined on the Greeter
class, and in this case you’d be right. But why is that the case? It may seem “obvious” or “intuitive,” but it’s very important to understand what’s actually going on, because things won’t always be obvious. Take this example:
class Greeter
def greeting
"Hello, world!"
end
end
Greeter.instance_eval do
def dismissal
"Bye, world!"
end
end
We’re using instance_eval
to set the object context to the Greeter
class when we define the method. So where is dismissal
defined? You might guess, on the Greeter
class. But you’d be wrong. It gets defined on the Object
class.
So what’s really going on? What actually governs on which class a method is defined?
The “current class”
The answer is that “self” isn’t the only piece of context that Ruby maintains. The “current object” governs lookup of method names (and instance variables), but method definitions are governed by a separate piece of context that I’ll call the “current class”.
Note: other terms have been used elsewhere for the “current class”. For example, Yugui used the term “default definee” when she wrote about the Ruby contexts in an earlier article.
When you define a class using the class
keyword, it creates a class and sets the current class context so that methods are attached to it. Similarly, when a method is called, the current class context is set to self
’s class.
class Greeter
# Current class is set to Greeter.
# This ensures the greeting method is defined on Greeter.
def greeting
# Current class is set to self's class, which is Greeter.
# This ensures the dismissal method is also defind on Greeter.
def dismissal
"Bye, world!"
end
"Hello, world!"
end
end
However, importantly, instance_eval
sets the current object but not the current class.
# Current class is Object at the top level of a Ruby file
Greeter.instance_eval do
# The instance_eval method sets self but not the current class,
# so the current class is still Object here.
def dismissal
"Bye, world!"
end
end
And this is where class_eval
is different. Whereas instance_eval
sets only the current object, class_eval
sets both the current object and current class.
Greeter.class_eval do
# The current class is now Greeter.
def dismissal
"Bye, world!"
end
end
So we’ve seen that Ruby maintains separate, independent class and object contexts. And that brings up an interesting question: Why?
Why are “current class” and “current object” distinct?
Why maintain two separate pieces of state? Isn’t that needlessly complicated?
It turns out there’s a good reason for it, and it has to do with Ruby’s goal of making programming intuitive. Let’s look again at the two places we’ve seen method definitions.
When you use a class
declaration, Ruby sets both the current object and the current class to the same thing, the class. This lets you both define methods and call class methods such as attr_reader
, include
, and even private
.
class Greeter
# current class == self
# This means you can define methods on the class
def greeting
"Hello, world!"
end
# And you can also call class methods
attr_reader :language
end
But when you define a method in another method, the current object and the current class are not the same. An arbitrary object doesn’t have methods; only classes do. So the current class is set to the class of the object.
class Greeter
def greeting
# current class != self
# current class == self.class
def dismissal
"Bye, world!"
end
"Hello, world!"
end
end
In order to support reasonable behavior in both of these two cases, Ruby needs the flexibility to be able to set up the two values, current object and current class, differently.
Why does this matter?
So Ruby has more context than just self
. Why does it matter?
Well, it might help you avoid some bugs, and it’s always useful to understand the details of the language you are using. But it’s particularly important when you are designing interfaces for other developers to use, especially if you’re designing a domain-specific language.
Ruby is a flexible language, and Ruby programmers expect to be able to use that flexibility, calling code, writing helper methods, and generally doing things you might not expect. If the Rails router had set the current class to some strange value and made it difficult for users to write helper methods, Rails would have been much more brittle and difficult to use, and ultimately less successful.
So it’s important for Ruby library writers to make sure you choose the correct method when using class_eval
or instance_eval
. And in general, library designers need to pay attention to the current class in order to avoid unexpected behavior.
If you’re interested in learning some tips for designing interfaces that pay attention to these issues, see my talk “Ruby Ate My DSL!” from RubyConf 2019.
Investigating further
It turns out, the rabbit hole goes even deeper. A third independent piece of Ruby context governs constants, where they are defined and how they are looked up. This piece of context, known internally as the cref
, represents the lexical nesting of classes and modules, basically what you get from calling Module.nesting
. However, unlike the current object and current class, the cref can’t be changed programmatically, at least not without dropping into C.
class Greeter
# The cref points to Greeter, so GREETING is defined there
GREETING = "hello"
end
puts Greeter::GREETING
# prints "hello"
Greeter.class_eval do
# class_eval doesn't affect cref, so SALUTATION is defined on Object
SALUTATION = "hi"
end
puts Object::SALUTATION
# prints "hi"
puts Greeter::SALUTATION
# Raises NameError
Because cref can’t be modified from Ruby, it’s difficult to control how constants are defined and managed in DSLs. This is generally why many DSLs eschew constants or provide alternatives.
And indeed, there are several other elements to the Ruby “context”, controlling such functionality as what super
calls, how iteration keywords like next
and break
behave, and so forth. If you’re interested in exploring this further, the old Ruby hacking guide has a chapter dedicated to the context, but it’s from the Ruby 1.7 era and might be out of date. Pat Shaughnessy’s excellent book Ruby Under a Microsocope is somewhat newer and also covers some of these topics. (If anyone knows of other resources, leave a note in the comments!)
Top comments (0)