Blog

How to handle many to many associations in nested forms using checkboxes !

Icône flèche bleue vers la gauche
Back to blog
How to handle many to many associations in nested forms using checkboxes !

How to handle many to many associations in nested forms using checkboxes !

June 24, 2012

As everybody know thanks to one of the first awesome Ryan Bates’ railscasts (see http://railscasts.com/episodes/17-habtm-checkboxes or the revised one http://railscasts.com/episodes/17-habtm-checkboxes-revised ), you can easily deal with many to many relations using check_box_tags.

For instance, if we have books and categories, here is the code you could obtain!

The classes:

book.rb

class Book < ActiveRecord::Base

has_many :classifications, :dependent => :destroy, :autosave => true , :inverse_of => :book
accepts_nested_attributes_for :classifications, :allow_destroy => true, :reject_if => :all_blank
has_many :categories, :through => :classifications

end

category.rb

class Category < ActiveRecord::Base

has_many :classifications, :dependent => :destroy, :autosave => true , :inverse_of => :category
accepts_nested_attributes_for :classifications, :allow_destroy => true, :reject_if => :all_blank
has_many :books, :through => :classifications

end

classification.rb

class Classification < ActiveRecord::Base

belongs_to :category, :inverse_of => :classifications
belongs_to :book, :inverse_of => :classifications

end

And the form:

new.html.erb

<%= form_for @book do |f| %>

<p>
<%= f.label :name %>
<%= f.text_field :name %>
</p>

<p>Categories</p>
<ul>
<% @categories.each do |cat| %>
<%= hidden_field_tag "book_category_ids_none", nil, {:name => "book[category_ids][]"}%>
<li>
<%= check_box_tag "book_category_ids_#{cat.id}", cat.id, (f.object.categories.present? && f.object.categories.include?(cat.id)), {:name => "book[category_ids][]"} %>
<%= label_tag "book_category_ids_#{cat.id}", cat.name %>
</li>
<% end %>
</ul>

<%= f.submit %>

<% end %>

But now, how to handle this if we introduce the new concept of library. And furthermore, if you need to had books with all these linked categories directly from the library creation or edition form.

You’ll need to use a unique reference to identify these nested objects, a convenient way exists to solve this problem.

First, install the cocoon gem (All detailed information are available here https://github.com/nathanvda/cocoon)

Then, here is the new library class and the partial form used with cocoon

classification.rb

class Library < ActiveRecord::Base

has_many :books, :dependent => :destroy, :autosave => true , :inverse_of => :library
accepts_nested_attributes_for :books, :allow_destroy => true, :reject_if => :all_blank

end

_book_fields.html.erb

<div>
<h3>Book</h3>

<p>
<%= f.label :name %>
<%= f.text_field :name %>
<p>

<p>Categories</p>
<ul>
<% @categories.each do |cat| %>
<%= hidden_field_tag "#{f.object_name}_category_ids_none", nil, {:name => "#{f.object_name}[category_ids][]"}%>
<li>
<%= check_box_tag "#{f.object_name}_category_ids_#{cat.id}", cat.id, (f.object.categories.present? && f.object.categories.include?(cat.id)), {:name => "#{f.object_name}[category_ids][]"} %>
<%= label_tag "#{f.object_name}_category_ids_#{cat.id}", cat.name %>
</li>
<% end %>
</ul>

<%= link_to_remove_association "Remove book", f %>
</div>

As you can see the method object_name give you the complete name of the newly instanciated object requested by the form. The same way, the method object_id could give you only the id of this object.

Ready to build your software product? Contact us!