Rails - 渴望加载2个表,但过滤一个 - 修复N + 1

时间:2016-04-18 19:42:28

标签: sql ruby-on-rails postgresql activerecord

我有三种模式:

class User < ActiveRecord::Base 
  has_many :destinations, :through => :trips
  has_many :destination_reviews

class Destination < ActiveRecord::Base
  has_many :users, :through => :trips
  has_many :destination_reviews

class DestinationReview < ActiveRecord::Base
  belongs_to :user
  belongs_to :destination

不同的用户可以为同一目的地留下评论。

我们正在构建一个表格,显示属于用户的所有目的地。用户已经为某些(但不是全部)目的地留下了评论。

控制器:

@user = User.find_by_id(params[:id])
@user_reviews = @user.destination_reviews

查看:

<% @user.destinations.each do |destination| %>
  <% review = @user_reviews.find_by_destination_id(dest.id) %>
  <% if review %>
    <div class="review>
      ...show the review
    </div>
  <% else %>
    <div class="add-review>
      ...prompt user to add a review
    </div>
  <% end %>
<% end %>

N + 1发生在@user_reviews.find_by_destination_id(dest.id),其中@user_reviews是用户评论的预加载列表。

如何热切加载属于该用户的所有目的地,包括用户为这些目的地留下的任何评论?

我正在寻找一些类似的查询:

dest_ids = current_user.destinations.pluck(:id)`
Destination.includes(:destination_reviews).where(id: dest_ids).where('destination_reviews.user_id = ?', user_id)

但是这会引发错误: ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR: missing FROM-clause entry for table "destination_reviews"

或者,如果有一种方法可以在不重新查询数据库的情况下编写@user_reviews.find_by_destination_id(dest.id),那就行了。

如果清楚或我是否应该添加更多详细信息,请告诉我

1 个答案:

答案 0 :(得分:1)

最简单的方法是使用group_by

@user_reviews = @user.desintation_reviews.group_by(&:destination_id)

然后在视图中

<% review = @user_reviews[dest.id] %>

它为您提供一个查询以获取用户的目的地,另一个用于获取他们的评论。

或者你可以将它归结为一个带有一些连接的查询,并使用selectDestination上创建一些伪属性,然后你可以从{{1 }}。但是它会变得更复杂,特别是因为你正在通过一个连接表。这将是这样的(未经测试):

dest

然后这个:

current_user.destinations.
             joins("LEFT OUTER JOIN destination_reviews r " +
                   "ON r.user_id = trips.user_id " +
                   "AND r.destination_id = destinations.id").
             select("destinations.*, r.foo AS review_foo, r.bar AS review_bar")