Rails: Should partials be aware of instance variables?

Alexandre picture Alexandre · Mar 23, 2010 · Viewed 24.9k times · Source

Ryan Bates' nifty_scaffolding, for example, does this

edit.html.erb

<%= render :partial => 'form' %>

new.html.erb

<%= render :partial => 'form' %>

_form.html.erb

<%= form_for @some_object_defined_in_action %>

That hidden state makes me feel uncomfortable, so I usually like to do this

edit.html.erb

<%= render :partial => 'form', :locals => { :object => @my_object } %>

_form.html.erb

<%= form_for object %>

So which is better: a) having partials access instance variables or b) passing a partial all the variables it needs?

I've been opting for b) as of late, but I did run into a little pickle:

some_action.html.erb

<% @dad.sons.each do |a_son| %>
<%= render :partial => 'partial', :locals => { :son => a_son } %>
<% end %>

_partial.html.erb

The son's name is <%= son.name %>
The dad's name is <%= son.dad.name %>

son.dad makes a database call to fetch the dad! So I would either have to access @dad, which would be going back to a) having partials access instance variables or I would have to pass @dad in locals, changing render :partial to <%= render :partial => 'partial', :locals => { :dad => @dad, :son => a_son } %>, and for some reason passing a bunch of vars to my partial makes me feel uncomfortable. Maybe others feel this way as well.

Hopefully that made some sense. Looking for some insight into this whole thing... Thanks!

Answer

ryanb picture ryanb · Mar 23, 2010

In recent versions of Rails it is quite a bit easier to render partials and pass locals to them. Instead of this.

<%= render :partial => 'form', :locals => { :item => @item } %>

You can do this.

<%= render 'form', :item => @item %>

I don't do this in the Nifty Scaffold generator to keep backwards compatibility, but I'll change this in a future release.

As for whether it's acceptable to use instance variables in partials. I think it is. In all practicality, what is the downside? Certainly things can get out of hand if you aren't consistent, but I like to apply these guidelines.

  1. Never create an instance variable just to share it between partials. Usually this means you will only be sharing the controller resource object.

  2. If the partial is the same name as the resource, pass it as a local with <%= render @item %>.

  3. If the partial will be shared across multiple controllers then only use locals.

This is what works well for me anyway.

Bonus tip: if you find yourself passing in a lot of locals into a partial and you want some of them to be optional, create a helper method which renders the partial. Then always go through the helper method so you can make a clean interface with optional args for rendering the partial.