Viewing object associated via "has_many :through"?


#1

This is my first question to the Forum. I’ve set up these associations:

class Playlist < ActiveRecord::Base
…
has_many :playlists_tracks, :dependent => :destroy
has_many :track_ones, :through => :playlists_tracks, :source => :track
has_many :track_twos, :through => :playlists_tracks, :source => :track
end

class Track < ActiveRecord::Base
…
has_many :playlists_tracks, :dependent => :destroy
has_many :playlists, :through => :playlists_tracks
has_many :playlists, :through => :playlists_tracks
end

class PlaylistsTrack < ActiveRecord::Base
belongs_to :playlist
belongs_to :track_one, :class_name => :track, :foreign_key =>
‘track_one_id’
belongs_to :track_two, :class_name => :track, :foreign_key =>
‘track_two_id’
end

With them I’m able to save to the playlists_tracks table the following:
id, playlist_id, track_one_id, track_two_id. That all seems to work
fine.

But no matter what I put in my playlists/show view file, I get an error
message when I attempt to view the tracks associated with a playlist.
For example:

<% for track in @playlist.tracks %>

<%= playlist.track_one.title %>

<% end %>

gets me this:

undefined method `tracks’ for #Playlist:0xeb4a380

I think the problem revolves around the “class_name” and “source”
options, as I have no problem displaying the name of the Playlistcreator
(who is also associated with Playlist through a “has many :through”
association, but without the “class_name” and “source” options) via the
playlists/show view.

Where am I going wrong? What do I need to do to fix this?


#2

You want to use @playlists.playlists_tracks in your view …

On Feb 5, 1:05 pm, Christian B. removed_email_address@domain.invalid


#3

Bill S. wrote:

You want to use @playlists.playlists_tracks in your view …

Thank you Bill!

That has got me a step further. This in the view, for example:

<% for track_one in @playlist.playlists_tracks %>

<%= track_one.id %>

<% end %>

now displays the track_one.id, which is a good sign.

But I still can’t reach beyond the playlists_tracks join table to pull
the title of the track from the tracks table itself. I need the “title”
of the track where the “track.id” is the same as the “track_one.id”. I
think I need a trick to kind of weave through the “:source” and
“:class_name” options to get to the tracks table, but maybe I’m missing
something obvious here. Any ideas?

Again, many thanks for your answer.


#4

Bill S. wrote:

I think what you need to do is leverage your has_many :through
relationship. Something like:

<% for track in @playlist.track_ones %>

<%= track.title %> <% end %>

Thanks again. I’d already tried that and got this error message:

Could not find the source association(s) :track in model PlaylistsTrack.
Try 'has_many :track_ones, :through => :playlists_tracks, :source =>
'. Is it one of :track_two, :playlist, or :track_one?

I’ve been through all the different variants I can think of (like the
odd one in my original example), but they all throw up an error message.
I tried acting on the error message I just listed, but didn’t get
anywhere with it. Can you or anyone see any wisdom in it that’ll help
me?

I don’t know your data model, however, the “track_ones” thing kind of
smells a bit of an incorrect data model.

Bad choice of name, I agree. It just refers to which position in the
form the track was selected at, and that’s arbitrary, non-essential
information. I’ll have a think about that, though, as people don’t tend
to give their kids ordinals as names, and you may be right. But even if
my model is a bit skewed, I think I’d be having the same problem with
“jazz_tracks” and “rock_tracks”, etc.

Can you or anyone else think what the problem might be? I’m pretty sure
it relates to the “:source” and “:class_name” options. Again, I have no
problem saving all the relevant data to the playlists_tracks join table,
but then can’t access the tracks table afterward to grab the track name.


#5

I believe that Rails uses the :source to identify the foreign key in
the join table for the :through relationship. Try this …

class Playlist < ActiveRecord::Base

has_many :playlists_tracks, :dependent => :destroy
has_many :track_ones, :through => :playlists_tracks, :source
=> :track_one
has_many :track_twos, :through => :playlists_tracks, :source
=> :track_two
end

Then use the your :through relationships in the view as before …

On Feb 5, 4:55 pm, Christian B. removed_email_address@domain.invalid


#6

I think what you need to do is leverage your has_many :through
relationship. Something like:

<% for track in @playlist.track_ones %>

<%= track.title %> <% end %>

or more idiomatically

<% @playlist.track_ones.each do |track| %>

<%= track.title %> <% end %>

I don’t know your data model, however, the “track_ones” thing kind of
smells a bit of an incorrect data model. In general, if you have a
table
that enumerates a set of columns (track_1, track_2, …) it may
indicate that you are not modeling a parent -> child relationship. It
was just a thought … I am probably barking up the wrong tree …

On Feb 5, 3:26 pm, Christian B. removed_email_address@domain.invalid


#7

Yes, you’re right!

With this model (like you said):

class Playlist < ActiveRecord::Base

has_many :playlists_tracks, :dependent => :destroy
has_many :track_ones, :through => :playlists_tracks, :source
=> :track_one
has_many :track_twos, :through => :playlists_tracks, :source
=> :track_two
end

And this in the playlists/show view:

<% for track_one in @playlist.track_ones %>

<%= track_one.title %>

<% end %>

I can display the track’s name.

Bill, Thank you so much! I really appreciate your walking me and my
noobishness through this. I can now link the last pieces of my
application together, and that’s a great relief. Cheers!