Когда нужно использовать отношение "has_many: through" в Rails?

Я пытаюсь понять, что такое has_many :through и когда его использовать (и как). Однако я не понимаю. Я читаю "Начало Rails 3", и я попробовал Google, но я не могу понять.

Ответ 1

Скажем, у вас две модели: User и Group.

Если вы хотите, чтобы пользователи принадлежали к группам, вы можете сделать что-то вроде этого:

class Group < ActiveRecord::Base
  has_many :users
end

class User < ActiveRecord::Base
  belongs_to :group
end

Что делать, если вы хотите отслеживать дополнительные метаданные вокруг ассоциации? Например, когда пользователь присоединился к группе или, возможно, роль пользователя в группе?

Здесь вы создаете ассоциацию объекта первого класса:

class GroupMembership < ActiveRecord::Base
  belongs_to :user
  belongs_to :group

  # has attributes for date_joined and role
end

Это вводит новую таблицу и исключает столбец group_id из таблицы пользователя.

Проблема с этим кодом заключается в том, что вам придется обновлять все, где вы используете пользовательский класс, и изменять его:

user.groups.first.name

# becomes

user.group_memberships.first.group.name

Этот тип кода отстой, и он вносит такие изменения, как это больно.

has_many :through дает вам лучшее из обоих миров:

class User < ActiveRecord::Base
  has_many :groups, :through => :group_memberships  # Edit :needs to be plural same as the has_many relationship   
  has_many :group_memberships
end

Теперь вы можете относиться к нему как к нормальному has_many, но при необходимости используйте преимущества модели ассоциации.

Обратите внимание, что вы также можете сделать это с помощью has_one.

Изменить: упростить добавление пользователя в группу

def add_group(group, role = "member")
  self.group_associations.build(:group => group, :role => role)
end

Ответ 2

Скажите, что у вас есть эти модели:

Car
Engine
Piston

Автомобиль has_one :engine
Двигатель belongs_to :car
Двигатель has_many :pistons
Поршень belongs_to :engine

Автомобиль has_many :pistons, through: :engine
Поршень has_one :car, through: :engine

По сути, вы делегируете отношение модели к другой модели, поэтому вместо вызова car.engine.pistons вы можете просто сделать car.pistons

Ответ 3

Таблицы соединений ActiveRecord

has_many :through и has_and_belongs_to_many связаны через таблицу соединений, которая представляет собой промежуточную таблицу, которая представляет взаимосвязь между другими таблицами. В отличие от запроса JOIN данные фактически хранятся в таблице.

Практические отличия

С has_and_belongs_to_many вам не нужен первичный ключ, и вы получаете доступ к записям через отношения ActiveRecord, а не через модель ActiveRecord. Обычно вы используете HABTM, если хотите связать две модели с отношением "многие-ко-многим".

Вы используете связь has_many :through, когда хотите взаимодействовать с таблицей соединений как модель Rails, в комплекте с первичными ключами и возможность добавлять собственные столбцы к объединенным данным. Последнее особенно важно для данных, относящихся к объединенным строкам, но на самом деле не относится к связанным моделям, например, сохраняя вычисленное значение, полученное из полей в объединенной строке.

См. также

В Руководство для активных ассоциаций записей, рекомендация гласит:

Самое простое правило состоит в том, что вы должны настроить has_many: через отношения, если вам нужно работать с моделью отношений как независимой сущности. Если вам не нужно что-либо делать с моделью отношений, может быть проще установить отношения has_and_belongs_to_many (хотя вам нужно помнить о создании таблицы соединения в базе данных).

Вы должны использовать has_many: через, если вам нужны проверки, обратные вызовы или дополнительные атрибуты в модели объединения.

Ответ 4

Я предлагаю пройти через

http://guides.rubyonrails.org/association_basics.html

http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html

Это официальная документация по рельсам, и они хорошо документированы.

также http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_many.

Существует также скринкаст ryan bates здесь http://railscasts.com/episodes/47-two-many-to-many