2010-03-04 124 views
49

在Rails中可以有多个穿越彼此的has_many :through关系吗?我收到了这样一个建议,作为我发布的另一个问题的解决方案,但一直无法实现。Ruby on Rails:多has_many:通过可能吗?

朋友是循环关联通过一个连接表。我们的目标是为friends_comments创建一个has_many :through,因此我可以采取User并执行类似user.friends_comments的操作,以便在单个查询中获取其朋友发出的所有评论。

class User 
    has_many :friendships 
    has_many :friends, 
      :through => :friendships, 
      :conditions => "status = #{Friendship::FULL}" 
    has_many :comments 
    has_many :friends_comments, :through => :friends, :source => :comments 
end 

class Friendship < ActiveRecord::Base 
    belongs_to :user 
    belongs_to :friend, :class_name => "User", :foreign_key => "friend_id" 
end 

这看起来不错,有道理,但不适合我。这是我收到的相关部分的错误,当我试图访问用户的friends_comments:
ERROR: column users.user_id does not exist
: SELECT "comments".* FROM "comments" INNER JOIN "users" ON "comments".user_id = "users".id WHERE (("users".user_id = 1) AND ((status = 2)))

当我刚进入user.friends,它的工作原理,这是它执行查询:
: SELECT "users".* FROM "users" INNER JOIN "friendships" ON "users".id = "friendships".friend_id WHERE (("friendships".user_id = 1) AND ((status = 2)))

因此,它似乎通过友谊关系完全忘记了原始的has_many,然后不恰当地尝试使用User类作为连接表。

我做错了什么,或者这是不可能的?

回答

69

编辑:

的Rails 3.1支持嵌套关联。例如:

has_many :tasks 
has_many :assigments, :through => :tasks 
has_many :users, :through => :assignments 

不需要下面给出的解决方案。有关更多详细信息,请参阅this截屏视频。

原来的答案

你传递一个has_many :through协会作为源另一has_many :through 关联。我不认为它会起作用。

has_many :friends, 
      :through => :friendships, 
      :conditions => "status = #{Friendship::FULL}" 
    has_many :friends_comments, :through => :friends, :source => :comments 

有三种方法可以解决这个问题。

1)写一个协会扩展

has_many :friends, 
      :through => :friendships, 
      :conditions => "status = #{Friendship::FULL}" do 
    def comments(reload=false) 
     @comments = nil if reload 
     @comments ||=Comment.find_all_by_user_id(map(&:id)) 
    end 
end 

现在你可以得到朋友们的意见如下:

user.friends.comments 

2)添加一个方法到User类。

def friends_comments(reload=false) 
    @friends_comments = nil if reload 
    @friends_comments ||=Comment.find_all_by_user_id(self.friend_ids) 
    end 

现在你可以得到朋友们的意见如下:

user.friends_comments 

3)如果你想这是更有效的,那么:

def friends_comments(reload=false) 
    @friends_comments = nil if reload 
    @friends_comments ||=Comment.all( 
      :joins => "JOIN (SELECT friend_id AS user_id 
           FROM friendships 
           WHERE user_id = #{self.id} 
         ) AS friends ON comments.user_id = friends.user_id") 
    end 

现在你可以得到朋友意见如下:

user.friends_comments 

所有方法都缓存结果。如果你想重新加载结果执行以下操作:

user.friends_comments(true) 
user.friends.comments(true) 

或者更好的是:

user.friends_comments(:reload) 
user.friends.comments(:reload) 
+1

不幸的是,这种方法背后的原始目的是让我可以在一个查询中获得来自SQL的所有朋友评论。见这里: http://stackoverflow.com/questions/2382642/ruby-on-rails-how-to-pull-out-most-recent-entries-from-a-limited-subset-of-a-dat 如果我编写一个函数,这将导致N + 1个查询。 – 2010-03-04 23:56:55

+1

不,它不会。这将是2个查询。第一个查询让朋友和第二个查询获得所有**朋友的评论。如果您已将朋友加载到用户模型中,则不会产生任何费用。我已经用两种方法更新了解决方案。看一看。 – 2010-03-05 00:17:09

+0

@williamjones您在这三种方法中都没有N + 1问题。您可以检查您的日志文件以确保这一点。我个人喜欢方法1,因为它非常优雅,即'user.friends.comments'优于'user.friends_comments' – 2010-03-05 00:35:21

8

有是解决你的问题的一个插件,看看this blog

您安装与

script/plugin install git://github.com/ianwhite/nested_has_many_through.git 
4

虽然这并没有在过去的工作,它工作在Rails的3.1现在罚款。

相关问题