Class_eval() problem

Here is some code that works as expected:

def create_class
Class.new
end

MyClass = create_class

MyClass.class_eval do
def greet
puts ‘hi’
end
end

obj = MyClass.new
obj.greet

–output:–
hi

Now here is a more complex version that doesn’t work as I expected:

def create_class
cl = Class.new

cl.class_eval do
yield
end

return cl
end

MyClass = create_class do

def greet
puts ‘hello’
end

end

obj = MyClass.new
obj.send(:greet) #hello
#obj.greet #private method – NoMethodError

Why is greet() a private method…and in what class is greet() even
defined?

puts obj.methods.grep(/^g/) #no output
puts obj.singleton_methods #no output

I can’t find greet() in main or Object either.

The greet method is a private method of Object not of MyClass.
So it seems that the def in the block is evaluated in the toplevel scope
(where the block is defined) and not in the MyClass scope.

2011/3/11 7stud – [email protected]

Gunther D. wrote in post #986869:

The greet method is a private method of Object not of MyClass.

Thanks. I didn’t realize that Object#methods() returned only public
methods.

So it seems that the def in the block is evaluated in the toplevel scope
(where the block is defined) and not in the MyClass scope.

This is what I’ve come up with:

def create_class
cl = Class.new

cl.class_eval do

#In this block self=cl because that is
#what class_eval() does

yield  #However, the block being yielded to
       #doesn't care what the bindings are
       #in this block--the block being yielded
       #to got its bindings when the block was
       #created. (bindings = variable_name/value pairs)

end

return cl
end

#Here self=main

MyClass = create_class do

#This block sees the bindings that were present when it was
#created–no matter where the block ends up.
#In the current binding self=main. Remember
#blocks can see the variable bindings present outside the
#block.

def greet
puts ‘hello’
end

end

obj = MyClass.new
obj.send(:greet) #hello

1)The block isn’t executed until the yield, and when yield() is finally
called the block still sees self=main.

  1. def statements create instance methods in the “current class”.

  2. When self is not a class, the current class ends up being self’s
    class.

The block sees self=main, so the current class
inside the block is main’s class, which is Object. As a result, when
the block finally executes the def becomes an instance method of the
current class, which is Object. In other words, when the block
executes it’s the exact same thing as defining a method in the
top-level, where self=main and the current class=Object. And by ruby
dictate a top-level def becomes a private instance method of Object(the
method can be called anywhere–but you can’t use a receiver
to call the method).