String interpolation in module_eval gotcha

ruby metaprogramming

Sat Dec 04 09:24:30 -0800 2010

If you find yourself doing some meta-programming in ruby with module_eval (aka class_eval), you may want to take heed of this gotcha if you exercise string interpolation inside the evaluated block. This was tested on versions 1.8.7, 1.9.1 and 1.9.2.

Example

module Foo  
  module_eval(<<-EVAL, __FILE__, __LINE__)
    def greet(name = nil)
      "Hello #{name}"
    end
  EVAL
end

class Bar
  include Foo
end

b = Bar.new
puts b.greet("Willy") #=> produces "Hello Foo"  

You get "Foo", because the code block with string interpolation in it, actually gets executed to create the following code:

def greet(name = nil)
  "Hello Foo"
end

There are two ways to get around this gotcha. Concatenate or correctly escape string interpolation inside the eval block

With string concatenation...

module Foo  
  module_eval(<<-EVAL, __FILE__, __LINE__)
    def greet(name = nil)
      "Hello " + name
    end
  EVAL
end

class Bar
  include Foo
end

b = Bar.new
puts b.greet("Willy") #=> produces "Hello Willy"  

Significant or not, there is always a performance penalty to pay with concatenation instead of interpolation. So the ideal fix for this error with interpolation will be as follows below.

Correctly escaping string interpolation...

module Foo  
  module_eval(<<-EVAL, __FILE__, __LINE__)
    def greet(name = nil)
      "Hello \#{name}"
    end
  EVAL
end

class Bar
  include Foo
end

b = Bar.new
puts b.greet("Willy") #=> produces "Hello Willy"  

The key being escaping the interpolation with "\", so the string interpolation gets executed during runtime not creation time. The code generated looking like this:

def greet(name = nil)
  "Hello #{name}"
end  
blog comments powered by Disqus