What's the diff between puts y and puts "#{y}" in class_eval

Look at the code.

y = " another hi"

class Wes
end

Wes.class_eval <<-END
def hello
    puts "#{y}"
    puts y # I get wrong number of arguments error
end
END

While puts "#{y}" works puts y fails. And I am not sure what's the
explanation.

Any thoughts.

···

--
Posted via http://www.ruby-forum.com/.

Hi,

···

In message "Re: what's the diff between puts y and puts "#{y}" in class_eval" on Mon, 28 Jan 2008 16:22:33 +0900, Raj Singh <neeraj.jsr@gmail.com> writes:

Look at the code.

Just because #{y} is expanded at compile time, so that class_eval
receives

  puts " another hi"

whereas mere y cannot be referenced since its scope is different.

              matz.

Hi Matz,

Thanks for the answer but I couldn't quite follow it.

It seems that you are suggesting that the scope of "#{y}" is different
from the scope of plain vanilla 'y' in "puts y".

Could you please further elaborate on that. To me it seems like both of
them have the same scope because they are both being used in the same
method.

···

--
Posted via http://www.ruby-forum.com/.

Could you please further elaborate on that. To me it seems like both of
them have the same scope because they are both being used in the same
method.

Well, when you write this

   y = " another hi"
   
   class Wes
   end
   
   Wes.class_eval <<-END
    def hello
       puts "#{y}"
       puts y # I get wrong number of arguments error
    end
   END

  1) ruby create the string at toplevel (where is defined `y') and this give

    def hello
       puts " another hi"
       puts y # I get wrong number of arguments error
    end

  2) the string is given to class_eval, which create the method #hello
     in Wes

    def hello
       puts " another hi"
       puts y # I get wrong number of arguments error
    end

  when the method is compiled, `y' is resolved as a call to a method (no
  local variable `y' was defined in the def #hello)

  When you call Wes.new.hello, ruby try to call this method and it give you
  an error.

Guy Decoux

Compare to this:

class T
    def first
        "hello"
    end
    def second
        "chunky bacon"
    end
end

y = "first"

T.class_eval <<-END
    def hello
        puts #{y}
    end
END

T.new.hello # prints "hello"

The key is that the "<<-END" is creating an interpolated string in the
original context; the interpolation of #{y} isn't happening during the
call to "hello", but rather as part of the string passed to
class_eval.

You are probably confused by the fact that in the sample code, the
interpolation is happening inside of another string constant, which
makes it look like it's happening as part of "hello". It's not.

  -- ryan

···

On 1/28/08, Raj Singh <neeraj.jsr@gmail.com> wrote:

Could you please further elaborate on that. To me it seems like both of
them have the same scope because they are both being used in the same
method.

Ryan Ingram wrote:

Could you please further elaborate on that. To me it seems like both of
them have the same scope because they are both being used in the same
method.

Compare to this:

class T
    def first
        "hello"
    end
    def second
        "chunky bacon"
    end
end

y = "first"

T.class_eval <<-END
    def hello
        puts #{y}
    end
END

T.new.hello # prints "hello"

The key is that the "<<-END" is creating an interpolated string in the
original context; the interpolation of #{y} isn't happening during the
call to "hello", but rather as part of the string passed to
class_eval.

You are probably confused by the fact that in the sample code, the
interpolation is happening inside of another string constant, which
makes it look like it's happening as part of "hello". It's not.

  -- ryan

Thanks for the explanation, I've been watching this thread and waiting
for an explanation that I could comprehend. If I understand your
explanation correctly, the steps are:

1) The substitution in the string:

str =<<STR
def hello
   puts #{y}
end
STR

is performed. The scope is the global scope, so the string becomes:

str =<<STR
def hello
   puts first
end
STR

2) The string is class_eval'ed in the scope of class T, producing:

class T
   def first
      "hello"
   end

   def second
      "chunky bacon"
   end

  def hello
     puts first
  end
end

3) When the method hello is called, the line:

puts first

outputs the return value of the first method.

Applying those steps to the op's example:

y = " another hi"

class Wes
end

Wes.class_eval <<-END
def hello
   puts "#{y}"
   puts y # I get wrong number of arguments error
end
END

1) The string:

<<-END
def hello
   puts "#{y}"
   puts y # I get wrong number of arguments error
end
END

becomes:

<<-END
def hello
    puts "another hi"
    puts y # I get wrong number of arguments error
end
END

2) The class Wes is transformed into:

class Wes
  def hello
    puts "another hi"
    puts y
  end
end

3) Then a statement such as:

w = Wes.new
w.hello

produces an error because there is no variable or method named 'y'
inside class Wes:

rb:4:in `hello': undefined local variable or method `y' for
#<Wes:0x25328> (NameError)

···

On 1/28/08, Raj Singh <neeraj.jsr@gmail.com> wrote:

--
Posted via http://www.ruby-forum.com/\.

Yep, you've got it exactly.

···

On 1/28/08, 7stud -- <bbxx789_05ss@yahoo.com> wrote:

Thanks for the explanation, I've been watching this thread and waiting
for an explanation that I could comprehend. If I understand your
explanation correctly, the steps are:

1) The substitution in the string:

str =<<STR
def hello
  puts #{y}
end
STR

is performed. The scope is the global scope, so the string becomes:

str =<<STR
def hello
  puts first
end
STR

2) The string is class_eval'ed in the scope of class T, producing:

class T
  def first
     "hello"
  end

  def second
     "chunky bacon"
  end

def hello
    puts first
end
end

3) When the method hello is called, the line:

puts first

outputs the return value of the first method.