我的一些课程:
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)
我目前只需要发件人姓名。
答案 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
)会很棒