DEV Community

Daniel Azuma
Daniel Azuma

Posted on • Originally published at daniel-azuma.com on

Should I use instance_eval or class_eval?

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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!)

Discussion (0)