motns
motns

Reputation: 315

Dynamically creating class method

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.

Upvotes: 3

Views: 1027

Answers (1)

Sean Hill
Sean Hill

Reputation: 15056

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!

Upvotes: 5

Related Questions