Querying many to many relation in Ruby on Rails - ruby-on-rails

Let's say I have an app where users could rate books. Tables are users(id), books(id) and rating(user_id, book_id, value). I've made these models
class Rating < ActiveRecord::Base
belongs_to :user
belongs_to :book
end
class User < ActiveRecord::Base
has_many :ratings
end
class Book < ActiveRecord::Base
has_many :ratings
end
I want to get a list of all (both rated and unrated) books with their ratings made by current user. It's easy in SQL with outer join but I can't figure out a way to do it in Rails 3.

According to LEFT OUTER joins in Rails 3 you'll have to specify the outer join in SQL...

it's quite simple in rails too. You probably should add a relationship in user with books as well.
class User < ActiveRecord::Base
has_many :ratings
has_many :users, :through => :ratings
end
current_user.books.includes(:ratings).all
should work.

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

How to reference a field in the join model of a has_many though Rails 5 relationship

I am working on an app where users have many quizzes and quizzes can have many users. I have set the relationships:
class User < ApplicationRecord
has_many :studies
has_many :quizzes, through: :studies
end
class Quiz < ApplicationRecord
has_many :studies
has_many :users, through: :studies
end
class Study < ApplicationRecord
belongs_to :user
belongs_to :quiz
end
I have a field in the Study table to store the score that the user made on the quiz, but I am unable to access the field. I have tried #quiz.studies.score and #quiz.study.score but Rails give me an undefined method. How to I access the field in a join model of a has_many though relationship?
#quiz.studies return the collection of studies. So you have to use first, last, each to get the score of the specific studies.
Try this:
#quiz.studies.first.score

Two Linked Models + User Model?

I am trying to create the following basic structure with RoR. The key is that all Users also be linked to a School and a Major. Users will write articles based on their School and Major. The linking is not exclusive: many users can be in one of many schools, and one of many majors. However, each user cannot be in more than one school, and cannot be in more than one major. Eventually, I would like to be able to show posts/filter articles based on the following:
All Users in both Major X and at School Y
All Majors at School Y
All Schools with Major X
I've done a little research, not sure if any of this is correct... (still learning) should I be using has_and_belongs_to_many below as compared to has_many?
Table
major_schools #(linking the two models below)
Models
class School < ActiveRecord::Base
has_many :major_schools
has_many :majors, :through => :major_schools
end
class Major < ActiveRecord::Base
has_many :major_schools
has_many :schools, :through => major_schools
end
#school.majors #now gives a list of all the majors this school has
#major.schools #still gives a list of all schools that have this major
What I need to do is also incorporate a user model with the two above:
class User < ActiveRecord::Base
has_and_belongs_to_many :major_schools
end
And I am quite stuck... How can I pull in the User model data to the above models?
Your domain model is a tad tangled here, but it works.
Here is one way to load all Users in both Major with id X and at School with id Y:
class MajorSchool < ActiveRecord::Base
belongs_to :major
belongs_to :school
has_and_belongs_to_many :users
end
# Load all users from this school/major combination
MajorSchool.where(major_id: X, school_id: Y).users
Why not simply do:
class School < ActiveRecord::Base
has_many :major_schools
has_many :majors, :through => :major_schools
has_many :users
end
class Major < ActiveRecord::Base
has_many :major_schools
has_many :schools, :through => major_schools
has_many :users
end
class User < ActiveRecord::Base
belongs_to :school
belongs_to :major
end
Then you should be able to do:
# all students of the school
#school.users
# all students of the school and major (each line should return the same results)
#school.users.where(major_id: #major.id)
#major.users.where(school_id: #school.id)
User.where(school_id: #school.id, major_id: #major.id)

Creating a many-to-many relation in Rails

So I'm Rails n00b and I want to create a "favorites" relationship such that a User can have many favorite Item. I'm not entirely sure how to do this, this is how I'm going to try but I'm not sure if this is a good practice at all:
class User < ActiveRecord::Base
has_many :favorites
//other code
end
class Favorite < ActiveRecord::Base
belong_to :user
has_one :item
end
class Item < ActiveRecord::Base
belongs_to :item
end
Is this a good way to do it? Should I be using has_and_belongs_to_many ?
I'm specially concerned in the following scenario: Say a user has 100 favorite items.
When I do a User.find(id) will I also be retrieving the 100 favorites and the 100 Items?
In case it's important: ruby version 1.9.3, rails version 3.2.11
Can you try has_many => :through?
class User < ActiveRecord::Base
has_many :favorites
has_many :items, :through => :favorites
//other code
end
In your case has_many :through is definitely the way to go. I would recommend reading: http://guides.rubyonrails.org/association_basics.html
Of particular interest with regard to your question:
2.8 Choosing Between has_many :through and has_and_belongs_to_many
Rails offers two different ways to declare a many-to-many relationship between models. The simpler way is to use has_and_belongs_to_many, which allows you to make the association directly:
class Assembly < ActiveRecord::Base
has_and_belongs_to_many :parts
end
class Part < ActiveRecord::Base
has_and_belongs_to_many :assemblies
end
The second way to declare a many-to-many relationship is to use has_many :through. This makes the association indirectly, through a join model:
class Assembly < ActiveRecord::Base
has_many :manifests
has_many :parts, :through => :manifests
end
class Manifest < ActiveRecord::Base
belongs_to :assembly
belongs_to :part
end
class Part < ActiveRecord::Base
has_many :manifests
has_many :assemblies, :through => :manifests
end
The simplest rule of thumb is that you should set up a has_many :through relationship if you need to work with the relationship model as an independent entity. If you don’t need to do anything with the relationship model, it may be simpler to set up a has_and_belongs_to_many relationship (though you’ll need to remember to create the joining table in the database).
You should use has_many :through if you need validations, callbacks, or extra attributes on the join model.
It is better than using has_and_belongs_to_many.
When I do a User.find(id) will I also be retrieving the 100 favorites
and the 100 Items?
No. You'll just get the user object.
Update:
Calling User.include(:favourites, :items).find(id) will get you joined tables in case you want to make many calls to items table from user object.

Ruby on Rails 3.1: Am I setting this relationship up correctly?

I am making my first app in Ruby on Rails 3.1....Do I have these relationships setup correctly? Essentially, a student/client will be able to login and rate a teacher. A client can have many teachers and a teacher can have many clients. Each client can create a rating for a particular teacher (a teacher can't rate clients). Ratings are optional.
I intend to be able to display a teacher's ratings from various clients and also allow clients to login and rate all the teachers they've had.
class Client < ActiveRecord::Base
has_many :ratings
has_and_belongs_to_many :teachers
end
class Teacher < ActiveRecord::Base
has_many :ratings
has_and_belongs_to_many :clients
end
class Rating < ActiveRecord::Base
belongs_to :teacher
belongs_to :client
end
I'd say that the usage of has_and_belongs_to_many should be used when you only have a database table and not a Rails model to join the models. In your case, since you do have a model called Rating then I'd say it is better to use has_many, :through.
To accomplish that, change your Teacher and Client models to look like this:
class Client < ActiveRecord::Base
has_many :ratings
has_many :teachers, :through => :ratings
end
class Teacher < ActiveRecord::Base
has_many :ratings
has_many :clients, :through => :ratings
end
The Rating model does not need any changing.

Resources