Associações Many-to-Many utilizando has_many e through.

Posted by aboutDevs on August 25, 2015

Utilizar uma associação many-to-many em uma aplicação Rails com a ideia de ter uma lista de checkboxes gerada através de um dos Models, dando a opção de selecionar os itens desejados é o objetivo desse Post.

Existem algumas maneiras de implementar uma associação desse tipo no Rails, mas neste post será utilizado o has_many e :through, a forma mais recomendada nos posts do Stackoverflow que pesquisei, então vamos adiante.

Versão Ruby: 2.2.2 Versão Rails: 4.2.0

Criando a nossa aplicação e configurando, abra o terminal:

rails new manytomany

cd manytomany

bundle install

rake db:create

Precisaremos de 3 Models pra implementar nosso relacionamento has_many, vou utilizar o scaffold para os dois principais e gerar apenas o Model pra o nosso relacionamento, nesse exemplo trabalharei com os objetos Product, Category (Produto e Categoria), onde um produto pode ter várias categorias e uma categoria contém vários produtos:

rails generate scaffold Product name:string

rails generate scaffold Category name:string

rake db:migrate

rails server

Verifique se está tudo correto em :

localhost:3000/products

e …

localhost:3000/categories

Agora, vamos criar nosso Model responsável pelo relacionamento entre nossos dois anteriores:

rails g model Categorization product_id:integer category_id:integer

Estou chamando o Model de Categorization pra ficar mais expressivo em relação aos outros dois Models e a relação que queremos criar.

Lembrando que é possível criar outros campos nesse Model caso necessário, se quiséssemos.

rake db:migrate

Agora vamos ao código, vamos configurar nossos Models e “ensina-los” a se relacionar:

app/models/product.rb

class Product < ActiveRecord::Base
	has_many :categorizations
	has_many :categories, :through => :categorizations
end

app/models/category.rb

class Category < ActiveRecord::Base
	has_many :categorizations
end

E nosso Model de relacionamento:

class Categorization < ActiveRecord::Base
	belongs_to :producto
	belongs_to :category
end

Testar se está tudo funcionando via Console é uma boa, depois veremos nas Views:

rails console

Vamos cadastrar algumas categorias e um produto:

c1 = Category.new

c1.name = "Celular"

c1.save

c2 = Category.new

c2.name = "Smartphone"

c2.save

p = Product.new

p.name = "iPhone 6"

A partir daqui entenderemos a ‘mágica’, vamos adicionar algumas categorias a esse produto:

p.category_ids = [1,2]

=> [1,2]

Então nossa associação está funcionando, vamos salvar esse produto e testar de outra forma:

p.save

Agora vejamos outra forma de trazer as categorias relacionadas a esse produto, sem apenas trazer os seus ids, mas sim todas as informações:

p.categories

Deve ser mostrado algo do tipo:

=> #<ActiveRecord::Associations::CollectionProxy [#<Category id: 1, name: "Celular", #<Category id: 2, name: "Smartphone">]>

Isso significa que está tudo ok, podemos trabalhar nossas views:

app/views/products/_form.html.rb

<%= hidden_field_tag "product[category_ids][]", nil %>
<% Category.all.each do |category| %>
  <%= check_box_tag "product[category_ids][]", category.id, 
  @product.category_ids.include?(category.id), 
  id: dom_id(category) %>
  <%= label_tag dom_id(category), category.name %>
<% end %>

app/views/products/show.html.rb

<% @product.categories.each do |category| %>
	<p><%= category.name %></p>		
<% end %>

E aqui vai o pulo do gato :), vamos adicionar o nosso array de ids relacionados as Categorias aos nossos atributos em:

app/controllers/products_controller.rb

params.require(:product).permit(:name, categories_ids: [])

et voilà. :)

Pesquisa: RailsCasts

Fonts on Github