Relacionamento muitos-para-muitos com ActiveRecord no Rails
16 de Abril de 2008
Esta semana recebi dois e-mails com a mesma dúvida: como funciona o relacionamento muitos-para-muitos no ActiveRecord. Aparentemente, este é um assunto que muitas pessoas têm dúvidas, mas cuja resposta é bastante simples, até. Você pode fazer tal relacionamento de duas maneiras diferentes, como veremos abaixo.
Utilizando o método has_and_belongs_to_many
O método has_and_belongs_to_many possui uma convenção para se nomear a tabela que persistirá os relacionamentos. Você deve colocar ambas as tabelas relacionadas no nome da tabela de relacionamentos, ordenados alfabeticamente e separados por underscore. Se você tem as tabelas "posts" e "categories", o nome de sua tabela de relacionamentos será "categories_posts". Os campos também possuem uma convenção, que é colocar o nome da tabela no singular, adicionando o sufixo "_id". Neste caso, teríamos um arquivo de migração como este:
class AddPostsAndCategories < ActiveRecord::Migration
def self.up
create_table :categories_posts do |t|
t.references :category, :post
end
end
def self.down
drop_table :categories_posts
end
end
Para relacionar ambos os modelos, precisaríamos colocar o seguinte método no modelo Post:
class Post < ActiveRecord::Base
has_and_belongs_to_many :categories
end
Já no modelo Category, o relacionamento seria o seguinte:
class Category < ActiveRecord::Base
has_and_belongs_to_many :posts
end
Muito simples! Apenas com estas poucas linhas você já pode usar diversos métodos, adicionados pelo próprio ActiveRecord.
post = Post.find(:first)
category = Category.find(:category)
# creating relationship through post
post.categories << category
# creating relationship through category
category.posts << post
# getting all posts
categories.posts.each do |post|
puts post.title
end
# getting all categories from a post
post.categories.each do |cat|
puts cat.title
end
# getting all the post ids
category.post_ids
O método has_and_belongs_to_many possui uma grande desvantagem. Você não pode adicionar campos extras aos relacionamentos. No entanto, o método has_many permite que você faça isso.
Utilizando o método has_many
Ao contrário do método has_and_belongs_to_many, você pode ter quantos campos adicionais precisar se fizer o relacionamento com o método
has_many.
Isso é extremamente útil e bastante utilizado por plugins que lidam com ActiveRecord. A convenção para este tipo de relacionamento é um pouco diferente.
Sua tabela pode ter um nome específico que identifica o tipo de relacionamento que está sendo feito, já que ele terá um modelo próprio, que fará o papel
intermediário entre as tabelas relacionadas.
Crie um modelo chamado Categorization. Ele deve ter os seguintes campos:
class CreateCategorizations < ActiveRecord::Migration
def self.up
create_table :categorizations do |t|
t.references :post, :category
t.timestamps
end
end
def self.down
drop_table :categorizations
end
end
Agora, você precisa criar relacionamentos entre este modelo e os demais (Post e Category). No seu modelo Categorization, adicione os seguintes relacionamentos:
class Categorization < ActiveRecord::Base
belongs_to :category
belongs_to :post
end
E em cada um dos outros modelos faça o relacionamento contrário, utilizando o método has_many.
class Post < ActiveRecord::Base
has_many :categorizations
end
class Category < ActiveRecord::Base
has_many :categorizations
end
E aqui entra realmente a parte importante: você irá usar um relacionamento comumente chamado de "has_many :through", que fará uma consulta SQL, unindo os resultados em uma única consulta. No seu modelo Post, adicione mais um relacionamento.
class Post < ActiveRecord::Base
has_many :categorizations
has_many :categories,
:through => :categorizations
end
Faça a mesma coisa no modelo Category.
class Category < ActiveRecord::Base
has_many :categorizations
has_many :posts,
:through => :categorizations
end
Pronto! Seu relacionamento já foi criado. Para testar, faça algo como isto usando o console.
post = Post.find(:first)
# get post categories
post.categories.collect(&:title).to_sentence
category = Category.find(:first)
# get posts by categories
category.posts.collect(&:title).to_sentence
Para criar uma nova associação, você precisa fazer isso através do relacionamento categorizations; caso contrário, você receberá um erro dizendo que não possui um campo para o id.
# through post
post.categorizations.create(:category => category)
# through category
category.categorizations.create(:post => post)
É isso! Se você tem alguma dúvida, envie um e-mail. Se for um assunto que me interessa, ou que interessa a mais pessoas, eu posso escrever algo aqui!
Textos escritos por
Comentários #
Muito bom, simples e direto ao ponto. Routes é uma boa pedida pra um tutorial
[...] Eu vi uma indicação do Nando Vieira para um post que ele deve ter escrito hoje, sobre relacionamento Has And Belongs To Many, o famoso HABTM. Fui ler, numa boa, uma dica excelente por sinal. No entanto, enquanto eu lia acendeu uma luzinha [...]
A dica é ótima! Este tipo de relacionamento realmente deixa quem está iniciando com Rails meio confuso, e esta dica com certeza vai permitir que os "railers" iniciantes tenham maior facilidade para compreender este relacionamento que é uma maravilha!
[...] Relacionamento muitos-para-muitos com ActiveRecord no Rails, Entenda como funciona este tipo de relacionamento e como aplicar algumas t
Estou começando a desenvolver em rails agora e este artigo tirou uma grande dúvida minha. Muito bem escrito.
Parabéns
Muito bom o artigo. Bem sucinto!
Aew Nando.
Otimo Tutorial.
sou iniciante no Rails
e estava com um problemão exatamente nessa parte
de relacionamentos NxN usando Through.
Brigadaum cara.
e sucessos!
E ai nando blza??
Muito bom este post, to aprendendo rails e estou com dificuldades de entender relacionamentos, mas depois deste post as coisas começaram a ficar mais claras.
Muito bom! Abraço!!
Fala Nando!
Parabéns pelo artigo.
Mutcho bueno ;)
Abraços!
Opa Nando!
Uma dúvida, e em caso de haver duas tabelas com o mesmo sufixo?
Por ex: forum_categories e outra chamada forums
Como fica nesse caso? tanto faz?
Abraço!
Nando, muito esclarecedor esse post! Só fazendo uma adendo, a tabela gerada para associar os 2 models usando HABTM não pode ter PK. Assim, o método self.up da migration deve estar dessa forma:
http://pastie.org/617656
Abraço!
Muito bom! Me ajudou aqui!
Olá, Nando.
Parabéns pelo seu post. É de muita ajuda. Está nos meus favoritos com certeza.
Me surgiu uma dúvida para um problema que eu tenho.
Vamos supor que eu tenho o modelo Friends e quero fazer uma relação N:N do modelo Friends com ele mesmo.
Friends N:N Friends
Tem ideia de como ficaria??
Aguardo uma resposta. Obrigado.
Abraços,
Deixe um comentário