views:

3348

answers:

3

I have a quite old templating system written on top of ERB. It relies on ERB templates stored in database. Those are read and rendered. When I want to pass data from one template to another I use the :locals parameter to Rails render method. For setting default variables of those variables in some templates I use the defined? method which simply tells me if local variable has been defined and if not I initialize it with default value like this:

unless defined?(perex)
  perex = true
end

I am upgrading the app to latest Rails and I see some weird behavior. Basically this sometimes works (sometimes perex is undefined) and sometimes it does not (perex is defined and set to nil). This happens without anything else changing.

I have two questions: Is there any better way other than using defined? which is proving unreliable (was reliable for several years on top Rails 1.6)? Such a way should not result in me rewriting all the templates. I have been going through Ruby docs and was not able to find anything about defined? method. Was it deprecated or am I just plain blind?

Edit: The actual issue was caused by what seems to be a Ruby/eRB bug. Sometimes the unless statement would work, but sometimes not. The weird thing is that even if the second line got executed perex stil stayed nil to the rest of the world. Removing defined? resolved that.

+7  A: 

First: actually, defined? is an operator.

Second: if I understand your question correctly, the way to do it is with this Ruby idiom:

perex ||= true

That'll assign true to perex if it's undefined or nil. It's not exactly what your example does, since yours doesn't evaluate the assignment when the value is nil, but if you are relying on that then, in my opinion, without seeing it, you're not writing clear code.

Edit: As Honza noted, the statement above will replace the value of perex when it's false. Then I propose the following to rewrite the minimum number of lines:

perex ||= perex.nil?  # Assign true only when perex is undefined or nil
Romulo A. Ceccon
This is not true. perex ||= true does the same as perex = perex || true which sets perex to true if it is undefined, nil or false. The last case would break everything.
Honza
+4  A: 

The safest way of testing if a local is defined in a Rails template is:

local_assigns[:perex]

This is documented in the Rails API together with the explanation that defined? cannot be used because of a implementation restriction.

mislav
+1  A: 

Per mislav's answer, I went looking for that documentation in the Rails API, and found it in Class ActionView::Base (under the heading "Passing local variables to sub templates"). It was hardly worth the search, though, since it barely said anything more than mislav did. Except that it recommends this pattern:

if local_assigns.has_key? :perex