Kiedy używać relacji „has_many: through” w Railsach?

123

Próbuję zrozumieć, czym has_many :throughjest i kiedy (i jak) tego używać. Jednak nie rozumiem. Czytam Beginning Rails 3 i próbowałem googlować, ale nie jestem w stanie tego zrozumieć.

Lucky Luke
źródło

Odpowiedzi:

177

Powiedzmy, że masz dwa modele: Useri Group.

Jeśli chciałbyś, aby użytkownicy należeli do grup, możesz zrobić coś takiego:

class Group < ActiveRecord::Base
  has_many :users
end

class User < ActiveRecord::Base
  belongs_to :group
end

A co by było, gdybyś chciał śledzić dodatkowe metadane wokół powiązania? Na przykład, kiedy użytkownik dołączył do grupy, a może jaka jest rola użytkownika w grupie?

Tutaj tworzysz asocjację jako obiekt pierwszej klasy:

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

  # has attributes for date_joined and role
end

Wprowadza to nową tabelę i eliminuje group_idkolumnę z tabeli użytkownika.

Problem z tym kodem polega na tym, że musiałbyś zaktualizować każde inne miejsce, w którym używasz klasy użytkownika, i zmienić ją:

user.groups.first.name

# becomes

user.group_memberships.first.group.name

Ten typ kodu jest do bani i sprawia, że ​​wprowadzanie takich zmian jest bolesne.

has_many :through daje to, co najlepsze z obu światów:

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

Teraz możesz traktować to jak normalne has_many, ale skorzystaj z modelu asocjacyjnego, gdy go potrzebujesz.

Pamiętaj, że możesz to również zrobić za pomocą has_one.

Edycja: ułatwianie dodawania użytkownika do grupy

def add_group(group, role = "member")
  self.group_associations.build(:group => group, :role => role)
end
Ben Scheirman
źródło
2
W tym miejscu dodam metodę do usermodelu, aby dodać grupę. Coś jak zmiana, którą właśnie zrobiłem. Mam nadzieję że to pomoże.
Ben Scheirman
Rozumiem, czy ja też muszę pisać user.groups << group? A może wszystkim zajmuje się to stowarzyszenie?
LuckyLuke
Mam klasę, która jest taka sama jak group_membership i mam problem z używaniem z nią Factory Girl, czy pomógłbyś mi?
Ayman Salah
Użyłbym „has_many: group_memberships”. Używanie liczby pojedynczej zadziała, ale user.group_membership będzie działać, a user.group_memberships nie będzie działać.
calasyr
To była literówka. Naprawiony.
Ben Scheirman
212

Powiedzmy, że masz te modele:

Car
Engine
Piston

Samochód has_one :engine
Silnik belongs_to :car
Silnik has_many :pistons
Tłokbelongs_to :engine

Samochód has_many :pistons, through: :engine
tłokowyhas_one :car, through: :engine

Zasadniczo delegujesz relację modelu do innego modelu, więc zamiast dzwonić car.engine.pistons, możesz to zrobićcar.pistons

cpuguy83
źródło
9
To ma sens!
RajG
24
Nazywam to SACA - ShortAndClearAnswer.
Asme Just
18

ActiveRecord Join Tables

has_many :throughi has_and_belongs_to_manyrelacje funkcjonują za pośrednictwem tabeli łączenia , która jest tabelą pośrednią reprezentującą relacje między innymi tabelami. W przeciwieństwie do zapytania JOIN dane są w rzeczywistości przechowywane w tabeli.

Praktyczne różnice

Dzięki temu has_and_belongs_to_manynie potrzebujesz klucza podstawowego i uzyskujesz dostęp do rekordów za pośrednictwem relacji ActiveRecord, a nie za pośrednictwem modelu ActiveRecord. Zwykle używasz HABTM, gdy chcesz połączyć dwa modele relacją wiele do wielu.

Używasz has_many :throughrelacji, gdy chcesz współdziałać z tabelą złączeń jako modelem Rails, wraz z kluczami podstawowymi i możliwością dodawania niestandardowych kolumn do połączonych danych. To ostatnie jest szczególnie ważne w przypadku danych, które są istotne dla połączonych wierszy, ale tak naprawdę nie należą do powiązanych modeli - na przykład przechowywanie obliczonej wartości pochodzącej z pól w połączonym wierszu.

Zobacz też

W Przewodniku po stowarzyszeniach Active Record rekomendacja brzmi:

Najprostsza praktyczna zasada jest taka, że ​​jeśli chcesz pracować z modelem relacji jako niezależny byt, powinieneś skonfigurować relację has_many: poprzez relację. Jeśli nie musisz nic robić z modelem relacji, prostsze może być skonfigurowanie relacji has_and_belongs_to_many (chociaż musisz pamiętać o utworzeniu tabeli łączącej w bazie danych).

Powinieneś użyć has_many: through, jeśli potrzebujesz walidacji, wywołań zwrotnych lub dodatkowych atrybutów w modelu łączenia.

Todd A. Jacobs
źródło