Rails中展示含多个Action Text字段的实例时如何避免N+1查询?
解决方案
一、实例层面预加载所有富文本字段
可以在ApplicationRecord中添加自定义方法,一次性查询并绑定所有富文本记录,彻底避免N+1查询:
class ApplicationRecord < ActiveRecord::Base def preload(*associations) ActiveRecord::Associations::Preloader.new.preload(self, associations.flatten) self end def preload_all_rich_text rich_text_fields = self.class.rich_text_attributes return self if rich_text_fields.empty? # 单次SQL查询获取所有关联的富文本记录 rich_texts = ActionText::RichText.where( record_type: self.class.name, record_id: id, name: rich_text_fields ) # 将查询到的记录绑定到实例对应的关联属性 rich_texts.each do |rt| association_name = "rich_text_#{rt.name}".to_sym assoc = self.association(association_name) assoc.target = rt assoc.loaded! end self end end
使用方式和你现有的preload助手完全一致:
# before_action中获取实例 @instance = Model.find(id) # 在需要的action中预加载所有富文本 @instance.preload_all_rich_text
该方法仅发起一次SQL查询,一次性取出所有关联富文本后手动绑定到实例,解决了单独预加载每个富文本关联引发的N+1问题。
二、优化eager_load查询,返回别名化的富文本字段
如果想减少冗余数据(如忽略富文本的id、created_at等无用字段),可以自定义作用域,只查询需要的body字段并设置别名:
class ApplicationRecord < ActiveRecord::Base # ... 保留其他已有方法 scope :with_optimized_rich_text, -> { rich_text_fields = self.class.rich_text_attributes return all if rich_text_fields.empty? relation = self.all select_clauses = [self.arel_table[Arel.star]] # 保留主表全部字段 rich_text_fields.each do |field| # 为每个富文本表创建别名,避免join冲突 rt_alias = ActionText::RichText.arel_table.alias("rt_#{field}") # 构造左连接条件 join_condition = rt_alias[:record_type].eq(self.name) .and(rt_alias[:record_id].eq(self.arel_table[:id])) .and(rt_alias[:name].eq(field)) relation = relation.joins(Arel::Nodes::OuterJoin.new(rt_alias, join_condition)) # 添加body字段的别名查询 select_clauses << rt_alias[:body].as("#{field}_body") end relation.select(*select_clauses) } end
使用方式:
@instance = Model.with_optimized_rich_text.find(params[:id]) # 通过别名直接获取富文本内容 @instance.body_body # 对应:body富文本字段的内容 @instance.description_body # 对应:description富文本字段的内容
这个作用域仅查询主表字段和每个富文本的body字段,返回结果无冗余数据,同时通过别名可直接访问对应内容。
内容的提问来源于stack exchange,提问作者Goulven
相关产品推荐
相关产品推荐

