Rails:包括所有模型A和仅相关模型B的子集

时间:2013-10-15 23:55:46

标签: ruby-on-rails ruby-on-rails-3.2

我正在尝试更好地理解rails数据库API。如果我有以下型号:

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等于当前用户的投票。我试过这个:

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

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

1 个答案:

答案 0 :(得分:0)

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

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,您可能希望使用较新的活动记录查询方法,因为我相信在Rails 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部分,这将创建一个从属子查询,如果它存在于投票表中(意味着SOMEBODY在某一点投票),将导致Link行被拒绝,或者如果它确实存在是否是我们指定用户的投票。最终结果是您获得任何没有任何投票的Link行,或者获得由指定用户创建的投票的链接。

<强>更新

正如评论中所提到的,这种方法的问题在于,根据需要不会急切地加载关联的votes行。在使用了这个约2个小时的工具之后,我几乎要承认我不相信可以通过这种查询来急切加载相关的表。

如果我们抛弃了include部分(似乎无论如何都没有效果),我们执行会收回已被投票通过的Link个对象的集合指定的用户,或者根本没人。问题是当我们调用.votes时,Rails很乐意在SELECT votes.* FROM votes WHERE votes.link_id = [the link id]的行中对进行另一次对数据库的调用,正如您所看到的那样,不会被user_id区分为点。

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

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

这会产生如下查询:

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

如果有人有更好的解决方案,我有兴趣看到它。

希望有所帮助。