Mongoid渴望加载嵌入式文档

时间:2015-04-06 13:43:23

标签: ruby-on-rails optimization mongoid eager-loading

我的一些课程:

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 %> 

这里的问题是notif.sender.name会导致N点击数据库。我可以以某种方式预加载/急切加载吗?像current_user.notifications.includes(:sender)之类的东西(但可行:D)

我目前只需要发件人姓名。

2 个答案:

答案 0 :(得分:3)

我认为你在这里运气不好。 Mongoid有一条错误消息,如:

  

Mongoid中的急切加载仅支持向M.includes提供参数,这些参数是M模型上关系的名称,并且仅支持一级的预先加载。 (即,不允许加载关联不在M上但通过另一种关系离开一步)。

特别注意最后一个带括号的句子:

  

不允许加载关联不在M上但通过另一种关系一步之遥

嵌入是一种关系,但您希望将includes应用于嵌入式关系,而且对于Mongoid来说,这是一个太过分的步骤。

fine manual确实说:

  

这适用于通过belongs_to引用其他集合的嵌入式关系。

但这意味着您要在嵌入式关系上调用includes而不是嵌入模型的内容。在您的情况下,这意味着您可以为每组嵌入式通知急切加载发件人:

current_user.notifications.includes(:sender).sort { ... }

这仍然会给您带来N+1问题,即急切加载应该绕过但N会更小。

如果它仍然太重,那么您可以将名称反规范化为每个嵌入文档(即复制它而不是通过sender引用它)。当然,如果允许人们更改姓名,您需要维护副本。

答案 1 :(得分:1)

这并不完美,但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方法(如Rails Active Record的includes)会很棒