prettymuchbryce
prettymuchbryce

Reputation: 131

has_many and belongs_to of the same model

I am pretty new to rails. I am trying to figure out the most efficient way to create a relationship between two models that states:

  1. A user can "favorite" many songs
  2. A song has an owner.

This is what I am thinking of doing. Does it make sense ?

class User < ActiveRecord::Base
    has_many :songs #songs this user has favorited
end

class Song < ActiveRecord::Base
    belongs_to :user #the user whom submitted this song
end

My concern about this method is that I'm unsure about the efficiency of doing query on every song in the database just to figure out which songs a particular user owns. Is there a different way I should be thinking about this ?

By the way, is there a method by which I can call the attribute something different than it's model name. So rather than User.find(1).songs[0] I could say User.find(1).favorites[0] even though the model is still a "Song".

Upvotes: 2

Views: 3490

Answers (3)

PinnyM
PinnyM

Reputation: 35531

You'll need 2 separate relationships between the User and Song models. Namely, you'll need an 'owner' relationship and a 'favorite' relationship. The 'owner' relationship can be a simple has_many/belongs_to as you have it now. The 'favorite' relationship is many-to-many and will need a join table used either as a habtm table or a first class model with a has_many through relationship as explained here.

The generallly recommended approach is to use has_many through as it gives you better control:

class User
  has_many :songs                                            # these are songs 'owned' by the user
  has_many :user_favorite_songs
  has_many :favorite_songs, :through => :user_favorite_songs # these are the favorites
end

class Song
  belongs_to :user
  has_many :user_favorite_songs
end

class UserFavoriteSong
  belongs_to :user
  belongs_to :favorite_song, :class_name => 'Song', :foreign_key => :song_id
end

Upvotes: 7

jacobsimeon
jacobsimeon

Reputation: 2188

I haven't tested this code, but you'll need something like this.

class User < ActiveRecord::Base
  has_and_belongs_to_many :favorites, :class_name => "Song" #user's favorited songs
end

class Song < ActiveRecord::Base
  belongs_to :user #the user who submitted the song
  has_and_belongs_to_many :user, :as => :favorite
end

And since multiple users can favorite a song, you'll need a 'join table'

CreateUsersFavorites < ActiveRecord::Migration
  def up
    create_table :users_favorites do |t|
      t.references :user
      t.references :favorite
    end
    create_index :users_favorites, :user_id
    create_index :users_favorites, :favorite_id
  end
  def down
    drop_table :users_favorites
  end
end

Also, I highly recommend taking a look at the rails guide for active record relationships.

Upvotes: 1

glasz
glasz

Reputation: 2565

This looks perfectly fine.
Rails associations try to be most efficient - don't prematurely optimize.

You can alias the association's name like so:

class User < ActiveRecord::Base
  has_many :favorites, class_name: 'Song'
end

see the docs about associations.

Regarding performance anyway, you might want to have a look at the :inverse_of association option.

Upvotes: 2

Related Questions