Creating a habtm relationship - ruby-on-rails

I'm creating a movie watchlist app in rails/angular. For this I have a table with users and a table with movies.
At the moment I create a unique record for each movie added, this results in several copies of existing records but all with different user id's. So I'm looking into the has_and_belongs_to_many relationship in rails. But I'm having some trouble using it in my app.
As I see it a user can have multiple movies, and a movie can have multiple users. So I've added this to the models,
movie.rb
has_and_belongs_to_many :users
user.rb
has_and_belongs_to_many :movies
I've also created a migration,
class AddMoviesUsersJoinTable < ActiveRecord::Migration
def self.up
create_table :movies_users, :id => false do |t|
t.integer :movie_id
t.integer :user_id
end
end
def self.down
drop_table :movies_users
end
end
But I'm unsure on how this is all connected. With the migration I've added a join table with 2 colums. Movie_id and user_id. Does the movie_id value in the movie_users table correspond to the movie_id in the movie table? Same goes for the user_id, does that correspond to the user_id in the user table?
Also, how do I connect the two id's together. Do I have to add "something" to the movies_users join table when I create a new movie?
This is my current movies_controller.rb
def create
respond_with Movie.create(movie_params.merge(user_id: current_user.id))
end
private
def movie_params
params.require(:movie).permit(:title, :image, :release_date, :movie_id)
end
//EDIT//
I've added the has_many_belongs_to relations to my movie and user model and I created a join table called movies_users with 2 colums user_id and movie_id.
I've created 2 accounts on my page and added the movie Creed with them. This is the result in my rails console Movie.all
#<Movie id: 1, title: "Creed", user_id: 1, movie_id: "312221">,
#<Movie id: 2, title: "Creed", user_id: 2, movie_id: "312221">
As you can see it still creates 2 different movies although they have the same values, except the user_id. So it looks like there's no checking to see if an value (the movie_id) already exists. I thought this was a part of the habtm relation.

Rather than habtm i would use has_many :through
class User
has_many :movies_users
has_many :movies, :through => :movies_users
class Movie
has_many :movies_users
has_many :users, :through => :movies_users
class MoviesUser
belongs_to :movie
belongs_to :user
Now you will have a single movie record per movie and create a movie_user record when a user watches a movie. You can do this RESTfully with a MoviesUsersController, which you call the create action, passing through params = {:movies_user => {:user_id => 123, :movie_id => 456}}

It is fine to use habtm in your case if you need the join table only for relationships and don't need to store extra information in the joining table. In this c\\ase records of the joining table (movies_users) are created automatically, while you are using given by the has_and_belongs_to_many methods.
Your create action may look like this:
def create
movie = Movie.create(movie_params)
current_user.movies << movie
...
end
or like this:
def create
current_user.movies.create(movie_params)
...
end
Added after the question updated
You don't need movie_id and user_id fields in your Movie model. This all should look like this:
movie = Movie.last
#<Movie id: 1, title: "Creed">
user = User.last
#<User id: 1, name: "Username", ...>
# add the movie to the user's watch list
user.movies << movie
# a record in the joining table will be created automatically
#<user_id: 1, movie_id: 1>

Related

Rails query has_many through association

I have three models associated with the following ways:
User model
has_many :project_memberships
has_many :projects, through: :project_memberships
Project model
has_many :project_memberships
has_many :users, through: :project_memberships
Project membership model
belongs_to :user
belongs_to :project
The project membership model also has additional fields like user_role, invitation_accepted etc.
I want to get all the users in a specified project, with all the project membership fields.
Example:
# user json response
[
{
id: user_id,
name: user_name,
user_role: "admin",
invitation_accepted: true
},
{
// Etc
}
]
Currently, I have something like:
def index
#project = Project.find(params[:project_id])
#team_members = #project.project_memberships
end
The team_members only returns
#<ActiveRecord::Associations::CollectionProxy [#<ProjectMembership id: "42087cd2-31f5-4453-b620-5b47a82de422", user_id: "4f428880-48d0-40d0-b6d6-eed9172ce78d", project_id: "3e758d26-7625-4cbd-8980-77085f8d38a0", role: "admin", invitation_accepted: true, job_title: nil, created_at: "2020-10-24 05:48:38", updated_at: "2020-10-24 05:48:38">]>
I am getting the user_id, but don't know how to merge the actual user fields in the above query.
You can use includes to preload data (behind the scenes, it is performing a join).
#team_members = #project.project_memberships.includes(:user)
Now you can call #team_members[0].user.name (or extract the user name from all of them) and it doesn't fire an additional database query to load the user.
Note that this does work without includes, but it will be slower, and introduces a common pitfall known as "N+1 queries"

Association Help In Rails

I'm making a movie review application. A user can scroll through the different reviews that have been created and save them to a list. In my console, I'm unable to access the user's list reviews. User.list.reviews. What am I missing? Thanks in advance!!
Here are my current models and associations.
User Model
has_one :list
has_many reviews, :through => :list
List Model
belongs_to :user
has_many :reviews
Review Model
has_many :lists
has_many :users, :through => :lists
Schema: List
user_id
review_id
In your schema, a List has one user ID and one review ID. So a List can only ever have one of those things. But you want a User to have just one List, while the List has many Reviews.
It then gets more complex, because a List can have many Reviews. But since many different Users can put Reviews into their own lists, one Review might appear in several Lists. In short, List has_and_belongs_to_many :reviews and Review has_and_belongs_to_many :lists. This means you need somewhere to put the List ID and Review ID pair that express this relationship - it's called a join table. The convention is just concatenate the two names of the related two tables to get the name of the join table - so if we have tables lists and reviews, we need a join table called lists_reviews (you can override this but it's easier to just go with the convention).
A bare minimum Rails migration would be:
create_table :users do |t|
end
create_table :lists do |t|
t.belongs_to :user # Leads to "user_id" column
end
create_table :reviews do |t|
end
create_table :lists_reviews do |t|
t.belongs_to :list # Leads to a "list_id" column
t.belongs_to :review # Leads to a "review_id" column
end
Given this, and given that with #has_one you're supposed to put #belongs_to in the thing it has, too, so List should belong_to :user, we get:
class User < ActiveRecord::Base # Rails <= v4
has_one :list
has_many :reviews, :through => :list
end
class List < ActiveRecord::Base
belongs_to :user
has_and_belongs_to_many :reviews
end
class Review < ActiveRecord::Base
has_and_belongs_to_many :lists
has_many :users, :through => :lists
end
And with all this dumped into an empty Rails shell we can test it at the console:
u1 = User.new; u1.save!
u2 = User.new; u2.save!
l1 = List.new( user: u1 ); l1.save!
l2 = List.new( user: u2 ); l2.save!
r1 = Review.new; r1.save!
r2 = Review.new; r2.save!
r3 = Review.new; r3.save!
l1.reviews << r1
l1.reviews << r2
l1.save!
l2.reviews << r2
l2.reviews << r3
l2.save!
u1.list
# => #<List id: 1, user_id: 1>
u1.list.reviews
# => #<ActiveRecord::Associations::CollectionProxy [#<Review id: 1>, #<Review id: 2>]>
u2.list
# => #<List id: 2, user_id: 2>
u2.list.reviews
# => #<ActiveRecord::Associations::CollectionProxy [#<Review id: 2>, #<Review id: 3>]>
l1.user
# => #<User id: 1>
l2.user
# => #<User id: 2>
r1.users
=> #<ActiveRecord::Associations::CollectionProxy [#<User id: 1>]>
r1.lists
=> #<ActiveRecord::Associations::CollectionProxy [#<List id: 1, user_id: 1>]>
r2.users
=> #<ActiveRecord::Associations::CollectionProxy [#<User id: 1>, #<User id: 2>]>
r2.lists
=> #<ActiveRecord::Associations::CollectionProxy [#<List id: 1, user_id: 1>, #<List id: 2, user_id: 2>]>
r3.users
=> #<ActiveRecord::Associations::CollectionProxy [#<User id: 2>]>
r3.lists
=> #<ActiveRecord::Associations::CollectionProxy [#<List id: 2, user_id: 2>]>
...it works.
Schema: List
user_id
review_id
This implies that your List belongs-to one user and also belongs-to one review... but you've defined your list associations like this:
belongs_to :user
has_many :reviews
So Rails is getting confused as to why a list has a review_id (which is a belongs_to association thing). and is looking at the Review model, hoping it will have a list_id column... because that's how you'd solve that one... and yet your Review model has many list, so it can't resolve the issue.
Can you tell us what you'd actually like to happen? How should these things be related? should we change the associations you've defined to match the id-columns or can you more completely specify the relationships between models (eg with an object diagram) and then we can tell you how to alter your id-columns/associations to match your object-diagram?
You'll need to use a has_and_belongs_to_many for this. Take a look at http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association.
The problem is that your List model has_many reviews, and your Review model has_many lists. If two models both have a has_many relation to each other, how can you model this in the database? That is, where would you put the foreign key? Whichever table has the foreign key can only have a belongs_to relation to the other table. i.e. it can only belong to one record on the other table.
The solution to this is to use a join table. In rails this will usually have a name of something like lists_reviews. This join table would have two foreign keys, one for the lists table, and one for reviews. This way, each table can have a has_many relation to the other. In rails, you can use has_and_belongs_to_many to do this. Check out the link above for more.

Rails: Assigning another user to an excisting entry (associations)

I have 2 models in my project. 1 is Users and 1 is courses.
A user has many courses
and courses has many users.
The main problem is that I can't figure out how to assign users to courses without creating a new course.
user = User.first
course = Course.new(title:"course01")
My output would then be something like
Course id: 2, title: "course01", created_at: "2016-03-20 07:05:23",
updated_at: "2016-03-20 07:05:23", user_id: 1>
Now I can't figure out how to add another user to this same course.
user = User.second
?
Remove user_id from courses table, no need of it
Create join table for HABTM
create_table :users_courses, id: false do |t|
t.references :course
t.references :user
end
In user.rb file
has_and_belongs_to_many :courses
In course.rb file
has_and_belongs_to_many :users
after creating new course
course = Course.create(title:"course01")
users = User.all or whatever you need to assign to same course
users.each do |user|
course.users << user ##assign
end
In rails console
course = Course.first or Course.find(1) or whatever you want
course.users << User.find(1) or whatever you want
Let's say a new 'UserCourseAssignment' is created when a user is assigned to a course.
You could create a new UserCourseAssignment model with attributes user_id & course_id to store which users are assigned to which courses. This new table will have a new entry every time a user is assigned to a course.
User model
has_many :user_course_assignments
has_many :courses, through: :user_course_assignments
Course model
has_many :user_course_assignments
has_many :users, though: :user_course_assignments
UserCourseAssignment model
belongs_to :user
belongs_to :course
Edit:
I think you currently have user_id attribute in Course model. You won't need it after implementing the has_many :through association.

Rails activity feed from a polymorphic following

I'm tracking the following tables:
Story (id, user_id, content)
Vote (id, user_id, story_id)
Flag (id, user_id, story_id)
etc..
with an activity table:
Activity (id, user_id,action, trackable_id, trackable_type)
The relationship table:
Relationship (id, follower_id, followed_id)
I'm currently getting the activities from users a user is following like this:
def get_activity_from_followers(current_user)
followed_user_ids = "SELECT followed_id FROM relationships
WHERE follower_id = :user_id"
where("user_id IN (#{followed_user_ids})",
user_id: user.id)
end
My question is, how do i get the activities where the the trackable table(e.g. story,vote,flag) belongs to you.
So right now i'm getting things like:
"someone you are following" posted a story
"someone you are following" voted a story
i want to also get things like:
"someone you are not following" voted your story
"someone you are not following" flagged your story
How do I go about this? Thanks.
I suggest you have following class definitions and associations:
# User
class User :trackable
end
# Flag (id, user_id, story_id)
class Flag :trackable
end
# with an activity table:
# Activity (id, user_id,action, trackable_id, trackable_type)
class Activity true
end
# The relationship table:
# Relationship (id, follower_id, following_id)
class Relationship "User"
belongs_to :following, :class_name => "User"
end
Now, Lets find activities of the users to whom you follow:
# List activities of my followings
Activity.where(:user_id => current_user.followings)
Listing my activities
current_user.activities
To solve this, I'd search into Activity using votes and flags id's instead of user id's. Something like this:
def get_votes(current_user)
stories = current_user.stories
votes = Votes.where(story_id: stories.map {|s| s.id})
activity = Activity.where(action: 'vote', trackable_id: votes.map{|v| v.id})
end
I madre some assumptions. First that you can call user.stories, and then that in Activity model 'action' can be vote or flag and that trackable_id relates to Vote and Flag id. If that's not correct, you can get the general idea anyways.
Hope it helps
What about making Activity STI class?
class Story < ActiveRecord::Base
belongs_to :user
end
class Activity < ActiveRecord::Base
belongs_to :story
belongs_to :user
end
class Vote < Activity
def to_partial_path
'users/vote'
end
end
class Flag < Activity
def to_partial_path
'users/flag'
end
end
You can store all user actions in same table and get it from there with:
Activity.where(user_id: uid).all
and you get as return array of all user actions with correct typing for every action, that allow you to implement polymorphic logic for different types of actions (as to partial path in my example, that allows you to write something like:
render Activity.where(user_id: uid).all

Rails 3, belongs_to, has one? For 3 models, Users, Instances, Books

I have the following models:
Users (id, name, email, instance_id, etc...)
Instances (id, domain name)
Books (id, name, user_id, instance_id)
In Rails 3, When a new book is created, I need the user_id, and instance_id to be populated based on the current_user.
Currently, user_id is being assigned when I create a new book but not instance_id? What needs to happen in rails to make that field get filled out on book creation?
Also, shouldn't rails be error'ing given that I can create books without that instance_id filled out?
thxs
It looks like you have de-normalized User and Book models by adding reference to Instance model. You can avoid the redundant reference unless you have a specific reason.
I would rewrite your models as follows:
class Instance < ActiveRecord::Base
has_many :users
has_many :books, :through => :users, :order => "created_at DESC"
end
class User < ActiveRecord::Base
belongs_to :instance
has_many :books, :order => "created_at DESC"
end
class Book < ActiveRecord::Base
belongs_to :user
has_one :instance, :through => :user
end
Now to create a new book for a user.
current_user.books.build(...)
To get a list of the books belonging to user's instance:
current_user.instance.books
To get a list of the books created by the user:
current_user.books
Make sure you index the instance_id column in users table and user_id column in books table.
Rails will only produce an error in this case if (a) you have a validation that's failing, or (b) you have database foreign keys that aren't being satisfied.
What's an instance? i.e. if instance_id is to be populated based on the current user, what attribute of the user should supply it? (the instance_id? why?)

Resources