Dealing with Collections in Rails Views
Wednesday, February 14, 2007 by Nate Murray.
Often, we want to render collections of things in Rails views. In this example the end goal is to output a list of Category names with urls. This is often referred to as "breadcrumbs"
Example 1
For our breadcrumbs the first thing one might think to do is create a helper to perform this function.
For those of you who are new to Rails, helpers are often designed to return a string and this string is added to the output.
Below is our first attempt at a helper for this problem. This code returns a string with our anchor tags separated by an image.
== helpers/sites_helper.rb def make_breadcrumbs(breadcrumbs) crumbs = [] spacer = "<img src='/images/separator.gif' width=5 height=5 border=0>" breadcrumbs.each do |crumb| crumbs << "<a href='#{url_for_category(crumb)}'> #{crumb.name}</a>" end return crumbs.join(spacer) end
We can call this helper easily from our view:
== views/sites/_breadcrumbs.rhtml <%= make_breadcrumbs(@breadcrumbs) %>
This works just fine, but there are a couple consequences:
- its very specific to our problem.
- All the HTML is in the code instead of in our views.
Example 2
"But wait a minute," you ask, "Doesn't Rails come with built-in collection-rendering methods?" You're right! In fact we can use Rails render method to achieve the same effect.
== views/sites/_breadcrumbs.rhtml <%= render :partial => "sites/breadcrumb", :collection => @breadcrumbs, :spacer_template => 'sites/breadcrumb_spacer' %>
In our view we call the render method and we pace the options :collection and :spacer_template. This renders the partial sites/breadcrumb and creates the local variable breadcrumb with each element of @breadcrumbs.
The two templates are below:
== views/sites/_breadcrumb.rhtml <a href='<%= url_for_category(breadcrumb)%>'><%=breadcrumb.name%></a>
== views/sites/_breadcrumb_spacer.rhtml <img src='/images/separator.gif' width=5 height=5 border=0>
A benefit of this is that all of our HTML is in our views.However, a consequence that we have to create two new partials that contain only one line each.
What we would really want is a way to keep all of our html in our view while not having to create any extra templates.
Example 3
Here is an example of the syntax we want:
== views/sites/_breadcrumbs.rhtml <% spacer = capture do %> <img src="/images/separator.gif" width=5 height=5 border=0> <% end %> <% with_collection @breadcrumbs, :spacer_template => spacer do |crumb| %> <a href='<%= url_for_category(crumb) %>'><%= crumb.name %></a> <% end %>
The Rails helper capture takes the block and puts it in the variable spacer.
Then we put this in helpers/sites_helper.rb
== helpers/sites_helper.rb def with_collection(collection, opts={}, &proc) collection.each_with_index do |element, i| yield element if spacer = opts[:spacer_template] concat(spacer, proc.binding) unless i == collection.size - 1 end end end
This helper takes the collection and yields each element. As each element is yielded the block in the view is added to the output. The trick is that we add the spacer to the output by using the concat method. The concat method outputs the string to the view from the helper. This is how we can output something to the template without using <%= (This is also what the helpers such as form_for use).
Labels: articles, helpers, rails, ruby