Haml not consistently raising "undefined local variable or method" exceptions

615 views
Skip to first unread message

Ben Fyvie

unread,
Jun 10, 2009, 11:21:24 AM6/10/09
to Haml
We have a very very simple problem that I am hoping somebody can
resolve.

We are running the following:

Frozen Rails Gem 2.1.2
Frozen Haml Gem 2.1.0
Ruby 1.8.6 patch level 287

Our problem is very simple. The following code in haml should always
raise an exception:

-p my_undefined_variable
-p my_undefined_variable.class


This is true in our production and staging environments (hosted at
EngineYard); however, on our local dev machines we get output of nil
and NilClass with no exceptions being raised. If we change from haml
to erb the exception is raised as expected. We have a windows and a
mac dev machine, neither of which are able to get the exception
raised.

Any ideas?

Nathan Weizenbaum

unread,
Jun 10, 2009, 12:25:58 PM6/10/09
to ha...@googlegroups.com
What is the output of "haml --version"?

Since you're finding this difficult to reproduce, I imagine I will as well. Could you create a minimal Rails app that demonstrates the problem and send it to me?

Ben Fyvie

unread,
Jun 11, 2009, 12:10:27 PM6/11/09
to ha...@googlegroups.com

Thanks for offering to help. I’ve included a very simple rails app that reproduces the problem. Included in the app is a frozen version of Rails as well as two frozen versions of HAML, the bug is occurring in both versions. To change the frozen gem that is used, simply modify environment.rb.

 

 

Ben Fyvie


haml_bug.7z

Nathan Weizenbaum

unread,
Jun 11, 2009, 4:10:52 PM6/11/09
to ha...@googlegroups.com
Ah, I see what the problem is. It's not from Haml, it's from Rails, and it's pretty inescapable due to the way locals are handled in templates. What happens is that each template has a set of locals associated with it - the locals that have been used in that template before. When you try to render a template with more locals than it has, it's recompiled with those locals added in.

What that means is that when you run your partial via AJAX, it's already been run when generating the index view, so it's been compiled with those locals. They're set to nil if you don't pass them in, but they're still there.

The upshot of all this is that if you ever pass locals in to a given partial, you can't rely on them raising an error ever.

Ben Fyvie

unread,
Jun 11, 2009, 4:31:28 PM6/11/09
to ha...@googlegroups.com

Thanks for the insight Nathan. It sounds like some sort of built in caching of variables, is that right? Does this caching take place only on the mongrel instance that handled the request? If that is the case then it would explain why the exception is raised on our production server where we have a total of 6 mongrels across two servers, so the AJAX request is likely sent to a different mongrel and possibly even a different server than the initial index request. But when running locally we only every have one mongrel or webrick instance to handle all requests and so the AJAX request is being sent to the same instance that also received the index request. This automatic caching behavior of Rails seems a bit odd to me in that is seems to do more harm than good and doesn’t really serve any purpose when running more than a single mongrel instance.

Nathan Weizenbaum

unread,
Jun 11, 2009, 4:47:14 PM6/11/09
to ha...@googlegroups.com
Yeah, more or less. The issue is that it's really time-consuming to compile a Haml or ERB template, so Rails minimizes it by re-using locals.

Ben Fyvie

unread,
Jun 11, 2009, 4:57:50 PM6/11/09
to ha...@googlegroups.com

Strange, especially when running in development mode where performance isn’t really that important and everything is supposed to be recompiled for every request so that the latest changes are reflected.

 

Sorry, I’ll stop filling the HAML group with this Rails issue. Thanks again for your insight.

scottwb

unread,
Jun 11, 2009, 5:11:16 PM6/11/09
to Haml
One way I have addressed this is by defining and assigning default
values to locals used by a partial. I suspect I may get push-back on
putting code in a view, but in a sense, I like to treat partials as
functions in a sense, that "take care of themselves". For example, at
the top of a partial that expects locals 'foo' and 'bar' I might put
something like:

- foo = nil unless defined?(foo)
- bar = 42 unless defined?(bar) && bar

This ensures that foo and bar are always defined and if omitted by the
caller, get default values (or raise meaning full exceptions if they
are required).


On Jun 11, 1:57 pm, "Ben Fyvie" <ben.fy...@champsoftware.com> wrote:
> Strange, especially when running in development mode where performance isn't
> really that important and everything is supposed to be recompiled for every
> request so that the latest changes are reflected.
>
> Sorry, I'll stop filling the HAML group with this Rails issue. Thanks again
> for your insight.
>
> Ben Fyvie
>
>   _____  
>
> From: ha...@googlegroups.com [mailto:ha...@googlegroups.com] On Behalf Of
> Nathan Weizenbaum
> Sent: Thursday, June 11, 2009 3:47 PM
> To: ha...@googlegroups.com
> Subject: [haml] Re: Haml not consistently raising "undefined local variable
> or method" exceptions
>
> Yeah, more or less. The issue is that it's really time-consuming to compile
> a Haml or ERB template, so Rails minimizes it by re-using locals.
>
> On Thu, Jun 11, 2009 at 1:31 PM, Ben Fyvie <ben.fy...@champsoftware.com>
> wrote:
>
> Thanks for the insight Nathan. It sounds like some sort of built in caching
> of variables, is that right? Does this caching take place only on the
> mongrel instance that handled the request? If that is the case then it would
> explain why the exception is raised on our production server where we have a
> total of 6 mongrels across two servers, so the AJAX request is likely sent
> to a different mongrel and possibly even a different server than the initial
> index request. But when running locally we only every have one mongrel or
> webrick instance to handle all requests and so the AJAX request is being
> sent to the same instance that also received the index request. This
> automatic caching behavior of Rails seems a bit odd to me in that is seems
> to do more harm than good and doesn't really serve any purpose when running
> more than a single mongrel instance.
>
> Ben Fyvie
>
>   _____  
>
> From: ha...@googlegroups.com [mailto:ha...@googlegroups.com] On Behalf Of
> Nathan Weizenbaum
> Sent: Thursday, June 11, 2009 3:11 PM
>
> To: ha...@googlegroups.com
> Subject: [haml] Re: Haml not consistently raising "undefined local variable
> or method" exceptions
>
> Ah, I see what the problem is. It's not from Haml, it's from Rails, and it's
> pretty inescapable due to the way locals are handled in templates. What
> happens is that each template has a set of locals associated with it - the
> locals that have been used in that template before. When you try to render a
> template with more locals than it has, it's recompiled with those locals
> added in.
>
> What that means is that when you run your partial via AJAX, it's already
> been run when generating the index view, so it's been compiled with those
> locals. They're set to nil if you don't pass them in, but they're still
> there.
>
> The upshot of all this is that if you ever pass locals in to a given
> partial, you can't rely on them raising an error ever.
>
> On Thu, Jun 11, 2009 at 9:10 AM, Ben Fyvie <ben.fy...@champsoftware.com>
> wrote:
>
> Thanks for offering to help. I've included a very simple rails app that
> reproduces the problem. Included in the app is a frozen version of Rails as
> well as two frozen versions of HAML, the bug is occurring in both versions.
> To change the frozen gem that is used, simply modify environment.rb.
>
> Ben Fyvie
>
>   _____  
>
> From: ha...@googlegroups.com [mailto:ha...@googlegroups.com] On Behalf Of
> Nathan Weizenbaum
> Sent: Wednesday, June 10, 2009 11:26 AM
> To: ha...@googlegroups.com
> Subject: [haml] Re: Haml not consistently raising "undefined local variable
> or method" exceptions
>
> What is the output of "haml --version"?
>
> Since you're finding this difficult to reproduce, I imagine I will as well.
> Could you create a minimal Rails app that demonstrates the problem and send
> it to me?
>
> On Wed, Jun 10, 2009 at 8:21 AM, Ben Fyvie <Ben.Fy...@champsoftware.com>

Adam Strzelecki

unread,
Jun 18, 2009, 9:48:29 AM6/18/09
to ha...@googlegroups.com
> - foo = nil unless defined?(foo)
> - bar = 42 unless defined?(bar) && bar

Correct me if I'm wrong but:
- foo ||= nil
- bar ||= 42

May be quicker and shorter way to force default values for undefined
or nil or false values.

Cheers,
--
Adam

scottwb

unread,
Jun 18, 2009, 12:43:19 PM6/18/09
to Haml
For the example I showed, you are absolutely right. The reason I don't
do it that way is that I want to treat false and nil differently --
false meaning the local should be set to false, and nil and undefined
mean use the default value. My example didn't really show that
case...that it would be something more like:

- bar = true unless defined?(bar) && !bar.nil?

This way, bar will be set to a default value of true if the caller of
the partial does not set bar, or sets it to nil, but if they
explicitly set it to false, it will stay false.

I use that style shown above in all cases to be consistent, but I
suppose it may be easier to read if I only used it specifically when I
have that boolean case. Also, I imagine some people would find it
awkward to treat nil/false the differently the way I do.

So yeah, I agree -- do it the way Adam suggested.
Reply all
Reply to author
Forward
0 new messages