我正在尝试在我的应用中实现“喜欢”系统。我使用订单呈现一个表,然后当前用户能够“喜欢”订单,这样当订单状态发生变化时,她将收到通知。问题是我遇到了N + 1问题,因为每次表格被渲染时,程序会显示与订单一样多的查询,以检测订单是否已被用户“喜欢”。
我已经读过,可以通过使用“includes”来急切加载相关记录来避免这种情况,但我无法理解如何做到这一点,特别是在我的情况下。
我有这些模特和协会:
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。我添加索引操作以防它有用:
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
答案 0 :(得分:2)
在这种情况下我通常做的是,因为您已经在视图上显示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
为单一,因此它将执行单个查询以查找它,而不是使用java.sql.Timestamp
在数据库中多次搜索。
显然,还有很多方法可以解决这个问题。选择取决于您和您的情况。
答案 1 :(得分:2)
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
实例,然后在该加载的Order上加载其关联的Like
实例以及加载的Like
实例。 1}}实例应用user_id
过滤器。
<强>更新强>
liked_orders
关联应定义为
has_many :liked_orders, through: :likes, source: :order
答案 2 :(得分:0)
它在OrdersController中显示或索引动作,对吗?您需要重新定义实例变量,如下所示:
@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 gem添加到应用中,它会警告您有关N + 1个查询并提供有关includes
的建议
更新:
只需将includes
添加到现有的@orders
@orders = orders.includes(:likes).order("#{sort_column} #
{sort_direction}").page(params[:page]).per(params[:paginas])