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

123

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

Счастливчик Люк
источник

Ответы:

177

Допустим, у вас есть две модели: 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
Здесь я бы добавил в userмодель метод для добавления группы. Что-то вроде той правки, которую я только что сделал. Надеюсь это поможет.
Ben Scheirman
Понятно, мне тоже писать надо user.groups << group? Или всем занимается эта ассоциация?
LuckyLuke
У меня есть класс, который совпадает с group_membership, и у меня возникли проблемы с использованием factory girl, вы бы мне помогли?
Айман Салах
Я бы использовал has_many: group_memberships. Использование единственного числа будет работать, но user.group_membership будет коллекцией, а user.group_memberships не будет работать.
calasyr
Это была опечатка. Исправлена.
Бен Шейрман,
212

Допустим, у вас есть эти модели:

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

cpuguy83
источник
9
В этом есть смысл!
RajG
24
Я называю это SACA - ShortAndClearAnswer.
Asme Just
18

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

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

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

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

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

Смотрите также

В Руководстве по ассоциациям Active Record рекомендация гласит:

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

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

Тодд А. Джейкобс
источник