Reputation: 838
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
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
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
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
insideinstance_eval
defines a method inself
'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