Quando você mapeia as associações entre seus models na sua aplicação em Rails, eventualmente você vai cair em um caso de relação NxN, quando dois models tem e pertencem um ao outro. Nesse caso, você precisa escolher entre duas formas de implementar utilizando o Active Record: uma relação has_many through ou has_and_belongs_to_many.
Devivo à maior facilidade aparente, você pode ser induzido a pensar que utilizar uma relação has_and_belongs_to_many é a melhor opção. Se há essa estratégia menos verbosa implementada no Active Record, ela deveria ser a estratégia a ser escolhida, certo? Errado. Quando você cria associações entre seus models, você quase nunca sabe como essa relação vai se expandir à medida que a aplicação cresce. Nesse post vou demonstrar um potencial perigo do uso da relação has_and_belongs_to_many. Com algum trabalho adicional, utilizando a relação has_many through com uma tabela de junção você provê uma flexibilidade muito maior a longo prazo.
Vamos começar explicando a utilização desses métodos similares de associação:
has_and_belongs_to_many
Vamos supor um caso simples de relação NxN: Membro e Equipe. Um membro pode participar de mais de uma equipe, e esta por sua vez pode conter mais de um membro. A simples declaração abaixo é suficiente para configurar essa associação. Mas vou mostrar um pouco mais à frente como isso pode ser problemático.
1 2 3 4 5 6 7 |
class Membro < ApplicationRecord has_and_belongs_to_many :equipes end class Equipe < ApplicationRecord has_and_belongs_to_many :membros end |
has_many through
Uma associação has_many through é usada para configurar uma relação de muitos-para-muitos usando um outro model em sua aplicação. Essa associação usa uma tabela de junção que conecta os dois models, permitindo que cada um deles tenha e pertença ao outro. Neste caso, com o uso dessa relação de junção, o mesmo esquema anterior ficaria da seguinte forma:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Membro < ApplicationRecord has_many :membro_equipes has_many :equipes, through: :membro_equipes end class Equipe < ApplicationRecord has_many :membro_equipes has_many :membros, through: :membro_equipes end class MembroEquipe belongs_to :membro belongs_to :equipe end |
Há alguns passos adicionais nesta configuração, já que você precisa gerar um model adicional e inserir mais algumas linhas de código afim de implementar a utilização da tabela de junção. Mas esses minutos a mais vão te salvar de horas de retrabalho e dores de cabeça se você precisar expandir essa junção no futuro. Veja por quê:
Quando usar has_and_belongs_to_many (spoiler: nunca)
O Guia do Rails propõe que “Você deve usar has_many through se você precisa de validações, callbacks ou atributos extras no model de junção.”. Aí que está a questão: como você pode prever a princípio que você não vai precisar de um desses recursos no futuro? No exemplo em questão, vamos supor que gostaríamos de, além de registrar a relação Membro x Equipe, armazenar também o cargo do membro nessa equipe ou número de horas empregados.
Utilizando o has_many through, nós apenas precisamos adicionar essas informações na tabela de junção, que já está pronta para suportar essas adições. No caso de has_and_belongs_to_many, nós teríamos que dar um passo atrás e ter o retrabalho de fazer o mesmo processo que poderia ter sido implementado de imediato (o que pode ser um problema ainda maior se o seu banco já tiver sido populado).
E você, o que acha? Você vê algum motivo pelo qual utilizar has_and_belongs_to_many poderia ser mais vantajoso? Deixe sua opinião nos comentários!
Referência: blog.flatironschool.com/why-you-dont-need-has-and-belongs-to-many/
4 Comentários
Sannytet
12 de dezembro de 2018 at 05:03Nice posts! 🙂
___
Sanny
João Moura Lima
7 de Fevereiro de 2019 at 16:38Muito interessante o post, direto ao ponto e intrigante, parabéns!
Rafael Oliveira
22 de Abril de 2022 at 10:24Persuasivo, muito bom o post!!
Rafael Bilar
10 de agosto de 2022 at 15:53Muito bom. Obrigado