I am struggling with associations with my models on ruby on rails - ruby-on-rails

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.

Related

How to create a many to many relation in Rails

So currently i have in a app:
class Post < ApplicationRecord
belongs_to :category
end
and
class Category < ApplicationRecord
has_many :posts
end
which works fine as expected. However, i need to add multiple categories to a post . I thought about using has_many_and_belongs_to for each of them to acquire this but some trouble implementing this. It seems like a need to add an join table? If so, how would one look like with the setup shown above?
Any ideas?
I appreciate any input! Thanks in advance!
The table should be named categories_posts (categories comes first because of alphabetical sequence) and contain post_id and category_id integer columns (indexed, most probably). The it's as simple as:
class Post < ApplicationRecord
has_and_belongs_to_many :categories
end
class Category < ApplicationRecord
has_and_belongs_to_many :posts
end
You can create join table adding migration using:
rails g migration CreateJoinTableCategoryPost category post
Alternatively you can use has_many :through to have more control over a join table.
Advantages of using :through for many to many relationships
With has_many :through relationship you can have a model which will allow you to add validation, callbacks.
If you initially take some extra efforts to setup many to many relationship using through it can save a lot of time and headache in future
What if in future you want save the more information on join table like some custom sort, information about how the tables are associated which will not be allowed with has_and_belongs_to_many
Example
class Post < ApplicationRecord
has_many :categories, through: :post_categories
has_many :post_categories
end
class Category < ApplicationRecord
has_many :posts, through: :post_categories
has_many :post_categories
end
Adding relationship model with rails generator command
rails g model post_category category_id:integer post_id:integer custom:text
class PostCategory < ApplicationRecord
belongs_to :category
belongs_to :post
end

Which Association Should I Use?

My app has 5 core models. I'm trying to figure out the best way to associate the models. How many tables should I build and which kind etc?
Here are the associations I would like to include and their respective models:
User
Has many boards
Has many lists
Has many cards
Has many comments
Board
Has many users
Has many lists
Has many cards
List
Belongs to board
Has many cards
Card
Belongs to board
Belongs to list
Has many comments
Comment
Belongs to card
Belongs to user
class User < ActiveRecord::Base
has_and_belongs_to_many :boards
has_many :lists, as: listable
has_many :cards, as: cardable
has_may :comments, as: commentable
end
class Board < ActiveRecord::Base
has_and_belongs_to_many :users
has_many :lists, as: listable
has_many :cards, as: cardable
end
class Comment < ActiveRecord::Base
belongs_to :commentable, :polymorphic => true
end
class List < ActiveRecord::Base
belongs_to :listable, :polymorphic => true
has_many :cards, as: cardable
end
class Card < ActiveRecord::Base
belongs_to :cardable, :polymorphic => true
has_many :comments, as:commentable
end
To establish HABTM relation you have to create a table named 'users_boards'
As Board and User are having many to many relationship, there will be a new table for it, if you want HABTM you can use it.
User(id, name, other_attributes...)
Board(id, name,...)
List(id, name, user_id(fk),...)
Card(id, name, user_id(fk), list_id(fk), board_id(fk),...)
Comment(id, comment_msg, user_id(fk), card_id(fk),...)
Board_User(board_id(fk), user_if(fk)) --- M-M relation
Few attributes might change if there is a has_many through relation.
FK-- Foreign key, you can use has_many through depending on your requirements.
Using polymorphic associations has some limitations, so please do through it then decide to use a polymorphic association
http://codeblow.com/questions/pros-and-cons-for-ruby-on-rails-polymorphic-associations/

has_many :through relationships explained

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

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:
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.

Two has_many links between the same models

I have users which have products through a habtm link, which is working.
I want to add a link between the user model and the product model, to keep track of the creator of that product (who doesn't always own the product, of course)
But when I write in my user and product models a new link, the application screws up because I can't distinguish the creator of a product from the owner of (a lot of) products.
Can you help me ? Here is my models :
class Product < ActiveRecord::Base
belongs_to :group
has_and_belongs_to_many :authors
has_and_belongs_to_many :users # THIS IS OK (with appart table)
has_many :users, :as => creator # THIS LINE DOES NOT WORK AT THE MOMENT
end
class User < ActiveRecord::Base
has_and_belongs_to_many :products
belongs_to :user # THIS LINE DOES NOT WORK AT THE MOMENT
default_scope :order => "username ASC"
end
The database is ok, and I can store the user_id under the creator column from my product, but the link product.creator.name doesn't work (because of the model is not correct, I presume), I can only read the user_id which is in the column but not get the user object with all his attributes.
rem : user.products works perfectly, but only when I remove my new link for creator...
Thanks !
The :as syntax is for polymorphic associations - this is not what you want. Your comments about your column names are a bit ambiguous, so I'm going to assume that you have a user_id column in your products table which is the id of the creator of that product (I'm only including the relevant associations)...
class Product < ActiveRecord::Base
has_and_belongs_to_many :users
belongs_to :creator, :foreign_key => :user_id, :class_name => "User"
end
class User < ActiveRecord::Base
has_and_belongs_to_many :products
has_many :owned_products, :class_name => "Product"
end
There is a nice gem for that: https://github.com/spovich/userstamp
I used it in my project and it works good - you just adds 'stampable' to your models and then creator_id (and updater_id) are filled authomaticly.

Resources