Nested loops through one model

60 views
Skip to first unread message

Tony Tambe

unread,
Feb 17, 2015, 10:39:57 AM2/17/15
to rubyonra...@googlegroups.com

In my music review app the Pins model has attributes of Pin.artist and Pin.album. I'm trying to list each artist reviewed on the site and which albums of theirs have been reviewed. Below is what I have so far, but I want to do it without repeating the artist name.

Controller:

@pin_albums = Pin.group(:album).order('artist')

View:

<% @pin_albums.each do |pin| %>
  <%= pin.artist %> | 
  <%= link_to pin.album, copy_pin_path(pin) %>
  <br/>
<% end %>

This lists them like this:

The Beatles | Let It Be The Beatles | Abbey Road Bob Dylan | Blood On The Tracks Bob Dylan | Highway 61 Revisited

I want to list them like so:

The Beatles | Let It Be
            | Abbey Road
Bob Dylan | Blood On The Tracks
          | Highway 61 Revisited

I need to do something to the effect of:

<% @pin_albums.each do |pin| %>
  <ul>
    <li><%= pin.artist %></li> 
      <ul>
      <% pin.artist.each do |pin_album| %> 
        <li><%= link_to pin_album.album, pin_album %></li>
      <% end %>
  <br/>
<% end %> 

I know that the above nested tables won't work, but that's the gist of what I'm trying to figure out.

Colin Law

unread,
Feb 17, 2015, 11:08:45 AM2/17/15
to rubyonra...@googlegroups.com
On 17 February 2015 at 15:39, Tony Tambe <tamb...@gmail.com> wrote:
> In my music review app the Pins model has attributes of Pin.artist and
> Pin.album. I'm trying to list each artist reviewed on the site and which
> albums of theirs have been reviewed. Below is what I have so far, but I want
> to do it without repeating the artist name.
>
> Controller:
>
> @pin_albums = Pin.group(:album).order('artist')
>
> View:
>
> <% @pin_albums.each do |pin| %>
> <%= pin.artist %> |
> <%= link_to pin.album, copy_pin_path(pin) %>
> <br/>
> <% end %>
>
> This lists them like this:
>
> The Beatles | Let It Be The Beatles | Abbey Road Bob Dylan | Blood On The
> Tracks Bob Dylan | Highway 61 Revisited
>
> I want to list them like so:
>
> The Beatles | Let It Be
> | Abbey Road
> Bob Dylan | Blood On The Tracks
> | Highway 61 Revisited

Leave the artist cell blank if the artist this time is the same as the
artist last time.

Colin

TTambe

unread,
Feb 19, 2015, 8:59:07 AM2/19/15
to rubyonra...@googlegroups.com
Someone suggested that I use local variables like this:

<% @pin_albums.each do |pin| %>

  <%= (last_artist ||= nil) != pin.artist ? (last_artist = pin.artist) : '' %> | 
  
<%= link_to pin.album, copy_pin_path(pin) %>
  <br/>
<% end %>
This gives me the same result as my original code:

The Beatles | Let It Be 
The Beatles | Abbey Road 
Bob Dylan | Blood On The Tracks 
Bob Dylan | Highway 61 Revisited

I'm not well versed on writing local variables in RoR, so I'm not sure that I'm analyzing this correctly, but I don't see where last_artist is referencing the last iteration of the loop. I was thinking of trying to use @pin_albums.each.with_index and then somehow comparing pin.artist to pin.artist - 1 

Any thoughts from everyone would be appreciated.
Thanks!


--
You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rubyonrails-ta...@googlegroups.com.
To post to this group, send email to rubyonra...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/rubyonrails-talk/CAL%3D0gLsQnnC32Xru-A6P1_Tr6SeL2ex6a%3DNCUOxV9Yrjr5%2BtJQ%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.

Colin Law

unread,
Feb 19, 2015, 9:13:59 AM2/19/15
to rubyonra...@googlegroups.com
On 19 February 2015 at 13:57, TTambe <tamb...@gmail.com> wrote:
> Someone suggested that I use local variables like this:
>
> <% @pin_albums.each do |pin| %>
> <%= (last_artist ||= nil) != pin.artist ? (last_artist = pin.artist) : ''
> %> |
> <%= link_to pin.album, copy_pin_path(pin) %>
> <br/>
> <% end %>
>
> This gives me the same result as my original code:
>
> The Beatles | Let It Be
> The Beatles | Abbey Road
> Bob Dylan | Blood On The Tracks
> Bob Dylan | Highway 61 Revisited

That looks as if it should work. Have you posted a direct copy/paste
from your code?
It assigns nil to last_artist unless last_artist already has a value,
then compares that to pin.artist. If not the same (so a new artist)
it assigns pin.artist to last_artist and displays that, if the the
same then it displays an empty string.
I wonder whether the precedence is wrong. If the above is a copy/paste
from your code then try
((last_artist ||= nil) != pin.artist) ? (last_artist = pin.artist) : ''
Note the extra parentheses

If that still doesn't work add a column on the end of the line showing
last_artist and see if it is tracking correctly.

Colin
> https://groups.google.com/d/msgid/rubyonrails-talk/CAEdg5qTkpQs%3DsbVg6QgS8fRf8JOQop_ryMJA9wb6epQfKjf%3DMg%40mail.gmail.com.

TTambe

unread,
Feb 19, 2015, 1:23:25 PM2/19/15
to rubyonra...@googlegroups.com
Yes, the above was a paste from my actual code. I added the parentheses, but there was no change. I added <%= last_artist %> and the output was just each artist again. So it seems that there is something wrong with teh portion that checks last_artist against pin.artist. 

Hassan Schroeder

unread,
Feb 19, 2015, 2:45:53 PM2/19/15
to rubyonrails-talk
On Thu, Feb 19, 2015 at 5:57 AM, TTambe <tamb...@gmail.com> wrote:
> Someone suggested that I use local variables like this:
>
> <% @pin_albums.each do |pin| %>
> <%= (last_artist ||= nil) != pin.artist ? (last_artist = pin.artist) : ''
> %> |
> <%= link_to pin.album, copy_pin_path(pin) %>
> <br/>
> <% end %>

Hint: "scope"

2.1.0 (main):0 > 3.times{ puts (val ||= 0); val += 1 }
0
0
0
=> 3
2.1.0 (main):0 > 3.times{ puts (@val ||= 0); @val += 1 }
0
1
2
=> 3

HTH,
--
Hassan Schroeder ------------------------ hassan.s...@gmail.com
http://about.me/hassanschroeder
twitter: @hassan
Consulting Availability : Silicon Valley or remote

TTambe

unread,
Feb 19, 2015, 7:04:01 PM2/19/15
to rubyonra...@googlegroups.com
I actually ended up doing this which worked:

    <% last_artist ||= nil %>
  <table id="artist">
    <tr>
      <th>Artist</th>
      <th></th>
      <th>Album</th>
   </tr>

<% @pin_albums.each do |pin| %>
  
  <% if last_artist != pin.artist %>
    <tr>
      <td><%= pin.artist %></td>
      <td><%= link_to image_tag(pin.image), pin %></td>
      <td><%= link_to pin.album, copy_pin_path(pin) %></td>
    </tr>  
  <% else %>
    <tr>
      <td></td>
      <td><%= link_to image_tag(pin.image), pin %></td>
      <td><%= link_to pin.album, copy_pin_path(pin) %></td>
    </tr>
  <% end %>     
  <% last_artist = pin.artist %>     
<% end %>


--
You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rubyonrails-ta...@googlegroups.com.
To post to this group, send email to rubyonra...@googlegroups.com.

Colin Law

unread,
Feb 20, 2015, 3:58:21 AM2/20/15
to rubyonra...@googlegroups.com
You are right in that the key was to initialise last_artist at an
appropriate scope.
There are an awful lot of repeated lines there, Suppose you wanted to
change the detail of the contents of the last cell in each row, you
would have to change it in two places. You should put the if
statement round just the first cell not round the whole row. so for
that cell use something like
<td>
<%= (last_artist == pin.artist) ? '' : pin.artist %>
</td>
Or use an if statement inside the <td> </td> if you prefer. That is
two single quotes in the line above of course.

Colin

Anil Yadav

unread,
Feb 20, 2015, 3:58:32 AM2/20/15
to rubyonra...@googlegroups.com
I Think you need something like this 
<%= form_for @albums do |f| %>

<%= f.fields_for :artist do |r_f| %>
---------do--
<% end %>
------------do--------
<% end %>

TTambe

unread,
Feb 20, 2015, 1:23:43 PM2/20/15
to rubyonra...@googlegroups.com
You're absolutely right Colin, thanks. I'm not that familiar with that simplified syntax of if/else statements, so I learned quite a bit from this exchange! 

--
You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rubyonrails-ta...@googlegroups.com.
To post to this group, send email to rubyonra...@googlegroups.com.

TTambe

unread,
Feb 21, 2015, 4:14:35 PM2/21/15
to rubyonra...@googlegroups.com
Ok, so I need some help again. Every thing works great locally, but when I push to Heroku the controller code doesn't play nice with the Postgres database. Here is the controller again:

@pin_albums = Pin.group(:album).order('artist asc')

The error in my log is:

ActionView::Template::Error (PG::GroupingError: ERROR:  column "pins.id" must appear in the GROUP BY clause or be used in an aggregate function

2015-02-21T20:42:53.231456+00:00 app[web.1]: LINE 1: SELECT "pins".* FROM "pins"  GROUP BY album  ORDER BY artist...

2015-02-21T20:42:53.231458+00:00 app[web.1]:                ^

2015-02-21T20:42:53.231460+00:00 app[web.1]: : SELECT "pins".* FROM "pins"  GROUP BY album  ORDER BY artist asc):

2015-02-21T20:42:53.231462+00:00 app[web.1]:     12:         <th></th>

2015-02-21T20:42:53.231464+00:00 app[web.1]:     13:         <th>Album</th>

2015-02-21T20:42:53.231466+00:00 app[web.1]:     14:       </tr>

2015-02-21T20:42:53.231469+00:00 app[web.1]:     15:     <% @pin_albums.each do |pin| %>  

2015-02-21T20:42:53.231471+00:00 app[web.1]:     16:         <tr>

2015-02-21T20:42:53.231473+00:00 app[web.1]:     17:           <td><%= (last_artist == pin.artist)  ?  ''   :   pin.artist %></td>

2015-02-21T20:42:53.231475+00:00 app[web.1]:     18:           <td><%= link_to image_tag(pin.image), pin %></td>

2015-02-21T20:42:53.231478+00:00 app[web.1]:   app/views/pages/artists.html.erb:15:in `_app_views_pages_artists_html_erb__4200071474432892294_70083862737420'



I've been Googling it, but any answers I've found haven't been clear enough for me to fix the issue. I see it says that pins.id must appear in the Group By clause, but I'm not clear on that.

TTambe

unread,
Feb 23, 2015, 12:44:22 PM2/23/15
to rubyonra...@googlegroups.com
I found this in the Postgres documentation, but it reads like jibberish to me for the most part. 

GROUP BY Clause

The optional GROUP BY clause has the general form

GROUP BY expression [, ...]

GROUP BY will condense into a single row all selected rows that share the same values for the grouped expressions. expression can be an input column name, or the name or ordinal number of an output column (SELECT list item), or an arbitrary expression formed from input-column values. In case of ambiguity, a GROUP BY name will be interpreted as an input-column name rather than an output column name.

Aggregate functions, if any are used, are computed across all rows making up each group, producing a separate value for each group (whereas without GROUP BY, an aggregate produces a single value computed across all the selected rows). When GROUP BY is present, it is not valid for the SELECT list expressions to refer to ungrouped columns except within aggregate functions, since there would be more than one possible value to return for an ungrouped column.

Reply all
Reply to author
Forward
0 new messages