Checkbox list in Ruby on Rails using HABTM

July 3rd, 2008 by Justin Ball

Checkboxes are one of those things that look easy and should be easy, but they aren't always easy. I needed a solution that could create a checkbox list of languages that a user speaks. So I don't forget here's how to do it:

The migrations are important. You have to be sure to exclude the id parameter when you create languages_users or you will get ' Mysql::Error: #23000Duplicate entry' due to the fact that ActiveRecord will try to store a value in the id field that indicates which model created the entry (User.languages << vs Langauges.users). The other option is the create the id parameter so that the direction is maintained but be sure that it is not created as a primary key.

 
class LanguagesUsers < ActiveRecord::Migration
    def self.up
        create_table :languages_users, :id => false, :force => true do |t|
            t.integer :user_id
            t.integer :language_id
            t.timestamps
        end
    end
 
    def self.down
        drop_table :languages_users
    end
end
 
 
class Languages < ActiveRecord::Migration
 
    def self.up
        create_table "languages", :force => true do |t|
            t.string  "name"
            t.string  "english_name"
            t.integer "is_default", :default => 0
        end
    end
 
    def self.down
        drop_table "languages"
        drop_table "users_languages"
    end
end
 
 
class Users < ActiveRecord::Migration
 
    def self.up
        create_table "users", :force => true do |t|
            t.string  "login"
            # other fields excluded for brevity
        end
    end
 
    def self.down
        drop_table "users"
    end
end
 

Here are my models:
user.rb

 
class User < ActiveRecord::Base
    has_and_belongs_to_many :languages
end
 

language.rb:

 
class Language < ActiveRecord::Base
  has_and_belongs_to_many :users
end
 

In my user_controller.rb the create and update methods are simple. This is thanks to the fact that you get a language_ids method on the user object because of the HABTM relationship.

 
    def create
        @user = User.new(params[:user])
        @user.save
    end
 
    def update
        params[:user][:language_ids] ||= []
 
        @user = User.find(current_user)
 
        if @user.update_attributes params[:user]
            flash[:notice] = "Settings have been saved."
            redirect_to edit_user_url(@user)
        else
            flash.now[:error] = @user.errors
            setup_form_values
            respond_to do |format|
                format.html { render :action => :edit}
            end
        end
 
    end
 

On to the view:

 
<ul class="checkbox-list">
  <% @languages.each do |language| -%>
<li><%= check_box_tag "user[language_ids][]", language.id, user_speaks_language?(language) -%> <%= language.english_name -%></li>
 
  <% end -%>
</ul>
 

NOTE: I had an error in my original method. This code:

 
<li><%= f.check_box :language_ids, {:checked => user_speaks_language?(language)}, "#{language.id}", ""  -%> <%= "#{language.english_name}" -%></li>
 

should be this:

 
<li><%= check_box_tag "user[language_ids][]", language.id, user_speaks_language?(language) -%> <%= language.english_name -%></li>
 

And we'll need this helper method:

 
def user_speaks_language?(language)
    if @user && !@user.login.nil? # no sense in testing new users that have no languages
        @user.languages.include?(language)
    else
        false
    end
end
 

The result is that you will get a list of check boxes that update values in the join table that is part of the has_and_belongs_to_many relationship. Rails is very cool

Tags:   · · · · 8 Comments

Leave A Comment

8 responses so far ↓

  • 1 Vermin Jul 21, 2008 at 8:09 am

    It’s not working for me. Everytime i try to update only the first checkbox is added. Seems like my category_ids not act as an array even if I have the HABTM relationship.

    If I look at my request, all the values is sent to the server but only the first one is added.

    Any ideas what to do?

  • 2 Justin Ball Jul 22, 2008 at 11:14 am

    Check your Rails version. I am on 2.1. Not sure if that makes a difference or not.

  • 3 Frank Jul 30, 2008 at 11:23 pm

    @Vermin - did you discover an answer to your problem? I have exactly the same thing. I am running Rails 2.1.0.

  • 4 Vermin Aug 1, 2008 at 7:53 am

    I also running 2.1.0 and haven’t found a solution yet. Sounds strange that it works for you but not for us.

  • 5 Frank Aug 1, 2008 at 8:05 am

    I found another implementation that worked like a champ for me: http://media.railscasts.com/videos/017_habtm_checkboxes.mov

    Unfortunately it is video, but it is short and more importantly, it worked for me. Slightly different approach but nothing radical.

  • 6 Justin Ball Aug 1, 2008 at 12:43 pm

    Thanks for the link. I haven’t had time to look into the issue so I am glad that you found a technique that works.

  • 7 Justin Ball Aug 5, 2008 at 4:52 pm

    There was an error in my code. Thanks to Frank for pointing out the link with the proper method.

  • [...] http://www.justinball.com/2008/07/03/checkbox-list-in-ruby-on-rails-using-habtm/ Read More [...]