如何避免N+1时显示一个实例与许多action_text字段在Rails?



Rails提供了with_all_rich_text作用域来主动加载与active_record对象集合相关联的rich_text。但是,它没有提供在实例级预加载富文本字段的方法,这会导致N+1个查询。

我添加了一个自定义方法来在实例级预加载其他类型的关联,使用如下:

class ApplicationRecord < ActiveRecord::Base
def preload(*associations)
ActiveRecord::Associations::Preloader.new.preload(self, associations.flatten)
self
end
end
# Using it in a controller is as easy as:
# 1. this is done in a before_action
@instance = Model.find(id)
# 2. In an action that needs to preload some associations
@instance.preload(:association_a, :association_b, :etc)

我已经尝试使用这个助手通过传递它生成的富文本关联(:rich_text_a, :rich_text_b)的名称来预加载所有富文本字段,但Rails仍然为每个rich_text字段触发一个查询。

我可以利用with_all_rich_text范围结合find_by在控制器中,但感觉相当笨拙,和生成的DB查询真的很重(它包含一个LEFT OUTER JOIN为每个富文本字段)。

# In the controller
def show
@instance = Model.with_all_rich_text.find_by(id: params[:id])
end

导致以下SQL查询:

SELECT 
"model_table"."id" AS t0_r0,
"model_table"."attribute_a" AS t0_r1,
"model_table"."attribute_b" AS t0_r2,
"model_table"."attribute_c" AS t0_r3,
"model_table"."attribute_d" AS t0_r4,
-- repeated for every attribute in my model

"action_text_rich_texts"."id" AS t1_r0,
"action_text_rich_texts"."name" AS t1_r1,
"action_text_rich_texts"."body" AS t1_r2,
"action_text_rich_texts"."record_type" AS t1_r3,
"action_text_rich_texts"."record_id" AS t1_r4,
"action_text_rich_texts"."created_at" AS t1_r5,
"action_text_rich_texts"."updated_at" AS t1_r6
-- repeated for every rich text field name
FROM "model_table"
LEFT OUTER JOIN "action_text_rich_texts" ON "action_text_rich_texts"."record_type" = "model_class"
AND "action_text_rich_texts"."record_id" = "model_table"."id"
AND "action_text_rich_texts"."name" = "name_of_first_rich_text"
-- repeated for every rich text field name
WHERE "model_table"."id" = 1 LIMIT 1

Rails日志输出表明,ActiveRecord时间在使用和不使用with_all_rich_text时大致相同,但在我的情况下,使用此作用域产生的分配减少了大约十倍,这显然更好。

是否有一种干净的方法来复制这个范围在实例级别,而不是结合它与find_by,像我添加的预加载助手?

,

顺便说一句,当使用eager_load时,Rails获取大量未使用/重复的数据(富文本id, created_at, updated_at, record_type和record_id)。N+1个查询现在已经合并到一个超级N+1连接中,这比几十个查询要好,但仍然不够优化。
是否可以让数据库为富文本字段返回别名列?

您可以使用ActiveRecord::QueryMethods中的includes方法

users = User.includes(:address, friends: [:address, :followers])

如果你不想查询所有的富文本关联,你可以用这个方法只查询一个字段,你可以从这里检查。

scope :"with_rich_text_#{name}", -> { includes("rich_text_#{name}") }

在此方法中,所有包含的关联将加载到您的实例中。

@instance = Model.includes(:with_rich_text_a, :with_rich_text_b).find_by(id: params[:id])

相关内容

  • 没有找到相关文章

最新更新