category.rb
has_many :topics
topic.rb
belongs_to :category
has_many :answers
answer.rb
belongs_to :topic
Question:
How can i preform queries like Category.first.topics.answers.count
Use a has_many :through relation:
# Category.rb
has_many :topics
has_many :answers, through: :topics
Now you can access all answers from all topics like so:
Category.first.answers.count
if you are set on your schema configuration (i.e. not using a has_many :through), you'd want to start with Answers and utilize a couple of joins to get to Category
Answers.joins(topic: :category).where(categories: { id: category_id })
here we're joining on a nested association, and then using a where clause to filter out by the category_id
note: i think this is the right syntax, but you may need to fiddle around with the plurality of topic and category there
Related
I have a list of products and a list of categories. Also I have a mapping table product_categories which tells that products come under different categories and also category has many products. Here the category list is defined by admin. The number of categories is fixed, but can be varied. Now I need to get the list of products which are mapped with categories
product.rb
has_many :product_categories, dependent: :destroy
has_many :categories, through: :product_categories
category.rb
has_many :product_categories
has_many :products, :through => :product_categories
product_category.rb
belongs_to :product
belongs_to :category
I have written the code as:
ProductCategory.joins(:category).map(&:category).uniq
Is there any way to simplify this line?
To get a list of categories that have products you can use SQL inner join, which is the default in rails.
Category.joins(:product_categories).distinct
Try this,
Category.joins(:products).distinct
Say I have two models, Question which has_many Answers and Answers which belongs_to question. If in my Users model I add has_many Questions, would Users then automatically has_many Answers as well, or would I need to add has_many Answers manually?
You should read up on the "has many through" relationship.
http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
Not automatically. For users to have many answers, you'd want to use :through, e.g.,
class User < ActiveRecord::Base
has_many :questions
has_many :answers, through: :questions
end
See: http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
If you have the following setup:
class User << ActiveRecord::Base
has_many :questions
end
class Question << ActiveRecord::Base
has_many :answers
end
class Answer << ActiveRecord::Base
belongs_to :question
end
You can't get to all user's answers with a single method. You would have to get all questions as a collection and then iterate through your questions collection and get all the answers. This would obviously lead to N+1 query problem.
#user.questions.map(&:answers).flatten
To solve that N+1 query problem, you'd have to use .includes(), but again that would only solve the N+1 problem.
#user.questions.includes(:answers)
Solution: use has_many :through
If you add has_many :through into your User model:
class User << ActiveRecord::Base
has_many :questions
has_many :answers, through: :questions
end
This would allow you to access your user's answers with a single method like this:
#user.answers
A user can teach many topics, and each topic can have many lessons.
A user can join lessons created by other users.
(1) is pretty simple and I implemented it as follow:
#user.rb
has_many :topics
has_many :lessons, through: :topic
#topic.rb
has_many :lessons
#lesson.rb
belongs_to :topic
belongs_to :user
(2) is a many-to-many relationships and I think a join table - let's call it matches (extra points to help me find a better name!) - is required.
If I add the following
#user.rb
.
.
has_many :lessons, through: :match
#lesson.rb
.
.
has_many :users, through: :match
#match.rb
belongs_to :lesson
belongs_to :user
I think I will get an error since Rails cannot get the difference between the two relationships while calling #user.lessons, for example.
What can be a correct approach?
p.s. I see there are many questions similar to this one but I was not able to find a proper solution to my problem.
I think you should name the join table user_lessons rather than match being a convention in Rails for join tables.
After renaming, I think you should rather have it like this:
user.rb
has_many :user_lessons
has_many :subscribed_lessons, through: :user_lessons, source: :lesson
Also note that your user
has_many :lessons, through: :topics #not :topic
Let me know if that works.
I have the following relations:
reservation.rb
has_many :room_requests, dependent: :destroy, inverse_of: :reservation
accepts_nested_attributes_for :room_requests, allow_destroy: true
has_many :rooms, through: :room_requests
room_request.rb
belongs_to :reservation
belongs_to :room
room.rb
has_many :room_requests
has_many :reservations, through: :room_requests
And I'm trying update the attribute 'status' from rooms that belong to certain reservations. Something like:
Reservation.joins(:rooms).update_all('rooms.status': 'to_clean')
But evidently it doesn't work like this. I want to do it in a single query but I can't quite grasp it. What am I missing?
In the end I couldn't do the query that way because what I needed to update was on the Room model, and the query was being done in the Reservation model. I needed to reverse it, and came up with this:
Room.where(id: RoomRequest.where(reservation: Reservation.checked_in).select(:room_id)).update_all(status: Room.statuses[:to_clean])
You should link your Reservations model also with Rooms via a has_many, through: room_requests..
Once done you can easily go with:
Reservations.rooms.status.update_all('rooms.status': 'to_clean')
However, make sure that your room_requests are available and that reservations are thus assigned to rooms via the room_requests.
Cheers,
T
Say I have three models...
Product
belongs_to :ProductCategory
belongs_to :Manufacturer
ProductCategory
has_many :products
Manufacturer
has_many :products
I'd like to ask an instance of ProductCategory for the set of Manufacturers for Products in that ProductCategory with a call like product_category.manufacturers.
I've currently implemented it in the Products model like this:
def manufacturers
Manufacturer.find(self.products.pluck(:manufacturer_id).uniq.to_a
end
Is there a better "rails way"?
Thanks!
Yes, this is an extremely well-solved problem and a fundamentally basic part of using Associations in Rails. You want has_many :through:
class ProductCategory
has_many :products
has_many :manufacturers, :through => :products
end