Mongoid预先加载嵌入文档
问题描述:
我的一些类:Mongoid预先加载嵌入文档
class User
embeds_many :notifications
field :first_name
field :last_name
def name{ "#{first_name} #{last_name}" }
class Notification
embedded_in :user
belongs_to :sender, class_name: "User", inverse_of: nil
现在,在我的意见,我实现了一个较小的邮箱系统通知。但是,它目前打N +数据库1次:
<% current_user.notifications.sort{...}.each do |notif|%>
...
<%= notif.sender.name if notif.sender %>
这里的问题是,这将导致数据库N
命中notif.sender.name
。我能以某种方式预加载/急切加载吗?类似于current_user.notifications.includes(:sender)
(但可以工作:D)
我目前只需要发件人姓名。
答
我觉得你在这里运气一半。 Mongoid有这样的错误信息:
在Mongoid中的预加载只支持提供参数给M.includes,它是M模型中关系的名称,并且只支持一级加载。 (即,不要在M上加载关联,但不能通过另一个关系离开一步)。
注意最后括号中的一句特别是:不
预先加载关联的M,但通过其他关系一步之遥不允许
嵌入功能的关系,但你要将includes
应用于嵌入关系,这对Mongoid来说太过分了。
的fine manual并说:
这对于通过
belongs_to
引用另一个集合以及嵌入关系的工作。 。
但是这意味着你会叫上嵌入的关系includes
而不是什么模型嵌入在你的情况,这意味着你可能急于负载发件人每一套嵌入式的通知:
current_user.notifications.includes(:sender).sort { ... }
这仍然留给您N+1
问题,急切的加载应该得到解决,但您的N
将更小。
如果这仍然太重,那么您可以将名称非规范化到每个嵌入文档中(即复制它,而不是通过sender
引用它)。当然,如果允许人们更改姓名,你需要保留副本。
答
这并不完美,但this article提出了一种可能的解决方案。
您可以加载所有发件人并使用set_relation来避免每次加载它们。
def notifications_with_senders
sender_ids = notifications.map(:sender_id)
senders = User.in(id: sender_ids).index_by(&:id)
notifications.each do |notification|
notification.set_relation(:sender, senders[notification.sender_id])
end
end
将是巨大的有,作为一个Relation
方法(如钢轨includes
活动记录)
哈哈,其实我的问题是风马牛不相及。我有一个奇怪的错误“错误的参数数量......”:工作查询是'current_user.notifications.includes(:sender).to_a.sort'。 'to_a'在这里很重要! – 2015-04-07 23:34:23
@CyrilDD它可能执行得更好,如果你想在你的查询中包含排序,而不是在本机数组上进行ruby排序 – ericraio 2015-04-30 02:54:58