<img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id=706355809571598&amp;ev=PageView&amp;noscript=1">
Free Assessment
Estimate Project
Menu
Estimate Project

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

by Stéphane A., on 25 June 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
1
2
3
4
5
6
7
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
1
2
3
4
5
6
7
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
1
2
3
4
5
6
class Classification < ActiveRecord::Base

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

end

And the form:

new.html.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<%= 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
1
2
3
4
5
6
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<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.

New Call-to-action

 
Topics:Under the hood

Comments