2010-03-04 6 views
49

Có thể có nhiều mối quan hệ has_many :through đi qua nhau trong Rails không? Tôi đã nhận được đề xuất làm như vậy là một giải pháp cho một câu hỏi khác mà tôi đã đăng, nhưng không thể làm cho nó hoạt động.Ruby-on-Rails: Nhiều has_many: thông qua có thể?

Bạn bè là liên kết cyclic thông qua bảng tham gia. Mục tiêu là tạo một has_many :through cho friends_comments, vì vậy tôi có thể chụp User và làm một cái gì đó như user.friends_comments để nhận tất cả các nhận xét của bạn bè trong một truy vấn.

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 

Điều này có vẻ tuyệt vời và có ý nghĩa nhưng không hiệu quả đối với tôi. Đây là lỗi tôi nhận được một phần có liên quan khi tôi thử truy cập friends_comments của người dùng:
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)))

Khi tôi chỉ cần nhập user.friends, mà làm việc, đây là truy vấn nó thực thi:
: SELECT "users".* FROM "users" INNER JOIN "friendships" ON "users".id = "friendships".friend_id WHERE (("friendships".user_id = 1) AND ((status = 2)))

Vì vậy, có vẻ như nó hoàn toàn quên đi bản gốc has_many thông qua mối quan hệ hữu nghị, và sau đó là không thích hợp khi cố gắng sử dụng lớp Người dùng làm bảng kết nối.

Tôi có làm điều gì sai, hoặc điều này đơn giản là không thể?

Trả lời

69

Edit:

Rails 3.1 hỗ trợ các hiệp hội lồng nhau. Ví dụ:

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

Không cần giải pháp đưa ra dưới đây. Tham khảo this screencast để biết thêm chi tiết.

gốc trả lời

Bạn đang đi qua một hiệp hội has_many :through như một nguồn cho một hiệp hội has_many :through . Tôi không nghĩ rằng nó sẽ làm việc.

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

Bạn có ba cách để giải quyết vấn đề này.

1) Viết một phần mở rộng liên kết

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 

Bây giờ bạn có thể nhận được những người bạn bình luận như sau:

user.friends.comments 

2) Thêm một phương pháp để lớp User.

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

Bây giờ bạn có thể nhận được những người bạn bình luận như sau:

user.friends_comments 

3) Nếu bạn muốn điều này thậm chí còn hiệu quả hơn sau đó:

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 

Bây giờ bạn có thể nhận được những người bạn nhận xét như sau:

user.friends_comments 

Tất cả các phương thức đều lưu trữ kết quả. Nếu bạn muốn tải lại các kết quả thực hiện như sau:

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

HOẶC vẫn tốt hơn:

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

Thật không may, mục đích ban đầu đằng sau phương pháp này là để tôi có thể nhận tất cả các bình luận của bạn bè từ SQL trong một truy vấn. Xem tại đây: http://stackoverflow.com/questions/2382642/ruby-on-rails-how-to-pull-out-most-recent-entries-from-a-limited-subset-of-a-dat Nếu tôi viết một hàm, điều đó sẽ dẫn đến một số truy vấn N + 1. –

+1

Không, nó sẽ không. Nó sẽ là 2 truy vấn. Truy vấn đầu tiên để có được bạn bè và truy vấn thứ hai để nhận được các ý kiến ​​cho ** tất cả ** bạn bè. Nếu bạn đã tải bạn bè vào mô hình người dùng thì bạn không phải chịu bất kỳ chi phí nào. Tôi đã cập nhật giải pháp với hai cách tiếp cận khác. Hãy xem. –

+0

@williamjones Bạn không có vấn đề N + 1 trong cả ba cách tiếp cận. Bạn có thể kiểm tra tệp nhật ký của mình để đảm bảo điều này. Cá nhân tôi thích cách tiếp cận 1 vì nó khá thanh lịch, tức là 'user.friends.comments' tốt hơn' user.friends_comments' –

8

Có một plugin mà giải quyết vấn đề của bạn, hãy nhìn vào this blog.

Bạn cài đặt plugin với

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

Mặc dù điều này không làm việc trong quá khứ, nó hoạt động tốt trong Rails 3.1 bây giờ.