Rails:仅包含模型A的所有内容,并且仅包含相关模型B的一个子集

Rails: Include all of model A and only a subset of related model B

我试图更好地理解Rails数据库API。如果我具有以下型号:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Link
  has_many :votes
  belongs_to :user
end

class Vote
  belongs_to: link
  belongs_to :user
end

class User
  has_many :links
  has_many :votes
end

我想"急切地"返回所有链接的列表,并且对于每个链接,我希望user_id等于当前用户的投票。我尝试了这个:

1
l = Link.all(:include => :votes, :conditions => { :votes => { :user_id => current_user.id}})

但这只会返回用户已提交投票的链接列表。我希望它返回所有链接,然后仅返回该用户的投票(如果没有,则不返回)。如何使用include语句执行此操作?


我不得不重复阅读几次这个问题,所以如果对此感到困惑,请告诉我。听起来您想使用LEFT OUTER JOIN。这将为您提供links表中的所有内容,其中投票与用户ID匹配,而其他所有链接都与当前用户不匹配。像这样的东西:

1
2
3
4
Link.all(:include => :votes,
:select=>"DISTINCT links.*",
:joins =>"LEFT OUTER JOIN votes ON links.id = votes.link_id AND votes.user_id = #{current_user.id}",
:conditions=>"NOT EXISTS (SELECT * FROM votes WHERE votes.link_id = links.id) OR votes.user_id = #{current_user.id}")

此外,由于您已经在使用Rails 3.2,因此您可能希望使用较新的活动记录查询方法,因为我相信基于旧哈希的finder方法已在Rails 4中弃用。因此,相同的查询看起来像这:

1
2
3
4
Link.include(:votes)
.joins("LEFT OUTER JOIN votes ON links.id = votes.link_id AND votes.user_id = #{current_user.id}")
.where("NOT EXISTS (SELECT * FROM votes WHERE votes.link_id = links.id) OR votes.user_id = #{current_user.id}")
.uniq.load

关于条件/ where部分,这将创建一个依赖子查询,如果Link行存在于投票表中(意味着SOMEBODY在某一点对其进行了投票),它将导致拒绝该子查询是否是我们指定用户的投票。最终结果是,您得到没有任何投票的任何Link行,或具有指定用户创建的投票的链接。

更新

如评论中所述,此方法的问题在于相关联的votes行未急于按需加载。经过大约2小时的处理之后,我几乎承认我不相信有可能渴望通过这种查询来加载关联的表。

如果我们抛弃include部分(无论如何似乎没有任何作用),我们确实会获得已由指定用户投票或完全没有投票的Link对象的集合。问题是当我们调用.votes时,Rails沿SELECT votes.* FROM votes WHERE votes.link_id = [the link id]的行愉快地再次调用了数据库,如您所见,这在那时并没有被user_id区分。

我能想到的唯一解决方案是在调用关联方法时正确量化您要查找的内容。像这样的东西:

1
link.votes.where(:user_id=>current_user.id)

这将产生如下查询:

1
SELECT votes.* FROM votes WHERE votes.link_id = [the link ID] AND user_id = [the user ID]

如果有人有更好的解决方案,我将很感兴趣。

希望有帮助。