rubyerb

Why is this an error with ERB?


<div class='row'>
  <%= form.field_container :name do %>
    <%= form.label :name, raw('Name' + content_tag(:span, ' *', :class => 'required')) %>
    <%= form.text_field :name, :class => 'fullwidth' %>
    <%= form.error_message_on :name %>
  <% end %>
</div>

Why is this producing the following error?

$ erb -x -T - test.erb | ruby -c
-:3: syntax error, unexpected ')'
...form.field_container :name do ).to_s); _erbout.concat "\n"
...                               ^
-:9: syntax error, unexpected $end, expecting ')'

Solution

  • If you look at the code outputted by erb -x -T - test.erb:

    #coding:ASCII-8BIT
    _erbout = ''; _erbout.concat "<div class='row'>\n  "
    ; _erbout.concat(( form.field_container :name do ).to_s); _erbout.concat "\n"
    ; _erbout.concat "    "; _erbout.concat(( form.label :name, raw('Name' + content_tag(:span, ' *', :class => 'required')) ).to_s); _erbout.concat "\n"
    ; _erbout.concat "    "; _erbout.concat(( form.text_field :name, :class => 'fullwidth' ).to_s); _erbout.concat "\n"
    ; _erbout.concat "    "; _erbout.concat(( form.error_message_on :name ).to_s); _erbout.concat "\n"
    ; _erbout.concat "  ";  end ; _erbout.concat "\n"
    ; _erbout.concat "</div>\n"
    ; _erbout.force_encoding(__ENCODING__)
    

    You can see that on the third line, a do is followed by a ). Ruby is expecting a doend block, but gets a closing parenthesis. That’s the immediate cause of the syntax error.

    The reason for erb outtputting bad code is that you are using <%= when you should be using <%. Changing your code to this fixes the syntax error:

    <div class='row'>
      <% form.field_container :name do %>
        <%= form.label :name, raw('Name' + content_tag(:span, ' *', :class => 'required')) %>
        <%= form.text_field :name, :class => 'fullwidth' %>
        <%= form.error_message_on :name %>
      <% end %>
    </div>
    

    I can’t run this code to test if it outputs what it should after my change, but the code generated by erb looks like it will work:

    #coding:ASCII-8BIT
    _erbout = ''; _erbout.concat "<div class='row'>\n  "
    ;  form.field_container :name do ; _erbout.concat "\n"
    ; _erbout.concat "    "; _erbout.concat(( form.label :name, raw('Name' + content_tag(:span, ' *', :class => 'required')) ).to_s); _erbout.concat "\n"
    # more...
    

    Edit

    Since this solution apparently does break the output, I looked into what mu is too short suggested. I checked if Erubis, which Rails 3 uses by default, behaves differently from ERB. The code outputted by erubis -x -T - test.erb (with the original, unedited test.erb):

    _buf = ''; _buf << '<div class=\'row\'>
      '; _buf << ( form.field_container :name do ).to_s; _buf << '
    '; _buf << '    '; _buf << ( form.label :name, raw('Name' + content_tag(:span, ' *', :class => 'required')) ).to_s; _buf << '
    '; _buf << '    '; _buf << ( form.text_field :name, :class => 'fullwidth' ).to_s; _buf << '
    '; _buf << '    '; _buf << ( form.error_message_on :name ).to_s; _buf << '
    ';   end 
     _buf << '</div>
    ';
    _buf.to_s
    

    Line three has the exact same problem, and erubis -x -T - test.erb | ruby -c outputs the same syntax error. So the differences between ERB and Erubis are probably not the problem.

    I also tried syntax-checking this piece of code from the official Rails documentation:

    <%= form_for(zone) do |f| %>
      <p>
        <b>Zone name</b><br />
        <%= f.text_field :name %>
      </p>
      <p>
        <%= f.submit %>
      </p>
    <% end %>
    

    It gets the same syntax error. So it’s not that your ERB code is badly written; your code is very similar to that example.

    At this point my best guess is that erb’s -x flag, which translates an ERB template into Ruby code instead of evaluating it directly, is flawed, and does not support some features it should. Though now that I think about it, I am having trouble imagining exactly what Ruby code should be outputted when you output the result of a block that itself outputs text should work. At what times should each of the outputs be written – the result first, or the block contents first?