How to display a collection grouped by an attribute value in Rails

Flashback time. We are in 1999 and you are coding in ASP thinking that this language is the future. You use ADODB recordsets to iterate over collections. You have a recordset containing some records from a “quotes” table. The “quotes” table contains a author column (varchar) and a body column (varchar). Now you want to display the results grouped by author name so it looks like this :

Georges Brassens
- Les filles quand ça dit “je t’aime”, c’est comme un second baptême
- Aucune idée sur terre n’est digne d’un trépas

Billie Holiday
- Don’t threaten me with love, baby. Let’s just go walking in the rain.
- I never hurt nobody but myself and that’s nobody’s business but my own.

Assuming your recordset is ordered by author, you do something like this (remember, we’re in 1999) :

[vb]<TABLE style="color:fushia;font-style:MSONormal generated=frontpage">
<%while not objRS.eof %>
<%if(current_author <> objRS("author"))%>
<%end if%>
<%current_author = objRS("author")%>

Ok welcome back in 2008. ASP is dead. You are coding in rails and you want to do the same thing. How will you do it? Storing the author name in a buffer variable like in 1999? Not too sure about it.

This is a job for Enumerable#group_by (Enumerable is a module that is mixed in the Array class)

Enumerable#group_by will create different sets of quotes based on the “author” values. Why the “&” sign? It’s because group_by expects a block. I could have done it this way : @authors = all_quotes.group_by{|quote|}

So @authors is now a hash that will look like this:

{“George Brassens” => [#<Quote id:131 ...>, #<Quote id:331 ...>], “Billie Holiday” => [#<Quote id:111 ...>, #<Quote id:911 ...>] }

Now you can iterate over it like this :

One more thing to note : @authors is a hash, and hashes cannot be ordered. If you want to display quotes by sorted author name, you could do this :

Note : Enumerable#group_by does not exist in ruby 1.8, it only exists in Rails. The method will be in ruby 1.9 however.

  • Grant Hutchins

    You can also do:

    @authors.sort.each do |author_name, quote|

    because Hash#sort returns an array of 2-value arrays of the form [key, value]

  • Frank

    Grant, this is even better than @authors.keys.sort.each, thanks for sharing.

  • Chess

    Thanks for this, very helpful for us who don’t yet know all the nooks and crannys in rails and ruby!

  • Miquel Herrera

    It is always interesting to learn new tricks but in this case I would consider more efficient to get the data ready to use straight from the DB rather than getting the same data and reorganize in memory. I would do something like this:

    @authors = Author.find :all, :include => :quotes, :order => :name

    this returns an array of authors and pre-load their quotes using two sql instances such as:

    SELECT * FROM authors ORDER BY name;
    SELECT quotes.* FROM quotes WHERE (quotes.author_id IN (1,2,3,…));

    in the view you just loop all authors and their quotes:

  • Miquel Herrera

    The form eaten my html example :(

    I hope you get the idea

  • crumbs delivery

    This article will help the internet viewers for creating new weblog or even a weblog from start to end.