您的范围示例仍然得到所有用户的所有回复的原因是因为这些条件用于查询 cmets。换句话说,它只用于确定从数据库中获取哪些 cmets。
例如,下面是使用范围运行的 SQL:
>> comments = Comment.joins(:replies).where(replies: { user_id: [2, 3] })
Comment Load (0.3ms) SELECT "comments".* FROM "comments" INNER JOIN "replies" ON "replies"."comment_id" = "comments"."id" WHERE "replies"."user_id" IN (2, 3)
这只是说“根据与回复表的内部连接的结果选择 cmets”。回复的结果现在几乎毫无意义,因为只要您访问任何一个 cmets 上的回复属性:
>> comments.first.replies
Reply Load (0.2ms) SELECT "replies".* FROM "replies" WHERE "replies"."comment_id" = ? [["comment_id", 1]]
ActiveRecord 将在回复表中进行单独查找,因为它不考虑 cmets 查找的范围。
解决方案
在我看来,您似乎更关心回复而不是 cmets。因此,我处理此查询的一种方法是查询回复表,但需要预先加载:
>> replies = Reply.includes(:comment).where(user_id: [2, 3])
Reply Load (0.4ms) SELECT "replies".* FROM "replies" WHERE "replies"."user_id" IN (2, 3)
Comment Load (0.2ms) SELECT "comments".* FROM "comments" WHERE "comments"."id" = 1
最后,您只有特定用户 ID 的回复。由于急切加载,您也已经以这种方式拥有 cmets。例如,执行以下操作不会导致任何额外的提取,因为这些 cmets 在内存中。
>> replies.map(&:comment)
但请注意不要再次访问其中一个评论模型上的回复属性,否则 ActiveRecord 将执行另一次获取:
>> replies.map(&:comment).first.replies
Reply Load (0.2ms) SELECT "replies".* FROM "replies" WHERE "replies"."comment_id" = ? [["comment_id", 1]]
如果您仍想将其设为范围,则现在将其放入回复模型中:
scope :from_users, -> (*user_ids) do
includes(:comment).where(user_id: user_ids)
end
然后调用:
>> Reply.from_users [2, 3]
Reply Load (0.1ms) SELECT "replies".* FROM "replies" WHERE "replies"."user_id" IN (2, 3)
Comment Load (0.1ms) SELECT "comments".* FROM "comments" WHERE "comments"."id" = 1