如何在Rails 4中加载既存在又不存在的对象关系

时间:2018-06-20 15:45:29

标签: sql ruby-on-rails ruby-on-rails-4 activerecord

我有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

1 个答案:

答案 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