Rails 7中关联取消默认作用域且避免N+1查询的优雅方案
Rails优雅解决包含非活跃作者标签的预加载问题
方案1:添加自定义关联(推荐)
在Product模型里新增一个专属关联,专门用来获取包含非活跃作者的所有标签,完美适配Rails的预加载机制:
# app/models/product.rb class Product < ApplicationRecord has_many :tags # 自定义关联,忽略Author的默认active作用域 has_many :all_tags, -> { joins(:author).unscope(author: { where: :active }) }, class_name: 'Tag' end
控制器里预加载这个关联,彻底解决N+1查询问题:
# app/controllers/categories_controller.rb def show @category = Category.includes(products: :all_tags).find(params[:id]) end
视图里直接调用就行,写法自然清晰:
<% @category.products.each do |product| %> <h3><%= product.name %></h3> <div>标签:<%= product.all_tags.pluck(:name).join(', ') %></div> <% end %>
这种方式完全遵循Rails的关联设计,代码易读易维护,是最优雅的解决思路。
方案2:预加载时动态移除默认作用域
如果不想新增关联,也可以在查询时直接调整逻辑,用eager_load结合unscope实现:
# app/controllers/categories_controller.rb def show @category = Category.eager_load(products: [tags: :author]) .unscope(author: { where: :active }) .find(params[:id]) end
这时视图里直接用product.tags就能拿到包含非活跃作者的标签,但要注意:eager_load会生成LEFT JOIN查询,且全局unscope可能影响同一请求中其他对Author的查询,所以这个方案的通用性不如方案1。
方案3:Rails 6+专属:ignore_default_scope
如果你的项目用的是Rails 6.0及以上版本,可以用ignore_default_scope临时跳过Author的默认作用域,结合预加载实现:
# app/controllers/categories_controller.rb def show tags_query = Tag.joins(:author).ignore_default_scope(Author) @category = Category.includes(products: { tags: tags_query }).find(params[:id]) end
这个方法写法稍显繁琐,不如方案1直观,但也是一种可行的思路。
内容的提问来源于stack exchange,提问作者aldm
相关产品推荐
相关产品推荐

