Using: Rails 3.1.0, Ruby 1.9.3
To reproduce, run this:
rails new haml_exception_test
echo "gem 'haml'" >> Gemfile
echo "gem 'hpricot'" >> Gemfile
echo "gem 'ruby_parser'" >> Gemfile
rails generate scaffold block
rm -f app/views/blocks/index.html.erb
cat > app/views/blocks/index.html.haml <<END
, then go to http://localhost:3000/blocks, which outputs:
NameError in Blocks#index
Showing /Users/change/Development-test/haml_exception_test/app/views/blocks/index.html.haml where line #1 raised:
undefined local variable or method `on_line_2' for #<#<Class:0x00000103afe868>:0x00000103b081b0>
Notice that the exception reports line #1 even though the exception was on line 2.
This is caused by the :ruby filter, and I don’t think it’s easy to fix.
When the source Haml is compiled, the lines in the generated Ruby are made to correspond to the lines in the original Haml as much as possible, so that if an error occurs the line reported as the cause of the error will match the location in the Haml, even though it is really referring to the line in the generated Ruby.
The problem with the :ruby filter is that the Ruby code in the filter needs to be ‘wrapped’ with extra code before and after before it is added to the generated code, so there are two extra lines of code to fit in.
For example if the Haml is something like:
then the generated Ruby code looks something like:
If this was added like this, then added_first_line would be a the same line number as :ruby, and all the Ruby from the Haml template would be at the correct place in the generated Ruby. However all the remaining lines in the template would be at the wrong position in the generated code since added_last_line would push them all down one.
What actually gets added to the generated code looks like this, using a ; to join added_first_line with the first line from the filter (the code is in haml/filters.rb):
Now it all fits into the number of lines available, so the rest of the template appears at the right place, but the code within the :ruby filter itself appears at one line less than it should.
The same trick of joining lines using a semicolon can’t be used on the last line, because the last line in the filter could be a comment (or end with one):
ruby.code #a comment here;added_last_line
This would leave all the generated lines in the right place, but breaks everything by commenting out the filter tidy up code.
If Ruby had something like C’s #line directive then this would be fairly simple to fix. Ruby does let you specify the line as a parameter to eval, so it might be possible to get something working by treating the code as a string and passing it to eval.
If you’re using :ruby a lot and it’s important to get the line numbers right you might want to look at implementing a custom filter using eval, but I don’t think it’s worth the complexity and performance costs to change the current implementation.
As an aside, you don’t need to create a Rails app to demonstrate this:
Exception on line 1: undefined local variable or method `on_line_2' for #<Object:0x00000100b83cc8>
Use --trace for backtrace.
(Thats CTRL-D/end of file, or you could just create the file).
There is a ruby line directive pull request at ruby/ruby#911.