Oct 13, 2008 @ 03:44 pm

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) :

<TABLE style="color:fushia;font-style:MSONormal generated=frontpage">
<%while not objRS.eof %>
<%if(current_author <> objRS("author"))%>
<h1><%=objRS("author")%></h1>
<%end if%>
<Tr><td><%=objRS("body")</td></TR>
<%current_author = objRS("author")%>
<%objRS.MoveNext%>
<%loop%>
<%objRS.close%>
</TABLE>

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)

all_quotes = Quote.find(:all)
@authors = all_quotes.group_by(&:author)

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| quote.author}

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 :

@authors.each_pair do |author_name, quotes|
  <h1><%=author_name%></h1>
  <%quotes.each do |quote| %>
    - <%=quote.body%><br />
  <%end%>
<%end%>

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 :

@authors.keys.sort.each do |author_name|
  <h1><%=author_name%></h1>
  <%@authors[author_name].each do |quote|%>
    - <%=quote.body%><br />
  <%end%>
<%end%>

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.

Bookmark this post : These icons link to social bookmarking sites where readers can share and discover new web pages.
  • DZone
  • Reddit
  • del.icio.us
  • Digg
  • Furl
  • Technorati
  • StumbleUpon
Posted under : In depth
5 Comments

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]

Comment by : Grant HutchinsNo Gravatar
— October 19, 2008 @ 3:05 am

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

Comment by : FrankNo Gravatar
— October 19, 2008 @ 9:20 am

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

Comment by : ChessNo Gravatar
— October 19, 2008 @ 11:08 pm

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:

Comment by : Miquel HerreraNo Gravatar
— December 28, 2009 @ 9:28 am

The form eaten my html example :(

I hope you get the idea

Comment by : Miquel HerreraNo Gravatar
— December 28, 2009 @ 9:30 am




Leave a comment
Name (required)
Email (will not be publish) (required)
Website