Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is this an error with ERB?

Tags:

ruby

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 ')'
like image 943
Daniel Fischer Avatar asked Jun 28 '13 21:06

Daniel Fischer


2 Answers

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?

like image 130
Rory O'Kane Avatar answered Nov 16 '22 13:11

Rory O'Kane


Short version: nothing is wrong; Rails does some crazy stuff.

Long version: You can only do

<%= some_method do %>
<% end %>

(that is, use <%= with a block) because of a giant flying hack in Rails. The content of <%= %> is supposed to be an expression (or expressions) that stands on its own: you should be able to stick it into eval and have it be syntactically valid.

Before Rails 3, people were sometimes confused by the fact that with ‘normal helpers’ (link_to, number_to_currency, etc.), you had to use <%=, but when using the block form of these helpers or helpers like form_for, you had to use <%. That’s why Rails 3 made <%= support blocks.

To handle blocks in this case, when you use <%=, Rails looks at the ERB code, sees whether it looks like a block by using a regular expression, and if it does rewrites the generated code on the fly to turn it into valid Ruby.

More gory details in the excellent blog post “Block Helpers in Rails 3”.

like image 12
Frederick Cheung Avatar answered Nov 16 '22 14:11

Frederick Cheung