Reputation: 227
I have 2 models: Comment and ReadReceipt. A Comment has many ReadReceipt for a given User. I want to create an ActiveRecord relation that would allow me Return all Comments with their ReadReceipt even if it doesn't exist. For example,
comments = Comment.includes(:read_receipts).all
comments.first.read_receipt -> #<ReadReceipt id: 1>
comments.last.read_receipt -> nil
Currently I have this #read_receipt on Comment. However, as I want to avoid N+1 queries I'm not sure what the best way to do this is. Do I need to do an left outer join? What's the Rails way to achieve this?
def read_receipt(user)
receipt = ReadReceipt.find_by(feed_item_id: id, contact_id: user.contact.id)
receipt ? true : false
end
Upvotes: 0
Views: 90
Reputation: 13014
I assume your join column is feed_item_id
in ReadReceipt
.
For Rails 4, use includes
and do LEFT OUTER JOIN manually, this will save you from N+1 queries and will also give all those comments where read receipt doesn't exist:
comments = Comment.includes(:read_receipts).joins('LEFT OUTER JOIN read_receipts on read_receipts.feed_item_id = comments.id')
comments.map do |c|
c.read_receipt(some_user)
end
Because of includes
earlier, read_receipts are already loaded in memory, using ActiveRecord for querying in read_receipt
will run the query again with more parameters. You can use ruby instead if you want to get away with that. You can use loaded?
to check if association is loaded or not.
Change your Comment#read_receipt
to:
def read_receipt(user)
# this will not load read receipts again in memory or fire any other query with subparameters
if read_receipts.loaded? # this means association is already loaded, use Ruby
read_receipts.find { |r| r.feed_item_id == id && r.contact_id == user.contact_id } ? true : false
else
# do not load all read_receipts, instead use proper ActiveRecord
read_receipts.find_by(contact_id: user.contact.id) ? true : false
end
end
For Rails 5, use left_outer_joins
:
comments = Comment.includes(:read_receipts).left_outer_joins(:read_receipts)
comments.map do |c|
c.read_receipt(some_user)
end
Upvotes: 3