如何合并两个ActiveRecord::Scope?实现特定客户筛选逻辑
问题:合并ActiveRecord Scope实现特定客户筛选逻辑
我为父亲公司的圣诞客户礼物整理程序定义了两个ActiveRecord Scope:
scope :has_food_preference, -> { where.not(food_preference: nil) } scope :wine_chosen, -> { where(relationship: 1, company_size: 1).where.not(wine_id: nil) }
has_food_preference:筛选有饮食偏好(如素食、纯素等)的客户wine_chosen:筛选relationship=1且company_size=1的公司客户,同时要求这类客户的wine_id不为空(按人数送酒,规模是关键)
我需要实现的筛选逻辑是:所有设置了food_preference,且当relationship=1、company_size=1时wine_id不为空的公司客户。
我尝试了几种方案都没达到预期:
方案1
Customer.where.not(food_preference: nil).or(Customer.where(relationship: 1, company_size: 1).where.not(wine_id: nil))
→ 无效,完全忽略or条件,只返回where.not(food_preference: nil)的结果。
方案2
companies = has_food_preference + wine_chosen
→ 返回数组类型,我需要的是ActiveRecord::Relation对象。
方案3
scope :food_and_wine, -> { self.has_food_preference.merge(self.wine_chosen) }
→ Merge是「与」(AND)逻辑,但我需要的是符合特定条件的组合逻辑。
方案4
用arel_table实现,结果和方案1一致,没达到需求。
正确实现方式
你的需求本质是所有有饮食偏好的客户,其中如果是relationship=1且company_size=1的客户,必须同时满足wine_id不为空。这不是简单的OR/AND逻辑,而是「基础条件 + 特定场景的附加条件」,可以用以下两种方式实现:
方式1:直接构建查询条件
用原生SQL片段或Arel构建清晰的逻辑:
scope :qualified_customers, -> { where.not(food_preference: nil) .where( "(relationship != 1 OR company_size != 1) OR (relationship = 1 AND company_size = 1 AND wine_id IS NOT NULL)" ) }
或者用Arel实现更安全的链式调用:
scope :qualified_customers, -> { customers = arel_table has_food_preference.where( customers[:relationship].not_eq(1).or(customers[:company_size].not_eq(1)) .or( customers[:relationship].eq(1) .and(customers[:company_size].eq(1)) .and(customers[:wine_id].not_eq(nil)) ) ) }
方式2:基于现有Scope组合
复用已定义的Scope,结合Arel组合条件:
scope :qualified_customers, -> { base_scope = has_food_preference # 获取wine_chosen的查询约束 wine_condition = wine_chosen.arel.constraints.reduce(:and) # 定义不属于wine_chosen群体的条件 non_wine_condition = arel_table[:relationship].not_eq(1).or(arel_table[:company_size].not_eq(1)) # 组合条件:基础条件 + (非目标群体 或 满足wine_chosen条件) base_scope.where(non_wine_condition.or(wine_condition)) }
逻辑拆解
核心规则:
- 必须满足基础条件:
food_preference IS NOT NULL - 分两种情况判断:
- 若客户不属于
relationship=1且company_size=1的群体 → 直接符合要求 - 若客户属于该群体 → 必须同时满足
wine_id IS NOT NULL
- 若客户不属于
以上方式均返回ActiveRecord::Relation对象,支持后续的链式调用(如排序、分页等)。
内容的提问来源于stack exchange,提问作者marcHoll90
相关产品推荐
相关产品推荐

