CircleCI+Rspec中添加where子句后ActiveRecord::Relation为空排查
问题根源及解决方案
核心问题分析
结合你描述的现象(本地正常、CircleCI测试失败,测试内调用过滤器正常但控制器内返回空),主要排查方向集中在以下几点:
1. 过滤器inject逻辑存在隐性bug
你的call方法中,inject块仅在过滤条件满足时返回处理后的作用域,条件不满足时未返回原作用域,会导致inject最终返回nil(Ruby块默认返回最后一行执行结果,未满足条件时无有效返回值)。虽然当前仅定义了一个过滤器,但如果后续新增过滤器或参数匹配异常,会直接导致作用域丢失。
2. 控制器与测试中传入的current_user实例类型不匹配
测试中调用过滤器时传入的是tech(Staff模型实例),但控制器中可能传入的是User模型实例(比如通过JWT解析得到的用户对象)。由于WorkOrder的technician关联的是Staff模型,用User实例做where(technician: user)查询自然无法匹配到数据。
3. 测试数据的Tenant隔离问题
CircleCI环境中,测试创建的assigned_work_order可能未正确关联到当前测试的tenant,而控制器中的查询带有current_tenant的作用域过滤,导致控制器内的基础作用域就不包含该工单,追加过滤后自然返回空。
具体修复方案
方案1:修复过滤器的inject逻辑
确保无论过滤条件是否满足,inject块始终返回有效的作用域:
def call(scope, params, current_user) FILTERS.keys.inject(scope.dup) do |current_scope, filter_key| filter = fetch_filter(filter_key) # 条件满足时返回过滤后的作用域,否则返回原作用域 filter[:apply?].call(params) ? filter[:apply].call(current_scope, current_user) : current_scope end end
方案2:统一控制器与测试的current_user实例类型
如果控制器中current_user是User模型,需关联到对应的Staff实例后再传入过滤器:
# 控制器内调用过滤器的代码 scope = current_tenant.work_orders.where(state: :in_progress) @work_orders = WorkOrderPreviews::IndexFilter.new.call(scope, params, current_user.staff)
方案3:确保测试数据的Tenant关联正确性
- 在
WorkOrder工厂中强制关联当前Tenant:factory :work_order do tenant { Tenant.current } # 假设存在Tenant.current全局设置 # ...其他属性 end - 在测试中添加断言验证关联:
context 'with user filter' do it 'should respond with the assigned work order when ?filter=current_user' do expect(assigned_work_order.tenant).to eq(tenant) # 验证工单属于当前测试租户 # ...剩余测试代码 end end
方案4:对齐测试与控制器的基础作用域
测试中使用与控制器一致的带Tenant过滤的基础作用域,避免测试与实际逻辑不一致:
scope = tenant.work_orders.where(state: 'in_progress') # 替换原WorkOrder.where(...) filtered_results = WorkOrderPreviews::IndexFilter.new.call(scope, { filter: 'current_user' }, tech)
内容的提问来源于stack exchange,提问作者AustinsGrumpy
相关产品推荐
相关产品推荐

