Rohan Pujari
Rohan Pujari

Reputation: 838

define_method inside instance_eval

When I define a method inside instance_eval block for class it creates a class method which is fine.

Eg)

class A
end

A.instance_eval do
  def method; end
end

A.method #works

But when I use define_method inside instance_eval it creates instance method instead of class method Eg)

A.instance_eval do
  define_method(:method1) {}
end
A.method1 # NoMethodError: undefined method `method1'
A.new.method1 # Works fine

I am unable to understand above phenomena. Please can somebody help me out.

Upvotes: 2

Views: 1722

Answers (3)

sandre89
sandre89

Reputation: 5898

Memorize this:

changes self to changes current class to
class_eval receiver receiver
instance_eval receiver receiver's singleton class

Methods defined in ruby with def keyword (without an explicit receiver) are always defined in the "current class" (that's what Paolo Perrotta calls it in Metaprogramming Ruby; others call it "default definee")

The "current class" is something we usually don't think about because it's intuitive; inside a class definition, the current class is the class itself, so methods defined with def foo inside a class definition become instance methods of that class.

But when you call instance_eval with the A class being the receiver, as per the table above, you are changing the 'current class' to the receiver's singleton class; since you are defining a method with def, it will be defined in the 'current class', which results in a 'class method' (a method defined in A's singleton class or eigenclass):

class A
end

A.instance_eval do
  # the current class here is not A, but A's singleton class; 
  def method; end
end

However, when you define the method with define_method, as per the table above, you are actually invoking define_method on an implicit receiver self; if you look at the table, self will be the receiver A, so it's the same as calling A.define_method or even:

class A

  define_method(:method1) {}

end

So that's why, in this case, you get an instance method, because you called define_method on the class A, and not in A's singleton class (or also called eigenclass).

Upvotes: 1

Cary Swoveland
Cary Swoveland

Reputation: 110655

For:

class A; end

A.instance_eval do
  puts "self=#{self}"
  def m; puts "hi"; end
  define_method(:n) {puts "ho" }
end
  #=> "self=A"

we find that:

A.methods(false)          #=> [:m] 
A.instance_methods(false) #=> [:n] 
A.m                       # hi
A.n                       # NoMethodError:...
A.new.m                   # NoMethodError:...
A.new.n                   # ho

A.instance_eval opens class A and the method m is defined on A. Next, the method define_method is then invoked, which creates the instance method :n on it's receiver, A.

Suppose we were to use Module#class_eval rather than BasicObject#instance_eval:

A.class_eval do
  puts "self=#{self}"
  def m; puts "hi"; end
  define_method(:n) {puts "ho" }
end
  #=> "self=A"

we find that:

A.methods(false)          #=> [] 
A.instance_methods(false) #=> [:m, :n] 
A.m                       # NoMethodError:...
A.n                       # NoMethodError:...
A.new.m                   # hi
A.new.n                   # ho

so you see that this behaviour is the same as:

class A
  puts "self=#{self}"
  def m; puts "hi"; end
  define_method(:n) {puts "ho" }
end

and here instance methods can be defined with either def or define_method.

Upvotes: 0

Max
Max

Reputation: 22315

This quirky behavior makes a little more sense if you look at instance_eval in the context of instances (which is its main purpose).

class A
end

a = A.new
a.instance_eval do
  def foo
  end
end

Where is foo defined? The only sensible place I can think of is a's singleton class, and indeed that is true

a.method(:foo).owner == a.singleton_class
# true

So this demonstrates the rule

def inside instance_eval defines a method in self's singleton class.

which is completely consistent with what you saw.

A.instance_eval do
  # defines method in A's singleton class!
  def method; end
end

So why does define_method behave differently? Because unlike def it's a method! So this

A.instance_eval do
  define_method(:foo) {}
end

is really just

A.define_method(:foo) {}

which is the metaprogramming way of creating normal instance methods. This inconsistency may seem annoying, but again look at the case for normal instances and you'll see why def and define_method can't be consistent. This

a.instance_eval do
  define_method(:foo) {}
end

is really just

a.define_method(:foo) {}

which is nonsense

NoMethodError: undefined method `define_method' for #<A:0x00008>

Upvotes: 4

Related Questions