The difference between instance_eval and instance_exec

There's an important difference between instance_eval and instance_exec. And there's a great lesson about how to use them well in FactoryGirl

But first, before you go rushing off to build your fantastic DSL, let's look at what instance_eval is and does.

The simplest of examples can be taken straight from the Ruby docs:

class KlassWithSecret
      def initialize
        @secret = 99
      end
    end
    k = KlassWithSecret.new
    k.instance_eval { @secret } #=> 99

The current value for self inside the provided block will be the object on which you call instance_eval. So in this case the k object is the current context for the block; @secret is a variable stored inside k and instance_eval opens up access to that object and all of it's internal variables.

The interface that FactoryGirl provides is simple and straightforward. Here's an example from it's "Getting Started" documentation:

FactoryGirl.define do
      factory :user do
        first_name "Kristoff"
        last_name  "Bjorgman"
        admin false
      end
    end

Here, FactoryGirl uses instance_eval to execute the blocks of code passed to factory.

Let's take a look at some representative code from how FactoryGirl makes this work:

def factory(name, &block)
      factory = Factory.new(name)
      factory.instance_eval(&block) if block_given?
      # ... more code
    end

That's not actually the code from FactoryGirl, but it represents roughly what happens. When the method factory is called a new Factory is created and then the block is executed in the context of that object. In other words where you see first_name it's as if you had that factory instance before it and instead had factory.first_name. By using instance_eval, the users of FactoryGirl don't need to specify the factory object, it's implicitly applied to it.

_Ok, that's all well and good, but what about instance_exec?_

I'm glad you asked.

The instance_eval method can only evaluate a block (or a string) but that's it. Need to pass arguments into the block? You'll be frozen in your tracks.

But instance_exec on the other hand, will evaluate a provide block and allow you to pass arguments to it. Let's take a look...

FactoryGirl allows you to handle callbacks to perform some action, for example, after the object is created.

FactoryGirl.define do
      factory :user do
        first_name "Kristoff"
        last_name "Bjorgman"
        admin false

        after(:create) do |user, evaluator|
          create_list(:post, evaluator.posts_count, user: user)
        end
      end
    end

In this sample, the after(:create) is run after the object is created, but the block accepts two arguments: user and evaluator. The user argument is the user that was created. The evaluator is an object which stores all the values created by the factory.

Let's take a look at how this is implemented:

def run(instance, evaluator)
      case block.arity
      when 1, -1 then syntax_runner.instance_exec(instance, &block)
      when 2 then syntax_runner.instance_exec(instance, evaluator, &block)
      else        syntax_runner.instance_exec(&block)
      end
    end

FactoryGirl will create a callback object named by the argument given to the after method. The callback is created with a name, :create in this case, and with a block of code.

The block that we used in our example had two arguments.

The run method decides how to execute the code from the block.

The callback object stores the provided block and Ruby allows us to check the arity of the block, or in other words, it allows us to check the number of arguments.

When looking at a case statement, it's a good idea to check the else clause first. This gives you an idea of what will happen if there's no match for whatever code exists in the when parts.

There we see syntax_runner.instance_exec(&block) and this could easily be changed to use instance_eval instead. Ruby will evaluate, or execute, the block in the context of the syntax_runner object.

If the block's arity is greater than zero, FactoryGirl needs to provide the objects to the block so that our code works the way we expect.

The second part of the case checks if the block arity is equal to 2.

when 2 then syntax_runner.instance_exec(instance, evaluator, &block)

If it is, the syntax_runner receives the instance (or in our case user) and the evaluator.

If, however, the arity is 1 or -1 then the block will only receive the instance object.

So what is that -1 value? Let's look at the ways we could create a callback:

# Two arguments and arity of 2
    after(:create) do |user, evaluator|
      create_list(:post, evaluator.posts_count, user: user)
    end
    # One argument and arity of 1
    after(:create) do |user|
      create_group(:people, user: user)
    end
    # Zero arguments and arity of 0
    after(:create) do
      puts "Yay!"
    end
    # Any arguments and arity of -1
    after(:create) do |*args|
      puts "The user is #{args.first}"
    end

Ruby doesn't know how many args you'll give it with *args so it throws up it's hands and tells you that it's some strange number: -1.

This is the power of understanding how and when to use instance_exec; users of the DSL will expect it to make sense, and it will.

But wait! There's more!

What if you want to specify the same value for multiple attributes?

FactoryGirl.define do
      factory :user do
        first_name "Kristoff"
        last_name  "Bjorgman"

        password "12345"
        password_confirmation "12345"
      end
    end

In the above example, both the password and password_confirmation are set to the same value. This could be bad. What if you change the password for one, but forget to change the other? If they are inherently tied in their implementation, then that could lead to some unexpected behavior when they are not the same.

I would, and probably you would too, prefer to tell FactoryGirl to just use the value I'd already configured.

Fortunately FactoryGirl allows us to use a great trick in Ruby using the to_proc method. Here's what it looks like in use:

FactoryGirl.define do
      factory :user do
        first_name "Kristoff"
        last_name  "Bjorgman"

        password "12345"
        password_confirmation &:password
      end
    end

The important part is the &:password value provided to password_confirmation. Ruby will see the & character and treat the following as a block by calling to_proc on it. To implement this feature, FactoryGirl defines to_proc on attributes and there will use instance_exec to provide the symbol password to the block:

def to_proc
      block = @block

      -> {
        value = case block.arity
                when 1, -1 then instance_exec(self, &block)
                else instance_exec(&block)
                end
        raise SequenceAbuseError if FactoryGirl::Sequence === value
        value
      }
    end

What about lambdas and procs?

Some commenters in Reddit raised an important question about how these methods behave when given lambdas and procs.

If you provide a lambda which accepts no arguments as the block, instance_eval will raise an error:

object = Object.new
    argless = ->{ puts "foo" }
    object.instance_eval(&argless) #=> ArgumentError: wrong number of arguments (1 for 0)

This error occurs because Ruby will yield the current object to the provided block as self. So you can fix it by providing a lambda which accepts an argument:

args = ->(obj){ puts "foo" }
    object.instance_eval(&args) #=> "foo"

This changes a bit if you use instance_exec:

object.instance_exec(&argless) #=> "foo"
    object.instance_exec(&args) #=> ArgumentError: wrong number of arguments (0 for 1)
    object.instance_exec("some argument", &args) #=> "foo"

Because a proc is less restrictive with argument requirements, it will allow either approach to work without error:

p_argless = proc{ puts "foo" }
    object.instance_eval(&p_argless) #=> "foo"

    p_args = proc{|obj| puts "foo" }
    object.instance_eval(&p_args) #=> "foo"

    object.instance_exec(&p_args) #=> "foo"
    object.instance_exec(&p_argless) #=> "foo"

Now you know, instance_exec and instance_eval are similar in the way they behave, but you'll reach for instance_exec if you need to pass variables around.

##Announcing Ruby Metaprogramming Masterclass

I'm offering a new online class where I'll be teaching you how to master metaprogramming in Ruby on April 30th (the day after my birthday!)

I'm keeping the spaces limited to 25 so attendees will be able to talk and ask questions but already over a quarter of the seats are gone. So grab a seat now, before they're all gone.