Как избежать N + 1 в этой ситуации - PullRequest
0 голосов
/ 26 апреля 2018

Я пытаюсь внедрить "симпатичную" систему в моем приложении. Я отображаю таблицу с заказами, тогда текущий пользователь может «понравиться» заказу, поэтому он получит уведомления, когда статус заказа изменится. Проблема в том, что у меня проблема N + 1, так как каждый раз, когда таблица обрабатывается, программа делает столько запросов, сколько отображается заказов, чтобы определить, понравился ли заказ пользователю.

Я читал, что этого можно избежать, используя «include» для загрузки соответствующих записей, но я не могу понять, как это сделать, особенно в моем случае.

У меня есть следующие модели и ассоциации:

user.rb Включил ли я лайки? метод, который вызывает предупреждение N + 1:

class User < ApplicationRecord
  devise :database_authenticatable, :recoverable, :rememberable, :trackable, 
  :validatable
  has_many :likes

  def likes?(order)
    order.likes.where(user_id: id).any?
  end
end

like.rb

class Like < ApplicationRecord
  belongs_to :user
  belongs_to :order
end

order.rb

class Order < ApplicationRecord

 has_many :likes
 .
 .
 .

Для каждой строки таблицы я отображаю эту частичку, чтобы показать, нравится ли заказ или нет:

<% if current_user.likes?(order) %>
  <%= link_to "<i class='fa fa-fire fa-2x fa-like'></i>".html_safe, 
  order_like_path(order), method: :delete, remote: true %>
<%else%>
  <%= link_to "<i class='fa fa-fire fa-2x fa-unlike'></i>".html_safe, 
  order_like_path(order), method: :post, remote: true %>
<%end%>

Это запрос:

Rendered orders/_likes.html.erb (135.5ms)
Like Exists (0.5ms)  SELECT  1 AS one FROM "likes" WHERE "likes"."order_id" 
=$1 AND "likes"."user_id" = $2 LIMIT $3  [["order_id", 7875], ["user_id", 
1], ["LIMIT", 1]]

EDIT. Я добавляю действие index, если оно полезно:

  def index
    orders = request.query_string.present? ? Order.search(params, 
    current_user) : Order.pendientes
    if params[:button] == 'report'
      build_report(orders)
    else
    @orders = orders.order("#{sort_column} # 
    {sort_direction}").page(params[:page]).per(params[:paginas])
    end
  end

Ответы [ 3 ]

0 голосов
/ 26 апреля 2018

Это в шоу OrdersController или в действии index, верно? Вам нужно переопределить переменную экземпляра следующим образом:

@orders = current_user.orders.includes(:likes)
or 
@order = current_user.orders.find(params[:id]).includes(:likes)

И переместите метод likes? к модели заказа (измените его liked_by, например).

def liked_by?(user)
  likes.where(user_id: user.id).exists?
end

В представлении у вас будет

<% if order.liked_by?(current_user) %>

В этом случае лайки будут предварительно загружены, и вы избежите проблемы N + 1.

Хорошей идеей будет добавить в приложение гем bullet , он предупредит вас о N + 1 запросах и даст предложения о includes

UPDATE:

Просто добавьте includes к существующим @ заказам

@orders = orders.includes(:likes).order("#{sort_column} # 
    {sort_direction}").page(params[:page]).per(params[:paginas])
0 голосов
/ 26 апреля 2018
class User < ApplicationRecord
  has_many :likes

  has_many :liked_orders, through: :likes, class_name: 'Order'

  def liked_orders_id
    @liked_orders_id ||= liked_orders.pluck(:id)
  end

  def liked_order?(order_id)
    liked_orders_id.include?(order_id)
  end
end

Мне кажется, что причина вашей проблемы в том, что вы реализовали метод likes?(order) в User модель

  def likes?(order)
    order.likes.where(user_id: id).any?
  end

Каждый раз, когда вы вызываете этот метод для загруженного User, он сначала загружает экземпляр Order, затем в этот загруженный ордер загружает связанные с ним экземпляры Like и к этим загруженным экземплярам Like применяет user_id фильтр.

Обновление

Ассоциация liked_orders должна быть определена как

  has_many :liked_orders, through: :likes, source: :order
0 голосов
/ 26 апреля 2018

Что я обычно делаю в этой ситуации, так это то, что у вас уже есть orders в представлении, а у вас есть user, поэтому я получаю:

likes = current_user.likes.where(order: orders)
liked_order_ids = likes.pluck(:order_id)

И я каждый раз передам liked_order_ids частичному _likes и проверю liked_order_ids.include?(order.id)

Я не получил user.likes напрямую, потому что может быть много orders, которые ему понравились, и все они отсутствуют на текущей странице. Если они есть, вы можете получить их напрямую так:

liked_order_ids = current_user.likes.pluck(:order_id)

Таким образом, таким образом он не будет выполнять ни новые запросы, ни кэшированные запросы.

То, что вы пытаетесь сделать, - это поиск по order лайкам, так что проходите через order объект. Вместо этого у вас есть user, через который вы можете найти likes, так же как и его belongs to. Так как order всегда будет кратным, а user будет единичным, он выполнит один запрос, чтобы найти его, вместо многократного поиска в базе данных, используя order.

Очевидно, есть еще много способов обойти это. Выбор будет зависеть от вас и вашей ситуации.

...