3

I am writing a class method to create another class method. There seems to be some strangeness around how class_eval and instance_eval operate within the context of a class method. To illustrate:

class Test1
  def self.add_foo
    self.class_eval do # does what it says on the tin
      define_method :foo do
        puts "bar"
      end
    end
  end
end

Test1.add_foo # creates new instance method, like I'd expect
Test1.new.foo # => "bar"


class Test2
  def self.add_foo
    self.instance_eval do # seems to do the same as "class_eval"
      define_method :foo do
        puts "bar"
      end
    end
  end
end

Test2.add_foo # what is happening here?!
Test2.foo # => NoMethodError
Test2.new.foo # => "bar"


class Test3
  def self.add_foo
    (class << self; self; end).instance_eval do # call explicitly on metaclass
      define_method :foo do
        puts "bar"
      end
    end
  end
end

Test3.add_foo # => creates new class method, as I'd expect
Test3.foo # => "bar"

My understanding is that class methods are instance methods defined on the metaclass of the class in question (Test2 in this case). Based on that logic, I would expect the receiver for the class method call add_foo to be the metaclass.

  • What is self referring to inside the Test2.add_foo method?
  • Why does calling instance_eval on this receiver object create an instance method?
sawa
  • 165,429
  • 45
  • 277
  • 381
motns
  • 305
  • 2
  • 6
  • instance_eval is explained here : http://stackoverflow.com/questions/13775794/a-confusing-case-in-ruby-metaprogramming/13790992#13790992 – BernardK Dec 13 '12 at 21:27
  • Interesting : http://stackoverflow.com/questions/14428531/ruby-instance-eval-on-a-class-with-attr-accessor/14431718#14431718 and the linked article. – BernardK Jan 21 '13 at 08:31

1 Answers1

5

The main difference between instance_eval and class_eval is that instance_eval works within the context of an instance, while class_eval works within the context of a class. I am not sure how familiar you are with Rails, but let's look at this for an example:

class Test3 < ActiveRecord::Base

end

t = Test3.first
t.class_eval { belongs_to :test_25 } #=> Defines a relationship to test_25 for this instance
t.test_25 #=> Method is defined (but fails because of how belongs_to works)

t2 = Test3.find(2)
t2.test_25 #=> NoMethodError

t.class.class_eval { belongs_to :another_test }
t.another_test #=> returns an instance of another_test (assuming relationship exists)
t2.another_test #=> same as t.another_test

t.class_eval { id } #=> NameError
t.instance_eval { id } #=> returns the id of the instance
t.instance_eval { belongs_to :your_mom } #=> NoMethodError

This happens because belongs_to is actually a method call that happens within the context of the class body, which you cannot call from an instance. When you try to call id with class_eval, it fails because id is a method defined on an instance, not in a class.

Defining methods with both class_eval and instance_eval work essentially the same when called against an instance. They will define a method only on the instance of the object it is called against.

t.class_eval do 
  def some_method
    puts "Hi!"
  end
end

t.instance_eval do
  def another_method
    puts "Hello!"
  end
end

t.some_method #=> "Hi!"
t.another_method #=> "Hello!"

t2.some_method #=> NoMethodError
t2.another_method #=> NoMethodError

They differ, however, when dealing with the class.

t.class.class_eval do
  def meow
    puts "meow!"
  end
end

t.class.instance_eval do
  def bark
    puts "woof!"
  end
end

t.meow #=> meow!
t2.meow #=> meow!

t.bark #=> NoMethodError
t2.bark #=> NoMethodError

So where did bark go? It got defined on the instance of the class' singleton class. I'll explain more below. But for now:

t.class.bark #=> woof!
Test3.bark #=> woof!

So to answer your question about what self is referring to within the class body, you can construct a little test:

a = class Test4
  def bar
    puts "Now, I'm a #{self.inspect}"
  end

  def self.baz
    puts "I'm a #{self.inspect}"
  end

  class << self
    def foo
      puts "I'm a #{self.inspect}"
    end

    def self.huh?
      puts "Hmmm? indeed"
    end

    instance_eval do
      define_method :razors do
        puts "Sounds painful"
      end
    end

    "But check this out, I'm a #{self.inspect}"
  end
end

puts Test4.foo #=> "I'm a Test4"
puts Test4.baz #=> "I'm a Test4"
puts Test4.new.bar #=> Now I'm a #<Test4:0x007fa473358cd8>
puts a #=> But check this out, I'm a #<Class:Test4>

So what is happening here is that in the first puts statement above, we see that inspect is telling us that self within the context of a class method body is referring to the class Test4. In the second puts, we see the same thing, it's just defined differently (using the self.method_name notation for defining class methods). In the third, we see that self refers to an instance of Test4. The last one is a bit interesting because what we see is that self is referring to an instance of Class called Test4. That is because when you define a class, you're creating an object. Everything in Ruby is an object. This instance of object is called the metaclass or the eigenclass or singleton class.

You can access the eigenclass with the class << self idiom. While you're in there, you actually have access to the internals of the eigenclass. You can define instance methods inside of the eigenclass, which is congruent with calling self.method_name. But since you are within the context of the eigenclass, you can attach methods to the eigenclass' eigenclass.

Test4.huh? #=> NoMethodError
Test4.singleton_class.huh? #=> Hmmm? indeed

When you call instance_eval in the context of a method, you're really calling instance_eval on the class itself, which means that you are creating instance methods on Test4. What about where I called instance_eval inside of the eigenclass? It creates a method on the instance of Test4's eigenclass:

Test4.razors #=> Sounds painful

Hopefully, this clears up a few of your questions. I know that I have learned a few things typing out this answer!

Sean Hill
  • 14,978
  • 2
  • 50
  • 56
  • The answer could be improved. I'd expand on how instance_eval and class_eval do the same thing when executed on a class and how defining method on eigenclass is different. – Sergio Tulentsev Dec 13 '12 at 18:38
  • Thanks for the response! I agree with @SergioTulentsev - it would be nice to get a bit more clarification here. Most importantly, what instance does `self` refer to in `Test2`? – motns Dec 13 '12 at 18:58
  • 1
    You might want to explain how the method resolution differs between the first and second puts examples above (one resolves to the class, and the other resolves to the eigenclass after not finding that method on the class). – Wes Gamble Dec 13 '12 at 23:08
  • Thanks for the detailed writeup, I definitely learned something new! What sparked my initial confusion is that Ruby's `inspect` displays Class objects as just the class name - `puts Test4.inspect` simply prints `Test4`, with no indication of it being an object (an instance of the Class class). – motns Dec 14 '12 at 15:45
  • Also, I think I now understand why `class_eval` and `instance_eval` end up doing the same thing in my original example, best illustrated by: `Test2.instance_eval{self}.equal? Test2.class_eval{self}` which returns `true`. Calling `instance_eval` will execute in the context of the `Test2` Class object, while `class_eval` will execute in the context of the Class, which is of course also the `Test2` Class object. Does this sounds correct? – motns Dec 14 '12 at 15:53
  • Yeah, that sounds right, though, admittedly, I'm still working out the details myself. – Sean Hill Dec 15 '12 at 20:26