Rails - 在没有表连接的情况下消除n + 1个查询

时间:2015-09-04 02:58:18

标签: ruby-on-rails performance caching activerecord

我有一些n + 1个查询,这使得我网站的加载时间非常慢。它就像Facebook一样是一个社交媒体网站,n + 1查询的来源是朋友。详情 -

友谊表有3列 - User_id(发送请求的用户),friend_id(获得请求的用户)和pending,这是一个布尔值,表示友情是否被接受。

朋友请求使用

在我的用户模型中实现
def friend_requests
  friend_ids = Friendship.where('friend_id = ? AND pending = true', self.id).all
end

我特别需要友谊ID,因为如果用户想要接受或拒绝请求,我需要更新待处理的布尔值。

友谊模型有belongs_to =>朋友协会宣布。

belongs_to :friend,
  class_name: "User",
  foreign_key: :friend_id,
  primary_key: :id

n + 1的来源。对于视图,当我想要获取用户收到的好友请求时,我还想要包括发送请求的用户的姓名和个人资料图片。它看起来像这样 -

json.friend_requests @user.friend_requests.includes(friend: :name) do |friendship|
  json.extract! friendship, :id, :user_id
  json.name User.find(friendship.user_id).name # n + 1
  json.profile_pic User.find(friendship.user_id).profile_pic # n + 1
end

我最初对include(friend :: name)的语法有些怀疑,但我已经尝试了this thread中提到的所有排列,但它给了我

  

名为'姓名'在用户上找不到;也许你拼错了   它?

哪个是正确的,因为name是User的属性而不是关联。

我能想到的解决这个问题的唯一方法是将我的friend_requests表更改为find_by_sql查询,看起来像这样 -

SELECT f.id, f.user_id, f.friend_id, f.pending, users.name, users.profile_pic
FROM friendship AS f
JOIN users ON users.id = friendship.friend_id
WHERE f.friend_id = ? AND pending = true

它会告诉我用户的名字和profile_pic,但我不想这样做,因为它看起来很脏但更重要的是我想知道是否有更好更智能的方法来做到这一点

1 个答案:

答案 0 :(得分:2)

来自#includes上的API文档:

  

指定要包含在结果集中的关系。

关键字是关系:您指定要包含的整个关系,而不是字段。

你应该试试这个:

@user.friend_requests.includes(:friend) do |friendship|

  # whatever you need before

  # access the friend's name through the friend relationship
  # this should not require another query as the friend object was
  # preloaded into friendship using #includes
  friendship.friend.name

  # whatever you need after

end

编辑:实际上,这将触发2个SQL查询,第一个获取用户的friend_requests,然后第二个获得所有friend关系。 第二个查询的格式为

SELECT "users".* FROM "users" WHERE "users"."id" IN (/*LIST OF IDs*/)

使用第一个friend_requests查询中找到的所有ID。

如果您只想触发一个查询,则应尝试JOIN,可能是这样的:

@user.friend_requests.joins(:friend).includes(:friend)