Rails 7测试预加载数据过滤时的Bullet查询优化报错问题
问题场景
在Rails 7的index动作中使用posts.includes([:user, :status])预加载数据,Post与User为可选关联(存在无User的Post)。基于不同参数过滤数据时,多数场景正常,但使用scope :no_user, -> { where(user_id: nil) }过滤无User的Post时,浏览器无报错,但Minitest控制器测试触发Bullet报错:
Bullet::Notification::UnoptimizedQueryError AVOID eager loading detected Post => [:user] Remove from your query: .includes([:user])
若移除includes中的:user,其他过滤测试则触发相反报错:
USE eager loading detected Post => [:user] Add to your query: .includes([:user])
尝试left_outer_joins(:user)也会出现相同报错,希望无需针对该过滤编写条件语句消除测试报错。
可行解决方案
1. 给Bullet添加白名单(推荐)
修改config/initializers/bullet.rb,添加针对Post模型:user关联的白名单配置:
Bullet.add_whitelist type: :unused_eager_loading, class_name: "Post", association: :user
这个配置会告诉Bullet:当Post的:user预加载未被使用时,不要抛出UnoptimizedQueryError。刚好匹配no_user scope的场景——此时所有查询结果都没有User,预加载的:user确实不会被用到,但其他场景下预加载依然有效,完全不用修改业务代码。
2. 动态切换预加载方式(备选)
如果可以接受极少量的条件判断,可利用Rails的preload和includes特性调整预加载逻辑:
posts = Post.all.preload(:status) posts = posts.includes(:user) unless params[:filter] == 'no_user' posts = posts.no_user if params[:filter] == 'no_user'
但此方法需要针对过滤参数做判断,不符合“无需编写条件语句”的需求,仅作备选。
3. 全局关闭未使用预加载检测(不推荐)
若项目中存在大量类似可选关联场景,可在bullet.rb中关闭全局的未使用预加载检测:
Bullet.unused_eager_loading_enable = false
但该方法会失去Bullet对其他不必要预加载的检测能力,不利于项目性能优化,谨慎使用。
内容的提问来源于stack exchange,提问作者Anton Zaharia

