As you probably already know, Rails has 2 methods of handling Many-to-Many relationships, has_and_belongs_to_many (HABTM) and has_many_through (HMT). Ryan Bates does a great job explaining the difference between the 2 in Railscast #47: Two Many-to-Many. He does seem to indicate the HABTM is effectively deprecated:
Now in the other cases you might want to go with has and belongs to many...you might, but that's kind of going quickly out of date and it has some limitations that you might experience further on in development, so if you are the least bit unsure on which way to go, it's usually better to go with has many through, because that it much more flexible.
Ok, so what if I do want to use simple checkboxes in my interface, but I don't want to use the soon-to-be out of date, inflexible method?
Basically, all you need to do is implement an _ids method for the association, and then you will be good to go. The _ids method is automatically generated for you when you use HABTM, but since HMT is often used with models that aren't just simple join associations, there is no automatic _ids method generated for you.
So let's create an example app where we have a User model which has many Groups through the Membership model:
$ rails example
$ cd example
$ mysqladmin -u root create example_development
$ script/generate scaffold_resource user name:string
$ script/generate scaffold_resource group name:string
$ script/generate scaffold_resource membership user_id:integer group_id:integer
$ rake db:migrate
Ok, now we've got our DB and our models, so we just need to set up the associations in each model:
#app/models/membership.rb
class Membership < ActiveRecord::Base
  belongs_to :user
  belongs_to :group
end
#app/models/group.rb
class Group < ActiveRecord::Base
  has_many :memberships
  has_many :users, :through => :memberships
end
And in the User model, we'll add the associations as well as an implementation for the group_ids method:
#app/models/user.rb
class User < ActiveRecord::Base
  has_many :memberships
  has_many :groups, :through => :memberships
  attr_accessor :group_ids
  after_save :update_groups
  #after_save callback to handle group_ids
  def update_groups
    unless group_ids.nil?
      self.memberships.each do |m|
        m.destroy unless group_ids.include?(m.group_id.to_s)
        group_ids.delete(m.group_id.to_s)
      end 
      group_ids.each do |g|
        self.memberships.create(:group_id => g) unless g.blank?
      end
      reload
      self.group_ids = nil
    end
  end
end
So what's going on here is that first we define an attribute to hold the group_ids. Then, we define a method that will get called after this model is saved. In that callback, first check to see if group_ids is nil, because if it is, we're going to do nothing. Then we iterate through each membership that this record has, delete it if it's group_id is not in our new array of group_ids. Then we remove the group_id from the array, so that anything we have left in the group_ids array after we've gone through all the existing memberships are groups that we should create new memberships for, for this user.
So let's see if this works. So we log into the console and first create some Groups to work with:
$ script/console
Loading development environment.
>> ('A'..'E').each{|n| Group.create!(:name => n) }
=> "A".."E"
Now we can create a User with some groups:
>> foo = User.create!(:name => 'foo', :group_ids => ['1','2','3'])
=> #<User:0x30e2c90...      
>> puts foo.memberships.collect{|m| "#{m.id} => #{m.group.name}\n" }      
1 => A
2 => B
3 => C
As you can see, we specified 3 groups that we wanted this user to have membership in. So there are 3 membership records created. Now let's take this user out of group B and put them in group D:
>> foo.update_attributes(:group_ids => ['1','3','4'])
=> true
>> puts foo.memberships.collect{|m| "#{m.id} => #{m.group.name}\n" }
1 => A
3 => C
4 => D
Notice how A and C still have the same membership id. This is important, because you don't want to just delete all the memberships and create new ones, in case there are other attributes on the membership. Of course if there are other attributes, you probably won't be using checkboxes to edit them, but you get the idea. Let's just check a couple more things to make sure this method works. First, we want to make sure if we update the model without specifying the group_ids that it remains unchanged:
>> foo.update_attributes(:name => 'foo')
=> true
>> puts foo.memberships.collect{|m| "#{m.id} => #{m.group.name}\n" }
1 => A
3 => C
4 => D
Ok, good, and last but not least, if we set group_ids to an empty array, then we want to make sure all of the memberships are deleted:
>> foo.update_attributes(:group_ids => [])
=> true
>> puts foo.memberships.collect{|m| "#{m.id} => #{m.group.name}\n" }
=> nil
Now we just need to update the views in our scaffolding to put checkboxes on the form, which I explained how to do in a previous article called HABTM Checkboxes.