Ruby: Eval with String Interpolation

Ruby: eval with string interpolation

What's happening, is eval is evaluating the string as source code. When you use double quotes, the string is interpolated

eval '"123 #{456.to_s} 789"'
# => "123 456 789"

However when you use single quotes, there is no interpolation, hence the # starts a comment, and you get

123 #{456.to_s} 789
# => 123

The string interpolation happens before the eval call because it is the parameter to the method.

Also note the 456.to_s is unnecessary, you can just do #{456}.

In Ruby, can you perform string interpolation on data read from a file?

Instead of interpolating, you could use erb. This blog gives simple example of ERB usage,

require 'erb'
name = "Rasmus"
template_string = "My name is <%= name %>"
template = ERB.new template_string
puts template.result # prints "My name is Rasmus"

Kernel#eval could be used, too. But most of the time you want to use a simple template system like erb.

Eval a string without string interpolation

You can do this via regex by ensuring that there are an even number of backslashes before the character you want to escape:

def safe_eval(str)
eval str.gsub( /([^\\](?:\\\\)*)#(?=[{@$])/, '\1\#' )
end

…which says:

  • Find a character that is not a backslash [^\\]
  • followed by two backslashes (?:\\\\)
    • repeated zero or more times *
  • followed by a literal # character
  • and ensure that after that you can see either a {, @, or $ character.
  • and replace that with
    • the non-backslash-maybe-followed-by-even-number-of-backslashes
    • and then a backslash and then a #

Using interpolation within an attribute name (avoiding eval)

You could use the fact that assignments like user.name = 'John' are actually method calls and can be written like this: user.name=('John'), where name= is the name of the method. We can invoke methods dynamically with send (call any method) or public_send (call a public method, will raise error if method exists but is private).

car.public_send("entered_text_#{i}=", new_car.public_send("entered_text_#{i}"))
puts car.public_send("entered_text_#{i}")


Related Topics



Leave a reply



Submit