Donato Donato - 1 month ago 6
Ruby Question

syntax error, unexpected || in instance_eval string

I want to interpolate a value in an

instance_eval
string. the argument
alias_name
can sometimes be nil or a string. If it is nil, then I invoke another method which returns a string, as shown below:

def create_document(klass, alias_name)
klass.instance_eval <<-EOS
def alias_name
#{alias_name} || name
end
EOS
end


But this is actually giving me a syntax error:

Uncaught exception: (eval):2: syntax error, unexpected ||
|| name
^


I am looking closely at this code and I don't see the syntax error. If
#{alias_name}
is either returning nil or string, why would this cause an error?

After all this all works:

> nil || 'something else'
=> "something else"
> 'something' || 'something else'
=> "something"


When
alias_name
is nil, it doesn't get evaluated at all. Its like just nonexistant in the instance_eval string, causing syntax error. But even this does not work:

alias_name = alias_name || ""
klass.instance_eval <<-EOS
def alias_name
if #{alias_name}.present?
#{alias_name}
else
demodulized_name
end
end
EOS


It would give this error:

Uncaught exception: (eval):2: syntax error, unexpected '.'
if .present?
^


You see that? Its as if
alias_name
does not exist.

Answer

When using string interpolation, "s #{expr} s" is equivalent to:

's ' + expr.to_s + ' s'

and nil.to_s is an empty string so this:

klass.instance_eval <<-EOS
  def alias_name
    #{alias_name} || name
  end
EOS

ends up like:

klass.instance_eval %q{
  def alias_name
    || name
  end
}

when alias_name is nil or an empty string and:

def alias_name
  || name
end

is not syntactically correct Ruby code.

The heredoc that you're feeding to klass.instance_eval doesn't know that there's any special "this is Ruby code" context, it is just building a string like any other string and blindly call to_s on the #{} interpolations.

Perhaps you want to use inspect instead of to_s:

klass.instance_eval <<-EOS
  def alias_name
    #{alias_name.inspect} || name
  end
EOS

That will give you Ruby code like:

def alias_name
  nil || name
end

when alias_name is nil and:

def alias_name
  "pancakes" || name
end

when alias_name is the string 'pancakes'.

Or perhaps you want the test outside the instance_eval if alias_name is meant to be the name of another method:

if alias_name.present?
  klass.instance_eval <<-EOS
    def alias_name
      #{alias_name} || name
    end
  EOS
else
  klass.instance_eval <<-EOS
    def alias_name
      name
    end
  EOS
end
Comments