2010-03-04 125 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現在罰款。

相關問題