Rails模型Scope问题:查询用户已购Recipe返回全部记录排查
Rails Scope 筛选用户已购买Recipe的问题分析与修复
嘿,我来帮你排查这个问题。你的scope之所以返回所有Recipe记录,主要有两个核心问题:
1. 错误使用了includes而非joins
includes的设计初衷是预加载关联数据以避免N+1查询,它默认生成的是左外连接(LEFT OUTER JOIN)。这种连接方式会保留主表(recipes)的所有记录,哪怕对应的Pack没有该用户的Purchase记录——只是未匹配的关联字段会被设为null。这就导致即使没有符合条件的Purchase,Recipe依然会被返回。
而我们的需求是只保留存在用户购买记录的Pack下的Recipe,这时候应该用joins,它会生成内连接(INNER JOIN),只有当关联表存在匹配记录时,主表的记录才会被保留。
2. WHERE条件的层级写法错误
你写的where(packs: { purchases: { user: user } })多嵌套了一层packs,这会让Rails错误解析条件。因为我们已经通过pack: :purchases关联到了purchases表,直接针对purchases表写条件就可以了。
修复方案一:使用joins + 去重(最直观)
scope :purchased, lambda { |user| joins(pack: :purchases) .where(purchases: { user_id: user.id }) .distinct # 避免同一个Recipe因同一Pack多次购买而重复返回 }
joins(pack: :purchases):建立recipes -> packs -> purchases的内连接,只保留有匹配Purchase的Recipe。distinct:如果用户多次购买同一个Pack,同一个Recipe会被匹配多次,去重后得到唯一的Recipe列表。
修复方案二:使用EXISTS子查询(性能更优)
当数据量较大时,子查询通常比joins + distinct更高效,因为不会生成重复的中间记录:
scope :purchased, lambda { |user| where( <<~SQL EXISTS ( SELECT 1 FROM purchases WHERE purchases.pack_id = recipes.pack_id AND purchases.user_id = ? ) SQL user.id ) }
这个写法直接检查当前Recipe所属的Pack是否存在该用户的Purchase记录,逻辑清晰且性能更好。
现在调用Recipe.purchased(current_user)应该就能正确返回该用户已购买的所有Recipe了!
内容的提问来源于stack exchange,提问作者heroxav
相关产品推荐
相关产品推荐

