views:

144

answers:

2

I'm working on a rails site that I've inherited and am trying to troubleshooting some sub-optimal model behavior. I have users, songs, and songs_download, each of which is its own model.

Here's the relevant line from the users model:

has_and_belongs_to_many :downloaded_songs, :class_name => 'Song', :join_table => :song_downloads

From the songs model:

has_and_belongs_to_many :downloaded_users, :class_name => 'User', :join_table => :song_downloads

And from the song_downloads model:

belongs_to :user
belongs_to :song

Here's the code to create a new song_download record when a user downloads a song (in the songs controller):

SongDownload.create( :song_id => @song.id,
                     :user_id => current_user.id,
                     :download_date => Date.today )

The problem I'm having is that once a user downloads a song, if I try to invoke the downloaded users from the interactive console, by, say, typing the following:

Song.find(<some id>).downloaded_users

I get back the complete record of the user, but the id in the returned objected is the primary key of the SongDownload, not the primary key of the User. All of the other fields are accurate, but the ID is not.

I didn't come up with this modeling scheme and it seems to me that :has_and_belongs_to_many might be more appropriately used with no explicitly modeled SongDownload object, but I'd rather not overhaul the codebase if I can help it. Are there any ways to get back the right user id given the current modeling scheme?

Thanks very much for your time and consideration!

Justin

+5  A: 

Has and belongs to relationships are being phased out in favour of has many :through relationships.

On the upside you won't need to change any of your underlying structure, just the relationship declarations in the Song and User models.

class Song < ActiveRecord::Base

  has_many :song_downloads
  has_many :users, :through => :song_downloads

  ...
end

class User < ActiveRecord::Base

  has_many :song_downloads
  has_many :songs, :through => :song_downloads

  ...
end

Now

Song.find(<some id>).users

Returns an array of User objects which are joined to the selected song through the song_downloads table.

EmFi
On top of that, your recommendation should be implemented becaue that habtm relationship is ugly and non standard anyway. Going with the hmt relationship kills two birds with one stone.
cgr
It would be worth the effort to do this.
jonnii
EmFi, thanks! So far, so good--your recommendations are in place. Just one thing: whenever I execute `Song.find(<some id>).downloaded_users`, I get back an array of associations, not an array of user objects. How do I get the array of users?
justinbach
Sorry, the command should be `Song.find(<some id>).users`. Als,o after the changes `@song.downloaded_users` shouldn't do anything.
EmFi
Thanks for the response! Hmm...problem is, I've got multiple song<->user relationships, because users can also (for example) be notified of the release of new songs (e.g. through notified_users). How can I specify the nature of the song<->user relationship in Song.find(<some id>).users?
justinbach
@justinbach: That question has a whole number of valid answers. I suggest you read up on ActiveRecord::Base::Associations or named scopes. Either can be used to solve your problem. If you still can't figure out you can always post another question.
EmFi
+1  A: 

The has_many :through is recommended when the join table has more columns than simply two foreign keys.

class User < ActiveRecord::Base
  has_many :song_downloads
  has_many :downloaded_songs,
           :through => :song_downloads,
           :source => :song
end
class Song < ActiveRecord::Base
  has_many :song_downloads
  has_many :downloaded_users,
           :through => :song_downloads,
           :source => :user
end

Now you can get a list of users that have downloaded a particular song like so:

Song.find(1).downloaded_users
ScottJ