Quando usar uma relação "has_many: through" no Rails?

123

Estou tentando entender o que has_many :throughé e quando usá-lo (e como). No entanto, eu não estou entendendo. Estou lendo Beginning Rails 3 e tentei pesquisar no Google, mas não consigo entender.

LuckyLuke
fonte

Respostas:

177

Digamos que você tenha dois modelos: Usere Group.

Se você quiser que os usuários pertençam a grupos, faça algo assim:

class Group < ActiveRecord::Base
  has_many :users
end

class User < ActiveRecord::Base
  belongs_to :group
end

E se você quiser rastrear metadados adicionais em torno da associação? Por exemplo, quando o usuário ingressou no grupo, ou talvez qual a função do usuário no grupo?

É aqui que você faz da associação um objeto de primeira classe:

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

  # has attributes for date_joined and role
end

Isso introduz uma nova tabela e elimina a group_idcoluna da tabela do usuário.

O problema com esse código é que você precisaria atualizar em todos os lugares em que usar a classe de usuário e alterá-la:

user.groups.first.name

# becomes

user.group_memberships.first.group.name

Esse tipo de código é péssimo e dificulta a introdução de alterações como essa.

has_many :through oferece o melhor dos dois mundos:

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

Agora você pode tratá-lo como um normal has_many, mas obtenha o benefício do modelo de associação quando precisar.

Observe que você também pode fazer isso com has_one.

Editar: facilitando adicionar um usuário a um grupo

def add_group(group, role = "member")
  self.group_associations.build(:group => group, :role => role)
end
Ben Scheirman
fonte
2
É aqui que eu adicionaria um método no usermodelo para adicionar o grupo. Algo como a edição que acabei de fazer. Espero que isto ajude.
21412 Ben Scheirman
Entendo, eu também tenho que escrever user.groups << group? Ou tudo é tratado por essa associação?
LuckyLuke
Eu tenho uma classe que é a mesma que a group_membership e estou tendo problemas para usar a menina de fábrica com isso, você me ajudaria?
Ayman Salah
Eu usaria "has_many: group_memberships". Usar o singular funcionará, mas user.group_membership funcionará como uma coleção e user.group_memberships não funcionará.
21715 Calasyr
Isso foi um erro de digitação. Fixo.
Ben Scheirman
212

Digamos que você tenha estes modelos:

Car
Engine
Piston

Um carro has_one :engine
Um motor belongs_to :car
Um motor has_many :pistons
Pistãobelongs_to :engine

Um carro de has_many :pistons, through: :engine
pistãohas_one :car, through: :engine

Essencialmente, você está delegando um relacionamento de modelo para outro modelo; portanto, em vez de precisar chamar car.engine.pistons, bastacar.pistons

cpuguy83
fonte
9
Isso faz muito sentido!
RajG
24
Eu chamo isso de SACA - ShortAndClearAnswer.
Asme Apenas
18

Tabelas de junção ActiveRecord

has_many :throughe os has_and_belongs_to_manyrelacionamentos funcionam por meio de uma tabela de junção , que é uma tabela intermediária que representa o relacionamento entre outras tabelas. Ao contrário de uma consulta JOIN, os dados são realmente armazenados em uma tabela.

Diferenças práticas

Com has_and_belongs_to_many, você não precisa de uma chave primária e acessa os registros por meio das relações do ActiveRecord, e não por um modelo do ActiveRecord. Você costuma usar o HABTM quando deseja vincular dois modelos a um relacionamento muitos-para-muitos.

Você usa um has_many :throughrelacionamento quando deseja interagir com a tabela de junção como um modelo Rails, completo com chaves primárias e a capacidade de adicionar colunas personalizadas aos dados ingressados. O último é particularmente importante para dados que são relevantes para as linhas unidas, mas realmente não pertencem aos modelos relacionados - por exemplo, armazenar um valor calculado derivado dos campos na linha unida.

Veja também

Em Um guia para associações de registros ativos , a recomendação diz:

A regra mais simples é que você configure um relacionamento has_many: through se precisar trabalhar com o modelo de relacionamento como uma entidade independente. Se você não precisar fazer nada com o modelo de relacionamento, pode ser mais simples configurar um relacionamento has_and_belongs_to_many (embora seja necessário lembrar de criar a tabela de junção no banco de dados).

Você deve usar has_many: through se precisar de validações, retornos de chamada ou atributos extras no modelo de junção.

Todd A. Jacobs
fonte