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"
Related
Summary
I have acts_as_taggable_on :skills in my Ad.rb Model.
I have the following relationships:
Host Model has_many :locations
Location has_many :ads
Ad acts_as_taggable_on :skills
I need the Host list of skills. For every host I need to retrieve the list of skills that he has through his ads.
I would need to query them and I don't know how to do it with the ActsAsTaggableOn object.
What I know
I can easily retrieve all the skills of a Location (number 2). Every host can have multiple locations.
Location.ads.tag_counts_on(:skills)
This query return an object containing, for each Location his Skills, this is the result:
=> [#<ActsAsTaggableOn::Tag:0x0000000943b878 id: 6, name: "javascript", taggings_count: 4>,
#<ActsAsTaggableOn::Tag:0x0000000943b738 id: 4, name: "css", taggings_count: 6>,
#<ActsAsTaggableOn::Tag:0x0000000943b5f8 id: 8, name: "rubyonrails", taggings_count: 2>]
This is the class of this object:
=> ActsAsTaggableOn::Tag::ActiveRecord_Relation
On this class I can call methods like .find(), but If i copy this data in an Array then I can not perform the .find() query, to check if I am adding a duplicate.
I just need possibly an array of this skills like:
skills = ["css","html","javascript"]
So that I can perform this query:
#developers = Developer.tagged_with(skills, :any => :true)
The last query works, I just need to find out what is the best way to retrieve the skills.
This is acs-as-taggable-on gem github page
https://github.com/mbleigh/acts-as-taggable-on
I also add the git of this project
https://github.com/fabriziobertoglio1987/SocialNetwork
Thanks a lot
Fabrizio
What about
class Host
has_many :ads, through: :locations
has_many :skills, through: :ads
end
host = Host.first
skills = host.skills.pluck(:name)
#developers = Developer.tagged_with(skills, any: :true)
Given this model relationships:
orders.rb
belongs_to user
user.rb
belongs_to company
company.rb
has_many users
I wanted to count all orders made by each company, so I got this:
Order.joins(user: :company)
.group(['companies.name, companies.id])
.count
and the result is
["Company 1", 1] => 123
BUT I don't know how to change this query to have
<Company id: 1,name: "Jim Beam"> => 5
instead of array of Company's attributes.
This can be made easier by adding another association to the model:
# company.rb
has_many :users
has_many :orders, through: :users
Then you can do this:
companies = Company.
joins(:orders).
group("company.id").
select("company.id, company.name, count(orders.id) as orders_count")
if you then say companies.first you will see something like this:
#<Company:0x005610fa35e678 id: 15 name: 'foo'>
it doesn't look like the orders_count is there. But it actually has been loaded already. You can get at it like this:
companies.first.orders_count
You can also see it if you convert the company to a hash:
companies.first.attributes # => { id: 15, orders_count: 2, name: "foo" }
In Postgres at least, if you want to get more data in the attributes hash you have to individually specify each of the columns in the select string. I'm not sure about other databases.
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.
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.
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>