渴望加载以解决n + 1个查询Rails

时间:2015-04-02 10:45:20

标签: ruby-on-rails postgresql

我有一个

User 
has_many :friends, through: :friendships
has_many :blocked_friends, through: blocked_users

通过网络方法,我试图让当前用户分为两个级别的朋友。 问题是第一次运行此方法会为每个第一级朋友查找提供个人查询。在我第二次运行它之后它应该像它应该的那样(给我6个查询的常量)。这有什么问题?

def network
    User.includes(friends: [:friends]).find(self.id)        
    all_friends = []
    all_friends << self # add current user
    all_friends += self.friends # add user's friends
    self.friends.each {|i| all_friends += i.friends} # add user's friend friends
    all_friends.uniq - self.blocked_friends # take away blocked friends
end

1 个答案:

答案 0 :(得分:0)

您的问题是您对数据库进行缓存,而不是在Rails应用程序上缓存。

让我们来看看您的network方法:

def network
    User.includes(friends: [:friends]).find(self.id)
    # Will do some queries like your User selection,
    # its friend selection, and sub-friends selection.
    # SQL queries are done, but the result is sent to nowhere.

    all_friends = []
    all_friends << self # add current user
    all_friends += self.friends # add user's friends
    # Now, you request AGAIN the friends of the user, but the
    # variable is not the same ! self is not equal to
    # User.find(self.id). It represents the same object, but
    # not exactly the same object_id in your Rails application.
    # In other words,
    # self == User.find(self.id) => true
    # self === User.find(self.id) => false !

    # [ ... ]
end

为了提高效率,你应该这样做:

def network
  self_with_eager_loading = User.includes(:friends => :friends).find(self.id)
  all_friends = []
  all_friends << self_with_eager_loading
  all_friends += self_with_eager_loading.friends
  self_with_eager_loading.friends.each {|i| all_friends += i.friends}
  [ ... ]
end

在此示例中,未使用变量self(不包含预先加载)。仅使用self_with_eager_loading

只是一点点说明:你不应该这样做。在实例类方法中使用User.find(self).id很糟糕。为了漂亮,您的代码应如下所示:

def network
  all_friends = []
  all_friends << self
  all_friends += self.friends.includes(:friends)
  self.friends.each {|i| all_friends += i.friends}
  [ ... ]
end

当您执行此操作并且退出此方法时,您的变量self始终包含friends和子friends,您可以在任何地方使用它。