2010-03-04 5 views
49

Возможно ли иметь несколько связей has_many :through, которые проходят друг друга в Rails? Я получил предложение сделать это в качестве решения для другого вопроса, который я разместил, но не смог заставить его работать.Ruby-on-Rails: Несколько has_many: через возможно?

Друзья являются циклической ассоциацией через таблицу соединений. Цель состоит в том, чтобы создать has_many :through для friends_comments, поэтому я могу взять 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

Edit:

Rails 3.1 поддерживает вложенные ассоциации. Например:

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

Нет необходимости в решении, приведенном ниже. Обратитесь к this screencast за более подробной информацией.

Оригинал ответа

Вы передаете 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. –

+1

Нет, не будет. Это будет 2 вопроса. Первый запрос, чтобы получить друзей и второй запрос, чтобы получить комментарии для ** всех ** друзей. Если вы уже загрузили друзей в модель пользователя, вы не понесете никаких затрат. Я обновил решение еще двумя подходами. Взглянуть. –

+0

@williamjones У вас нет проблемы с N + 1 во всех трех подходах. Вы можете проверить свой файл журнала, чтобы убедиться в этом. Лично мне нравится подход 1, поскольку он довольно изящный, т. Е. 'User.friends.comments' лучше, чем' user.friends_comments' –

8

Существует плагин, который решает вашу проблему, посмотрите на this blog.

Вы устанавливаете плагин с

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

Хотя это не работало в прошлом, он прекрасно работает в Rails 3.1 теперь.

Смежные вопросы