You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rails中如何编写not_voted_by作用域获取未被指定用户投票的电影?

解决Rails中not_voted_by作用域在多投票场景下的失效问题

这个问题我之前也碰到过,核心原因是你当前的实现没考虑到电影存在多条投票记录的情况——当一部电影被多个用户投票时,where.not(votes: { user: user })会匹配到其他用户的投票行,导致这部电影仍然被错误地包含在结果里。咱们来拆解下问题,再给出靠谱的解决方案:

问题根源

举个例子:电影M被用户A和B同时投票。当你调用Movie.not_voted_by(A)时,你的查询会关联出M的两条投票记录(A和B的),其中B的投票记录满足votes.user != A,所以这条记录会被保留,最终M就出现在了结果里,这显然不符合你要“排除该用户投票过的所有电影”的需求。

我们真正要的是:所有不存在当前用户投票记录的电影,包括从来没被任何人投过的电影。

解决方案

方案1:用子查询直接排除已投票的电影(最推荐)

这个写法简洁直观,性能也不错:先找出该用户所有投票过的电影ID,然后直接排除这些ID即可。

class Movie
  scope :not_voted_by, ->(user) {
    where.not(id: user.votes.select(:movie_id))
  }
end

为什么这个方案好?

  • 逻辑一目了然:就是排除用户已经投过票的电影
  • 性能高效:数据库会利用votes表上的user_id和movie_id索引快速获取已投票的电影ID集合,再做排除
  • 自动处理“从未被投票的电影”:这类电影的ID不在子查询结果里,自然会被保留

方案2:用LEFT JOIN + 空值判断

如果你更倾向于用关联查询的方式,可以通过左连接投票表,筛选出没有匹配到当前用户投票的电影:

class Movie
  scope :not_voted_by, ->(user) {
    left_joins(:votes)
      .where("votes.user_id != ? OR votes.id IS NULL", user.id)
      .distinct
  }
end

这里的distinct很关键,因为如果一部电影有多个其他用户的投票,左连接会生成多条重复的电影记录,distinct可以帮我们去重,确保每部电影只出现一次。

验证效果

拿你提到的场景测试:电影M被A和B投票,调用Movie.not_voted_by(A)时,方案1会先获取A投票过的电影ID(包括M),然后排除这些ID,所以M不会出现在结果里;方案2通过左连接后,M的投票记录里只有B的那条满足votes.user_id != A,但因为distinct会去重,最终M不会被包含进来——两种方案都能得到符合预期的结果。

内容的提问来源于stack exchange,提问作者Jun Zhou

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:19:17