Как создать «двусторонние» отношения «многие ко многим» в Rails? - PullRequest
5 голосов
/ 14 февраля 2010

Предположим, у нас есть сайт с фотографиями. Любой автор может подписаться на получение обновлений от любого другого автора. Очевидно, что если автор A подписан на автора B, это не означает, что B подписан на A. Поэтому мы строим модели

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

Таким образом, мы можем использовать

  1. some_author.subscribeed_by_author - список авторов, на которых подписан some_author.
  2. Для любой подписки мы можем знать оба конца (кто на кого подписан)

Но вопрос в том, как получить список людей, подписанных на какого-либо автора, используя только рельсы (не используя простой SQL), т.е. получить ответ: «Кто подписан на some_author?»

Вопрос: есть ли в Rails возможность заставить отношения работать с обеих сторон, то есть не только писать some_author.subscribed_BY_author, но и иметь some_author_subscribed_TO_author? Если есть, то что это?

P.S. Очевидное решение -

  1. Изменить дизайн базы данных, добавив столбец с именем "direction"
  2. Создание 2 записей каждый раз при создании подписки
  3. Добавить к автору модель

    has_many: subscribed_BY_author,: through =>: подписки,: source =>: subscribed_to,: условий => "direction = 'by'"

    has_many: подписано_TO_author,: через =>: подписки,: источник =>: подписано_то,: условия => "direction = 'to'"

Но мне интересно, есть ли решение без изменения дизайна базы данных.

Ответы [ 2 ]

2 голосов
/ 14 февраля 2010

Я бы использовал простой 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

Создайте несколько соединений (я бы сделал SubscriptionController для 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 -%>
0 голосов
/ 14 февраля 2010
# Author model
has_many :subscriptions_to, :class_name => "Subscription", :foreign_key => "subscribed_to"
has_many :subscribed_to_author, :through => :subscriptions_to, :source => :author

Насколько я знаю - это работает! :)

...