Let's say I have three models (models changed, I wouldn't use a has_many :through for this kind of relationship normally):
class User << ActiveRecord::Base
has_many :image_users
has_many :images, through: :image_users
end
class ImageUser << ActiveRecord::Base
belongs_to :user
belongs_to :image
end
class Image << ActiveRecord::Base
has_many :image_users
has_many :users, through: :image_users
has_many :comments, through: :image_comments
end
class ImageComment << ActiveRecord::Base
belongs_to :image
belongs_to :comment
end
class Comment << ActiveRecord::Base
has_many :image_comments
has_many :images, through: :image_comments
end
Given a user, how do I select all comments on their images? Ideally I'd do this with ActiveRecord or SQL to avoid loading lots of objects into memory. Hopefully this hasn't been asked before, it was pretty difficult to google for.
You should be able to do this and let Rails handle the SQL:
#user.images.includes(:comments).map(&:comments)
User
has_many :images
has_many :comments
Image
belongs_to :user
has_many :comments
Comment
belongs_to :user
belongs_to :image
and it's gonna be something like this
#user.images.each {|e| e.comments}
If you take the relationships from #railsr's answer, you can do it like this:
Comment.where(image_id: #user.images.pluck(:id))
Granted, this takes two queries, but you could use some raw SQL and make it into one.
Related
I'm trying to query on ActiveRecord multiple nested eager loaded associations with conditions like so:
user.books.includes(slot: [room: :school]).where("books.slot.room.school.id = 1")
Obviously this query is wrong, but basically what I'm trying to reach is a relation of user.books for a certain school.
My model structure is:
class User < ActiveRecord::Base
has_many :books
has_many :slots, through: :books
has_many :rooms, through: :slots
has_many :schools
end
class Book < ActiveRecord::Base
belongs_to :user
belongs_to :slot
end
class Slot < ActiveRecord::Base
has_many :books
belongs_to :room
end
class Room < ActiveRecord::Base
belongs_to :school
has_many :slots
end
class School < ActiveRecord::Base
has_many :users
has_many :rooms
has_many :slots, through: :rooms
has_many :books, through: :slots
end
Any ideas? Thanks in advance.
includes eager loads association records, while joins does what you want to do.
This question has been asked here numerous times. Please make sure that you look and try to find a similar question here before you ask one.
So you want to change your code like this:
user.books.joins(slot: [room: :school]).where(schools: { id: 1 })
Rails 4.2 newbie:
2 Questions;
1) Is the first has_many redundant? Since its name is a plural of Save Class?
can I have only:
has_many :savers, through: :saves, source: :saver
Or even better;
has_many :savers, through: :saves
If the answer is yes, where can I set "dependent: :destroy"?
class Post < ActiveRecord::Base
belongs_to :user
has_many :saves, class_name: "Save", foreign_key: "saver_id", dependent: :destroy
has_many :savers, through: :saves, source: :saver
end
class Save < ActiveRecord::Base
belongs_to :saver, class_name: "User"
validates :saver_id, presence: true
end
class User < ActiveRecord::Base
has_many :posts, dependent: :destroy
...
end
2) This is the typical blog model, where user can 'save' posts posted by another user to their timeline. Does this model make use best practices? Specially in db performance, doing a Join to get posts saved by a User. The 'Save' table that will have 100MM rows?
Lets first alter your example a bit to make the naming less confusing:
class User
has_many :bookmarks
has_many :posts, through: :bookmarks
end
class Post
has_many :bookmarks
has_many :users, through: :bookmarks
end
class Bookmark
belongs_to :user
belongs_to :post
end
Lets have a look at the query generated when we do #user.posts
irb(main):009:0> #user.posts
Post Load (0.2ms) SELECT "posts".* FROM "posts" INNER JOIN "bookmarks" ON "posts"."id" = "bookmarks"."post_id" WHERE "bookmarks"."user_id" = ? [["user_id", 1]]
=> #<ActiveRecord::Associations::CollectionProxy []>
Now lets comment out has_many :bookmarks and reload:
class User
# has_many :bookmarks
has_many :posts, through: :bookmarks
end
irb(main):005:0> #user.posts
ActiveRecord::HasManyThroughAssociationNotFoundError: Could not find the association :bookmarks in model User
So no, the first has_many is not redundant - in fact its the very core of how has_many through: works. You setup a shortcut of sorts through another relation.
Note in has_many :posts, through: :bookmarks :bookmarks is the name relation we are joining through. Not the table which contains the joins.
To fix your original code you would need to do:
class Post < ActiveRecord::Base
has_many :saves, dependent: :destroy
has_many :savers, through: :saves
end
class Save < ActiveRecord::Base
belongs_to :saver, class_name: "User"
belongs_to :post # A join table with only one relation is pretty worthless.
validates :saver_id, presence: true
end
class User < ActiveRecord::Base
has_many :posts
has_many :saves, dependent: :destroy
has_many :posts, through: :saves
end
Note that you don't need half the junk - if you have has_many :savers, through: :saves ActiveRecord will look for the relation saver by itself. Also you only want to use dependent: destroy on the join model - not on the post relation as that would remove all the posts a user has "saved" - even those written by others!
Teaching Rails myself, I want to learn the professional way to use the framework and following Rails guidelines best practices. That's not easy, because I usually find answers that 'just works'
I'll try to answer myself and maybe it could be useful for Rails Newbies:
Using has_many through, association, Rails firstly infers the association by looking at the foreign key of the form <class>_id where <class> is the lowercase of the class name, in this example; 'save_id'.
So, if we have the column name 'save_id', we will have the following simplified model:
class Post < ActiveRecord::Base
belongs_to :user
has_many :saves, through: :saves
end
class Save < ActiveRecord::Base
belongs_to :savers, class_name: "User"
validates :save_id, presence: true
end
class User < ActiveRecord::Base
has_many :posts, dependent: :destroy
...
end
I searched for quite a long time and couldnt find that problem.
user.erb
has_many :workouts
has_many :result_units
workout.erb
belongs_to :user
has_many :sets
set.erb
belongs_to :workout
has_one :result_unit
result_unit.erb
belongs_to :user
belongs_to :set
1 possible Solution is that ResultUnit dont belong to User. But the question is then how much performance it will cost to query User.workouts.all.sets.all.resultunits.all
How could i create a new ResultUnit for User and Set?
This is a case for using a has_many :through association.
http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
Running User.workouts.all.sets.all.resultunits.all will result in numerous queries being executed. A has_many :through however, will execute only a single query and allow the database to optimize the joins between tables.
class User < ActiveRecord::Base
has_many :workouts
has_many :sets, through: :workouts
has_many :result_units, through: :sets
end
Ok, I didn't understand your problem 100%.. but I'm gonna have a stab at it and feel free to downvote if it's not right.
class User
has_many :workouts
has_many :result_units
has_many :sets, through: :workouts
# User.first.workouts
# User.first.result_units
# User.first.sets
end
class Workout
belongs_to :user
has_many :sets
# Workout.first.user
# Workout.first.sets
end
class ResultUnit
belongs_to :user
belongs_to :set
# ResultUnit.first.user
# ResultUnit.first.set
end
class Sets
belongs_to :workout
has_one :result_unit
# Set.first.workout
# Set.first.result_unit
end
For some reason, my has_many through association isn't working. Here are my models:
class Interest < ActiveRecord::Base
has_many :evints
has_many :events, through: :evints
has_many :images, through: :events
end
class Event < ActiveRecord::Base
has_many :evints
has_many :images
has_many :interests, through: :evints
end
class Evint < ActiveRecord::Base
belongs_to :events
belongs_to :interests
end
The Evints table has three columns: interest_id, event_id, and id.
When I call #interest.events, I get the error message
uninitialized constant Interest::Events
Obviously, there's something going wrong with the association if #interest.events is being read as a constant!
Does anyone have any ideas?
Thanks!
Check your Evint class, it should be:
class Evint < ActiveRecord::Base
belongs_to :event
belongs_to :interest
end
On a different note, I think Evint isn't really a very good name. It'd suggest that you go with EventInterest, and name the table event_interests.
class User < ActiveRecord::Base
has_many :posts
has_many :images, as: :imageable
end
class Post < ActiveRecord::Base
belongs_to :user
has_many :images, as: :imageable
end
class Image < ActiveRecord::Base
belongs_to :imageable, polymorphic: true
end
Is there a specific way where I can do User.images and get both the user's images and the post's images that belong to that user?
For some reason I can't wrap my head around how to do this best.
In that case you can avoid polymorphic relationship, simple has_many and belongs_to will suffice. where :
class User ActiveRecord::Base
has_many :posts
has_many :images
end
class Post ActiveRecord::Base
belongs_to :user
has_many :images
end
class Image < ActiveRecord::Base
belongs_to :post
belongs_to :user
end
But then again I think you wanted to ask about User.last.images and not User.images
the same can be done through has_many through associations as well