Как создать «двусторонние» отношения «многие ко многим» в Rails?
-
20-09-2019 - |
Вопрос
Предположим, у нас есть фотосайт.Любой автор может подписаться на получение обновлений от любого другого автора.Очевидно, что если автор А подписан на автора Б, это не означает, что Б подписан на А.Итак, мы строим модели
class Author < ActiveRecord::Base
has_many :subscriptions
has_many :subscribed_by_author, :through => :subscriptions, :source => :subscribed_to
end
class Subscription < ActiveRecord::Base
belongs_to :author
belongs_to :subscribed_to, :class_name => "Author", :foreign_key => "subscribed_to"
end
Таким образом, мы можем использовать
- some_author.subscribed_by_author — список авторов, на которых подписан some_author.
- Для любой подписки мы можем знать оба конца (кто на кого подписан)
Но вопрос в том, как получить список людей, подписанных на какого-то автора, используя только рельсы (не используя простой SQL), т.е. получить ответ на вопрос: «Кто подписан на some_author?»
Вопрос:Есть ли в Rails какая-либо возможность заставить отношения работать с обеих сторон, т.е.не только писать some_author.subscribed_BY_author
но имея some_author_subscribed_TO_author
?Если он есть, то что это?
P.S.Очевидное решение состоит в том, чтобы
- Измените дизайн базы данных, добавив столбец с именем «направление».
- Создавайте 2 записи каждый раз при создании подписки.
Добавить к автору модель
has_many :subscribed_BY_author, :through => :subscriptions, :source => :subscribed_to, :conditions => "direction = 'by'"
has_many :subscribed_TO_author, :through => :subscriptions, :source => :subscribed_to, :conditions => "direction = 'to'"
Но мне интересно, есть ли решение без изменения дизайна базы данных.
Решение
# Author model
has_many :subscriptions_to, :class_name => "Subscription", :foreign_key => "subscribed_to"
has_many :subscribed_to_author, :through => :subscriptions_to, :source => :author
Насколько я знаю - это работает!:)
Другие советы
Я бы использовал простой HABTM для чего-то простого, но вам в любом случае понадобится таблица соединений.
create_table :subscriptions do |t|
t.column :author_id, :integer
t.column :subscriber_id, :integer
end
Укажите на это Автора:
class Author < ActiveRecord::Base
has_and_belongs_to_many :subscribers
:class_name => "Author",
:join_table => "subscriptions",
:association_foreign_key => "subscriber_id"
def subscriptions # "subscribers" is already included above
self.subscribers.find(:all, :subscriber_id=>author.id) # hopefully not too
end # much SQL
end
Если вы действительно привержены именам своих методов:
def subscribed_to_author
subscribers
end
def subscribed_by_author(author)
self.subscribers.find(:all, :subscriber_id=>author.id)
end
Создайте несколько соединений (я бы сделал SubscriptionsController RESTy)
SubscriptionsController < ApplicationController
def create
@author = Author.find(params[:author_id] # author to be subscribed to
@user = current_user # user clicking the "subscribe" button
@author.subscribers << @user # assuming authors should only
@author.save # be able to subscribe themselves
end
end
Отображаемые имена или что-то еще
@author.subscribers.each do |s|
s.name
end
# ...or...and...also...
<%= render :partial => @author.subscribers -%>
<%= render :partial => @author.subscriptions -%>