Don't Use Instance Variables in Partials 2
Using instance variables (e.g. @article) is a handy way to pass data from your controller to your views. When your view is rendered, Rails arranges for all the controller instance variables to be available within the view.
However, you should avoid using instance variables to pass data to partials. Partials are almost always used for snippets of HTML that will be reused in multiple places in your application, and using instance variables makes it harder to achieve that reuse.
Here’s a simple example. Let’s say we have a blog article, and we want to display the article body followed by a set of links to actions that can be performed on the article. So we might have a controller like this:
def show
@article = Article.find(params[:id])
endHere’s the view, show.rhtml:
<h1><%= h @article.title %></h1>
<div><%= h @article.body %></div>
<%= render :partial => 'links' %>And here’s the partial, _links.rhtml:
<p>
<%= link_to 'Comments', :controller => '/articles', :action => 'comments', :id => @article.id %>
</p>(Note how were referencing @article from inside the partial.)
Now let’s say we want to display a list of articles, and we want to reuse the links partial to add links to each article.
Here’s the controller:
def list
@all_articles = Article.find(:all)
endAnd here’s the view:
<h1>All Articles</h1>
<% @articles.each do |art| %>
<h2><%= h art.title %><h2>
<%= render :partial => 'links' %>
<% end %>Uh-oh. How does _links.rhtml find the article id, since we don’t have an @article instance variable?
You might be tempted to “fix” it like this:
<h1>All Articles</h1>
<% @articles.each do |art| %>
<h2><%= h art.title %><h2>
<% @article = art # needed by the partial %>
<%= render :partial => 'links' %>
<% end %>Don’t do that. Instead, change your partial to avoid using instance variables and use local variables instead.
You have two choices for naming local variables:
- Use a local variable named the same as your partial (e.g. “
links” in this case. - Use any local variable names you wish. For example, we could use
articleas a local variable for the article instead of the@articleinstance variable. More about this in a moment.
The first form is useful if your partial is used to display all the details for each item of a collection (array). In this case, you can render the entire array without using an explicit loop by using the :collection argument to render:
<%= render :partial => 'links', :collection => @all_articles %>Even though there is no loop, the partial is rendered once for each element in @articles. Within the partial, you would reference the current article through the links local variable:
<p>
<%= link_to 'Comments', :controller => '/articles', :action => 'comments', :id => links.id %>
</p>If you only want to render the partial for a single element (as in our example, since we already have a loop that is doing more than just rendering the partial), you pass the element using :object instead of :collection:
<h1>All Articles</h1>
<% @articles.each do |art| %>
<h2><%= h art.title %><h2>
<%= render :partial => 'links', :object => art %>
<% end %>To make this work with our orignal show.rhtml view, we pass the article in the same way:
<h1><%= h @article.title %></h1>
<div><%= h @article.body %></div>
<%= render :partial => 'links', :object => @article %>Now our partial is much more flexible and is decoupled from dependence on any particular instance variables. We can pass an article to it from whatever context is appropriate.
The only thing that’s a bit ugly is using the local variable name links in our partial. Perhaps you would prefer to use a more meaningful name, like article.
If you do this, you need to pass the variable using :locals instead of :object. :locals takes a hash of variable names/values (so it can also be used to pass multiple variables). For example:
<h1><%= h @article.title %></h1>
<div><%= h @article.body %></div>
<%= render :partial => 'links', :locals => { :article => @article } %>Within your partial, you would now refer to the article object through the local variable article.
This technique can’t be used with the :collection form; it will always pass the collection element using the same name as the partial itself.