Rails partials should be explicitly called with locals

Since Rails partials can see all the @variables of their parent view it’s pretty common to see partials with @’s all over the place. But the cool thing about partials is reusing them in other views and if you do you have to remember to populate all the stuff it needs and name everything the way it wants. Bah, I say. More and more I find myself explicitly calling partials with locals even if seems a bit redundant:

<%= render(:partial => 'content/poll',
:locals => {:poll => @poll}) %>

It goes along with this "partials are like methods" analogy I’ve been thinking about lately. And in big complicated pages with lots ‘o partials everything seems more clear. What do you think?

Comments

Anonymous said…
Agreed.
Any time a partial is going to be shared, it should definitely be called with explicit locals, and even if you don't think that you're going to share, you might in the future, so you might as well go all out and just explicitly define locals in all cases.
Sam Smoot said…
A helper to copy instance variables into the hash might be a nice addition?

def copy_instance_variables()
instance_variables.inject({}) do |hash,name|
name_as_symbol = name[1..name.size].to_sym
hash[name_as_symbol] = instance_variable_get(name)
hash
end
end

Certainly more convenient. You could use instance_eval in conjunction with this to get a variables-hash for whatever object you wanted too...
Farooq Ali said…
In your example and in many other cases, a less-redundant approach is to use:

render(:partial => 'content/poll', :object => @poll)

which gives you a local variable 'poll' within your partial. The name of the local variable when using :object and :collection options is always set to the name of the partial.
Rick Bradley said…
Jake,
I agree wholeheartedly -- I've been doing this for quite a while now. I've found it tends to make my partials converge to an "API" that is reusable as well, or if not it sets me to thinking if I'm doing the right thing and maybe should refactor early on.

Also, it's a real code smell to use the partial name as the magical object variable within the partial. If you want a long descriptive name you're making a long variable name (which may not be descriptive inside the partial), or if your partial is named _show then you've got a 'show' variable in the partial, which is usually non-intuitive. Worse, if you move the partial or rename it you not only have the calls to the partial to worry about but you also have to edit the partial to rename the magic variable. Smelly.

Anyway, using :locals you get as many variables as you need as part of the API, the API is explicit.

I go so far as to occasionally grep for '@' in partials to see what I might've missed.

Best,
Rick
Matt said…
Great post! If you want your locals to be optional, rather than all explicitely set, you can use local_param = nil unless defined?(local_param) - see How-To Make a Rails Partial With Optional Locals (Parameters)
Duncan said…
I find myself actually moving a lot of my render :partial calls out to Helper modules and giving them descriptive names. That way I can use calls in my view like <%= render_shouts_list %>

This way I can make sure that all the API-ish stuff for a partial, such as required locals, is handled in a single place and all methods that need to render that partial must do so through a Helper-provided method.
Carlos said…
I agree with you, but what could I do if I wanted to modify the local variable inside the local, so that these change could be seen in the parent form (or in another instance of the same partial).

In other words, how to pass locals as input/output parameters?
James said…
Carlos, IMHO, partials should never be changing instance variables or local variables that are passed back out. That just sounds like a frontend debugging nightmare waiting to happen.

Popular posts from this blog

Point Inside a Polygon in Ruby

What's a Good Flog Score?

SICP Wasn’t Written for You