Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get first association from a has many through association

I'm trying to join the first song of each playlist to an array of playlists and am having a pretty tough time finding an efficient solution.

I have the following models:

class Playlist < ActiveRecord::Base
  belongs_to :user
  has_many :playlist_songs
  has_many :songs, :through => :playlist_songs
end

class PlaylistSong < ActiveRecord::Base
  belongs_to :playlist
  belongs_to :song
end

class Song < ActiveRecord::Base
  has_many :playlist_songs
  has_many :playlists, :through => :playlist_songs
end

I would like to get this:

playlist_name  |  song_name
----------------------------
chill          |  baby
fun            |  bffs

I'm having a pretty tough time finding an efficient way to do this through a join.

UPDATE ****

Shane Andrade has lead me in the right direction, but I still can't get exactly what I want.

This is as far as I've been able to get:

playlists = Playlist.where('id in (1,2,3)')

playlists.joins(:playlist_songs)
         .group('playlists.id')
         .select('MIN(songs.id) as song_id, playlists.name as playlist_name')

This gives me:

playlist_name  |  song_id
---------------------------
chill          |  1

This is close, but I need the first song(according to id)'s name.

like image 202
mrabin Avatar asked Mar 08 '13 18:03

mrabin


3 Answers

Assuming you are on Postgresql

Playlist.
  select("DISTINCT ON(playlists.id) playlists.id, 
          songs.id song_id, 
          playlists.name, 
          songs.name first_song_name").
  joins(:songs).
  order("id, song_id").
  map do |pl|
    [pl.id, pl.name, pl.first_song_name]
  end
like image 52
Harish Shetty Avatar answered Nov 18 '22 23:11

Harish Shetty


I think this problem would be improved by having a a stricter definition of "first". I'd suggest adding a position field on the PlaylistSong model. At which point you can then simply do:

Playlist.joins(:playlist_song).joins(:song).where(:position => 1)
like image 34
Dave S. Avatar answered Nov 18 '22 23:11

Dave S.


What you are doing above with joins is what you would do if you wanted to find every playlist with a given name and a given song. In order to collect the playlist_name and first song_name from each playlist you can do this:

Playlist.includes(:songs).all.collect{|play_list| [playlist.name, playlist.songs.first.name]}

This will return an array in this form [[playlist_name, first song_name],[another_playlist_name, first_song_name]]

like image 28
rocket scientist Avatar answered Nov 18 '22 23:11

rocket scientist