我有2个模型:Comment和ReadReceipt。注释具有给定用户的许多ReadReceipt。我想创建一个ActiveRecord关系,该关系将允许我连同其ReadReceipt一起返回所有注释,即使该注释不存在也是如此。例如,
comments = Comment.includes(:read_receipts).all
comments.first.read_receipt -> #<ReadReceipt id: 1>
comments.last.read_receipt -> nil
目前,我在评论上有此#read_receipt。但是,由于我想避免N + 1个查询,因此我不确定执行此操作的最佳方法是什么。我需要做一个左外部联接吗? Rails实现此目的的方法是什么?
def read_receipt(user)
receipt = ReadReceipt.find_by(feed_item_id: id, contact_id: user.contact.id)
receipt ? true : false
end
答案 0 :(得分:3)
我假设您的加入列是feed_item_id
中的ReadReceipt
。
对于Rails 4,使用includes
并手动执行 LEFT OUTER JOIN ,这将使您免于N + 1次查询,并在不存在已读回执的情况下给出所有注释:
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
由于先前有includes
,read_receipts已加载到内存中,因此使用ActiveRecord在read_receipt
中进行查询将使用更多参数再次运行查询。如果您想摆脱它,可以改用ruby。您可以使用loaded?
来检查是否已加载关联。
将您的Comment#read_receipt
更改为:
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
对于Rails 5,使用left_outer_joins
:
comments = Comment.includes(:read_receipts).left_outer_joins(:read_receipts)
comments.map do |c|
c.read_receipt(some_user)
end