My great friend and mentor Jake Dempsey is always preaching about code readability and keeping logic out of views, etc, and therefore he’s always writing nice little helpers to do some of the magic — for instance, collection_content_for(collection) which will takes a block and will only render that block if that collection is not empty.
Now, it’s my turn to give back:
I wanted something completely reusable, simple and easy-to-read that would allow me to iterate over a collection, whilst producing an html block of content specific to the type of object in the collection. Understand? No. Okay then, here you go:
BEFORE
<% if @blog_posts && @blog_posts.size > 0 %>
<% @blog_posts.each do |blog_post| %>
<div class="blog_post">
<%= link_to blog_post.title, blog_post %>
<%= truncate blog_post.content, :length => 500 %>
</div>
<% end %>
<% end %>
That’s a lot of ugliness, logic, just — ugh.
Now, mind you, I am fully aware of the ability to do partial rendering to achieve the same affect … i.e.
<%= render :partial => @blog_posts %>
But, I have some objects that don’t even have controllers much less views, especially when they’re associated to other things (like photos for instance … no controller, no views, but I’d like to render all of the photos for a certain object)
Introduce collection_content_each_tag.
def collection_content_each_tag(collection, tag_name=:div, &block)
return unless collection && collection.size > 0
collection.each do |item|
concat("<#{tag_name.to_s} class=\"#{item.class.name.underscore}\">")
concat(capture(item, &block))
concat("</#{tag_name}>")
end
end
NOTE: Only in recent versions of Rails does #concat not want you to pass the binding argument — If you’re running older versions of Rails, make sure that on each of the #concat methods above, you add block.binding as a second argument.
So what is this method doing? Well I’ll tell you. Say for instance you have a collection of photos and you want to generate a div containing each photo and its caption.
AFTER
<% collection_content_each_tag @photos do |photo| %> <%= image_tag photo.public_filename(:thumb) %> <%= photo.caption %> <% end %>
The method loops over @photos creating a div, (defaults to :div, but you could pass :span, :p, whatever as your second arg), and generates your html tag with your block elements inside. The photo block argument in this example represents an item in the @photos collection, which gets passed to the block. So our output is:
<div class="photo"> <img src="/photos/0001/photo_1_thumb.jpg"/> Is this not the most gorgeous flower you've ever seen? </div> <div class="photo"> <img src="/photos/0002/photo_2_thumb.jpg"/> A panda loose in Brooklyn. </div>
EXTEND IT
Add an id attribute on your tag with the object id in it:
def collection_content_each_tag(collection, tag_name=:div, &block)
return unless collection && collection.size > 0
collection.each do |item|
concat("<#{tag_name.to_s} id=\"#{item.class.name.underscore}-#{item.id}\" class=\"#{item.class.name.underscore}\">")
concat(capture(item, &block))
concat("</#{tag_name}>")
end
end
Would give you:
<div class="photo" id="photo-1"> <img src="/photos/0001/photo_1_thumb.jpg" /> Is this not the most gorgeous flower you've ever seen? </div>
Maybe someone’s done this before, who knows. But that’s it, simple, reusable, and easy-to-read.
Happy coding!
No related posts.
Related posts brought to you by Yet Another Related Posts Plugin.