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:
A user can "favorite" many songs
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".
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
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.
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.
Related
I created a new rails application called imdb. I have created two models (via scaffolding) called User and Movie. I ran this in terminal.
rails g scaffold Movie title:string review:text location:string
rails g scaffold User name:string password_digest:string
I am finding it hard to imagine the associations that can be implemented into my application and need some help figuring this through.
We have these associations:
belongs_to,
has_one,
has_many,
has_many :through,
has_one :through,
has_and_belongs_to_many
I have so far thought that a 'user' can have many 'movies' and a 'movie' can have many 'users', but unfortunately my mind has gone blank! Help would be greatly appreciated.
Thanks.
This probably seems confusing to you because there may not be any direct relationship between movies and user. If you had a third model Review this may make more sense.
class Movie < ActiveRecord::Base
has_many :reviews, inverse_of: movie
end
class User < ActiveRecord::Base
has_many :reviews, inverse_of: user
end
class Review < ActiveRecord::Base
belongs_to :movie
belongs_to :user
end
I would create something like
Class User
has_many :movies
Class Movie
Belong_to :users
has_many :reviews :as => :reviewable
class Review
Belongs_to :movie
or
belongs_to :user
belongs_to :reviewable :polymorphic => true (this way this class can be reused later for more than movies and always written by user)
has_many :comments :as => :commentable etc.
It is similar to a case where A user may have many roles, and a role may have many users when you work with permissions (admin, user, guest, banned, etc...).
If I wanted to create a model that could be used in association with other models independently I would use a Polymorphic association as Richardlonesteen. ex: I want to have reviews written by users for books and movies, but a review belongs either to a book or movie and not both.
Your solution is to use the has_many :through association. You will have 3 models: User, Movie, and another model that will tie the relationship between the Users and the Movies. For example purpose we will call this third model Screening, but it can be anything else. When you create you Screening model, you will also need a migration with two columns user_id:integer movie_id:integer, and make sure you index them both.
Your user.rb
class User < ActiveRecord::Base
has_many :movies, :through => :screenings
has_many :screenings
attr_accessible :username
end
Your movie.rb
class Movie < ActiveRecord::Base
has_many :users, through => :screenings
has_many :screenings
attr_accessible :name
end
Your screening.rb
class Screening < ActiveRecord::Base
# attributes are :user_id and :movie_id
belongs_to :user
belongs_to :movie
end
Resulting with a relationship table that would look something like this
user_id | movie_id
------------------
1 | 1
------------------
1 | 2
------------------
2 | 1
------------------
3 | 2
Once you have this setup, you can fetch all movies of a user by doing #user.movies and vice-versa #movie.users. Rails will know how to find the associations.
I have a User model and a product model.
User has_many :products, :dependent => :destroy
Product belongs_to :user, :foreign_key => "user_id", touch: true
I want to create a wishlist for every user.
So i have to create a wishlist model with proper association.
But i don't know how to start.
I presume that the wishlist model contain an id, user_id and product_id field
Do i have to use has_many through association or a has_and_belongs_to_many ?
I also want that if a user is destroyed to destroy his wishlist.
What is the best way to do?
Many thanks!
As #JZ11 pointed out, you shouldn't be linking a Product directly to a User (unless a User actually 'owns' a product for some reason). However, what was missed is the model that makes up a Wishlist item:
class User < ActiveRecord::Base
has_many :wishlists # or has_one, depending on how many lists a User can have...
end
class Product < ActiveRecord::Base
has_many :wishlist_items
end
class Wishlist < ActiveRecord::Base
belongs_to :user
has_many :wishlist_items
has_many :products, :through => :wishlist_items
end
class WishlistItem < ActiveRecord::Base
belongs_to :product
belongs_to :wishlist
end
Naturally, you should be adding :dependent => :destroy where necessary.
You don't need the has_many :products relationship on User.
I don't think it makes sense for User and Product to be linked outside of a Wishlist.
class Wishlist < ActiveRecord::Base
has_many :products
belongs_to :user
end
class User < ActiveRecord::Base
has_one :wishlist, dependent: :destroy
end
class Product < ActiveRecord::Base
belongs_to :wishlist
end
To create your join table, do:
rails g migration create_products_users_table
Once you've done that, you need to add some code, below, to create the fields in the join table. Notice the :id => false, because you do not need an id in the join table:
class CreateProductsUsersTable < ActiveRecord::Migration
def change
create_table :products_users, :id => false do |t|
t.references :product
t.references :user
end
add_index :products_users, [:product_id, :user_id]
add_index :products_users, :user_id
end
end
The code above also creates some indexes and ensures that you don't have duplicates even at the database level.
Your models would then have to look like this:
class Product < ActiveRecord::Base
has_and_belongs_to_many :users
end
class User < ActiveRecord::Base
has_and_belongs_to_many :products
end
When you destroy a user correctly, like user.destroy and not just delete it (there is a difference), then the related rows in the join table will be deleted as well. This is built in to ActiveRecord.
Notice though, that doing this will not really let you use the join table. It will accept code like user.products = [product1, product2] etc, and other goodies, but no real use of a wish list.
If you do want to use a wish list, you will have to create and use the middle join table differently, using has_many :through (I didn't check PinnyM's answer but that might be the way to do it).
I'm new to Rails and have some doubts about the kind of relationship do I need to use. Here is the case.
I have two models Offer and User, a user could belong to to many offers and offers can have many user. Also the users create the offers.
I think I have to use a has_many :through ralationship. For example I've created another model "Applicant". Applicant belongs_to user and belongs_to offer. But how is the relationship from the user and offer model? For example:
User Model
has_many :offer, :through => :applicant
Offer Model
has_many :user, :through => :applicant
My doubt is because I already have this two relationship
User Model
has_many :offers, :dependent => :destroy
Offer Model
belongs_to :user
After solve this, I guest I have to save the record in the applicant model from the applicanst_controller, right?
Thanks in advance
What you have described is a many-to-many relationship using a join table. You're actually pretty close but you just need to remove the has_many :offers, :dependent => :destroy from your user model and the blongs_to :user in your offer model. It should look something like this:
class User < ActiveRecord::Base
has_many :offers, :through => :applicants
end
class Applicant < ActiveRecord::Base
belongs_to :users
belongs_to :offers
end
class Offer < ActiveRecord::Base
has_many :users, :through => :applicants
end
You don't have to worry about the dependent destroy part as associations are automatically removed as the corresponding objects are removed. With a many to many association it doesn't really matter how you go about building the relationship. Either of the following will work:
#user.offers << #offer
#offers.users << #user
If you don't need to store any information specific to your applicant join table (e.g., time stamps, descriptions) you might instead want to look at a has_and_belongs_to_many relationship. Check out choosing between has_many_through and has_and_belongs_to_many for reference.
Edit
Heres the code for a HABTM relationship:
class User < ActiveRecord::Base
has_and_belongs_to_many :offers
end
class Offer < ActiveRecord::Base
has_and_belongs_to_many :users
end
Okay, so here is my question. I have a 3 different models, People, Roles, Client, and Store. Clients have many Stores and can also have many people. Stores have many people. People have various roles. 1 Person can work at multiple stores, and they may have different roles at each store.
For example. Joe may be an assistant manager at one store and a manager at another store. What I would like to be able to do is pull the correct roles by doing something like Store.find(1).people.find(1).roles (would return 'assistant manager' for example) or
Store.find(2).people.find(1).roles (would return 'manager' for example). Is this possible to do in ActiveRecord?
I've created a table :roles_people which has the following definition:
create_table :roles_people, :id => false do |t|
t.references :role
t.references :person
t.references :store
t.references :client
end
However i can't figure out how to get associations to work properly using this table. Can anyone point me in the right direction?
Thanks
class People
belongs_to :client
has_many :store_roles
end
class Roles
has_many :store_roles
end
class StoreRole
belongs_to :role
belongs_to :people
belongs_to :store
end
class Client
has_many :stores
has_many :people
end
class Store
belongs_to :client
has_many :store_roles
has_many :roles, :through => :store_roles
end
Assume that all of those classes inherit from ActiveRecord::Base ;)
You're going to need to setup the migration and database structure to mirror these relationships. For each belongs_to there is an :object_id field on the table reference the appropriate table's id.
Your query is going to need to look something like:
Store.find(1).roles.find(:all, :conditions => ["store_roles.person_id = ?", 1])
I would probably add a method to the store model to make this a little easier:
def roles_for(person_id)
roles.find(:all, :conditions => ["store_roles.person_id = ?", person_id])
end
This way you can find the roles using:
Store.find(1).roles_for(1)
Or, better yet:
def self.roles_for(store_id, person_id)
Role.find(:all, :joins => :store_roles, :conditions => ["store_roles.store_id = ? AND store_roles.person_id = ?", store_id, person_id])
end
Which changes our finder to:
Store.roles_for(1, 1)
I would say that this last method is the most ideal since it causes only a single query, while each of the other options execute two queries to the database per role look-up (one to find the store, and one to get the roles for a person_id). Of course if you already have the Store object instantiated then it's not a big deal.
Hopefully this answer was sufficient :)
I think what you want is has_many :through
class Person < ActiveRecord::Base
has_many :roles_people
has_many :roles, :through => :roles_people
end
class Store < ActiveRecord::Base
has_many :roles_people
has_many :people, :through => roles_people
end
You'll also need to add relationships to RolePerson:
class RolePerson < ActiveRecord::Base
belongs_to :store
belongs_to :person
has_one :role
end
Is that what you were looking for?
Very helpful link #blog.hasmanythrough.com
has_and_belongs_to_many is your friend.
class Person < ActiveRecord::Base
has_and_belongs_to_many :roles
end
That way, you can get all roles the person has by calling Person.roles.all. The resulting query is going to use the people_roles table. You can also use has_many :through but have to build model classes for the join table yourself and maintain all the associations yourself. Sometimes it's necessary, sometimes it's not. Depends on the complexity of your actual model.
Nice question. You can't do exactly what you wanted, but i guess we can come close.
For completeness, i am going to recap your datastructure:
class Client
has_many :stores
end
class Store
has_many :people
has_many :roles
end
class Person
has_many :roles
has_many :stores
end
class Role
belongs_to :store
belongs_to :person
end
You see that the role does not need the link to the client, because that can be found straightaway from the store (i am assuming a stored is "owned" by only one client).
Now a role is linked both to a person and a store, so a person can have different roles per store.
And to find these in a clean way, i would use a helper function:
class Person
has_many :roles
has_many :stores
def roles_for(store)
roles.where("store_id=?", store.id)
end
end
So you can't write something like
store.people.first.roles
to get the roles of the first person working for that store.
But writing something like:
store.people.first.roles_for(store)
is not too hard i hope.
The reason why this is so is because in the context of the person (-> store.people.first) we no longer have any notion of the store (how we got there).
Hope this helps.
You need to change your table name in people_roles and you can drop both store and client references:
create_table :roles_people, :id => false do |t|
t.references :role
t.references :person
t.references :store
end
Role is something that belongs only to people.
You then need to use has_and_belongs_to_many:
class Person < ActiveRecord::Base
has_many :roles
has_many :stores, :through => :people_roles
end
class Store < ActiveRecord::Base
has_many :roles
has_many :people, :through => :people_roles
end
Than you can query:
Store.find(1).people.find(1).roles
I have three classes; User, Feed, and Entries. Users have one or more Feeds and Feeds have one or more Entries:
class Feed < ActiveRecord::Base
has_many :entries
has_many :feeds_users
has_many :users, :through => :feeds_users, :class_name => "User"
end
Class User < ActiveRecord::Base
has_many :feeds_users
has_many :feeds, :through => :feeds_users, :class_name => "Feed"
end
class Entry < ActiveRecord:Base
belongs_to :feed
end
The User and Feed classes are related through a third table:
class FeedsUser < ActiveRecord::Base
belongs_to :user
belongs_to :feed
end
Now, my problem is that I need to get a certain user's feeds and each feed's latest n entries. I'm fiddling with something like this:
u = User.first
Feed.joins(:entries, :users).where(:entries => { :created_at => (Time.now-2.days)..Time.now }, :users => u)
My problem here is that this generates some SQL that tries to query "users" on the table "feeds" - which doesn't exist.
How do I go about creating a correct query that can give me all feeds and their latest 10 entries for a given user?
Take out has_many :feeds_users. :feeds_users shouldn't be a model.
You should keep the table, but some Rails magic goes on behind the scenes. If you have a table named feeds and a table named users, Rails derives the table name of the look-up unless you specify it differently.
For your example:
Class Feed < ActiveRecord::Base
has_many :users
end
Class User < ActiveRecord::Base
has_many :feeds
end
It will look for the relationship in the users_feeds table. If it looked like this:
Class Feed < ActiveRecord::Base
belongs_to :user
end
Class User < ActiveRecord::Base
has_many :feeds
end
It would look for the relationship in the user_feeds table.
It's linguistically correct, as many users will have many feeds in the first example, and one user will have many feeds in the second example. That's also a good indicator of whether your relation logic is correct.
It also sounds like you need the has_and_belongs_to_many relationship instead of just has_many.
For a complete run-down on all forms of model associations, check out the guide.