HABTM updating/saving behaviour

Newbie question (I guess).

I have a User whom can have some Preference. So the scracth model is:

class User < ActiveRecord:Base
[…]
has_and_belongs_to_many :preferences
[…]
end

class Preference < ActiveRecord:Base
[…]
has_and_belongs_to_many : users
[…]
end

In the migration I have created a table preferences_users with primary
keys
preference_id and user_id

In the view the choice of which preferences are connected to and user is
performed by means of a checkbox sets.

In my user_controller scalffold generated actions “create” and “update”
I
added the following lines:

[…]
params[:preferences].each { |k,v|
if v == 1 then #if checkbox is checked
preferences << Preference.find(k)
end
}
[…]
the user is saved or updated

So I have two question:

  1. Is there a better way to perform that check, ot any helper that
    already
    do that task?
  2. Actually in the join table preferences_users nothing is saved. I have
    to
    force the saving ?

The ROR Wiki has a pretty complete solution that might do what you want

http://wiki.rubyonrails.org/rails/pages/CheckboxHABTM

The crux of the matter involves creating a sub-hash of preference_ids
that
is then used to find the preferences and map them to the user. Then
user.save saves the user and all the dependent preference data. It does
involve one oddity - you have to use the older style check_box_tag
helper -
even if the rest of your form is written using form_for syntax. I did
some
searching and experimenting and could not find any way to use the
check_box
helper and constrict the list I would need for mapping.

I followed the directions on the wiki page - and snagged some code from
an
older version of ActiveRBAC - and my first attempt worked. However, I am
trying to add another, practically identical set of checkboxes but the
mapping isn’t working. Or to be more specific, the log shows that the
original mappings are deleted, the new mappings are created, AND THEN
THE
MAPPINGS ARE DELETED AGAIN!? I cannot for the life of me figure out
where
that extra delete is coming from. I tried removing the roles stuff from
the
view and controller, but that didn’t make the event mapping work.

development.log

Processing UserController#update (for 131.215.130.72 at 2007-07-27
14:34:59)
[POST]
Session ID: 663471e0ab9cbe10e33b4ce5d4bbffd6
Parameters: {“user”=>{“event_ids”=>[“1”, “6”], “role_ids”=>[“1”, “2”],
“first_name”=>“C”, “last_name”=>“K”, “email”=>“[email protected]”},
“commit”=>“Update”, “action”=>“update”, “id”=>“2”,
“controller”=>“admin/user”}
^[[4;35;1mUser Columns (0.002214)^[[0m ^[[0mSHOW FIELDS FROM
users^[[0m
^[[4;36;1mUser Load (0.000595)^[[0m ^[[0;1mSELECT * FROM users WHERE
(users.id = 2) LIMIT 1^[[0m
^[[4;35;1mRole Columns (0.001494)^[[0m ^[[0mSHOW FIELDS FROM
roles^[[0m
^[[4;36;1mRole Load (0.000513)^[[0m ^[[0;1mSELECT * FROM roles WHERE
(roles.rolename = ‘Admin’) LIMIT 1
^[[0m
^[[4;35;1mJoin Table Columns (0.001440)^[[0m ^[[0mSHOW FIELDS FROM
roles_users^[[0m
^[[4;36;1mRole Load (0.000601)^[[0m ^[[0;1mSELECT * FROM roles INNER
JOIN roles_users ON roles.id = roles
_users.role_id WHERE (roles_users.user_id = 2 ) ^[[0m
^[[4;35;1mUser Load (0.000620)^[[0m ^[[0mSELECT * FROM users WHERE
(users.id = 2) ^[[0m
^[[4;36;1mJoin Table Columns (0.001443)^[[0m ^[[0;1mSHOW FIELDS FROM
events_users^[[0m
^[[4;35;1mEvent Load (0.000583)^[[0m ^[[0mSELECT * FROM events INNER
JOIN events_users ON events.id = eve
nts_users.event_id WHERE (events_users.user_id = 2 ) ^[[0m
^[[4;36;1mSQL (0.000158)^[[0m ^[[0;1mBEGIN^[[0m
^[[4;35;1mSQL (0.000139)^[[0m ^[[0mCOMMIT^[[0m
CNK after collecting all the role and event info but before
update_attributes
^[[4;36;1mEvent Columns (0.001768)^[[0m ^[[0;1mSHOW FIELDS FROM
events^[[0m
^[[4;35;1mEvent Load (0.000623)^[[0m ^[[0mSELECT * FROM events WHERE
(events.id IN (1,6)) ^[[0m
^[[4;36;1mSQL (0.000163)^[[0m ^[[0;1mBEGIN^[[0m
^[[4;35;1mevents_users Columns (0.001388)^[[0m ^[[0mSHOW FIELDS FROM
events_users^[[0m
^[[4;36;1mSQL (0.000391)^[[0m ^[[0;1mINSERT INTO events_users
(event_id, user_id) VALUES (1, 2)^[[0m
^[[4;35;1mevents_users Columns (0.001350)^[[0m ^[[0mSHOW FIELDS FROM
events_users^[[0m
^[[4;36;1mSQL (0.000362)^[[0m ^[[0;1mINSERT INTO events_users
(event_id, user_id) VALUES (6, 2)^[[0m
^[[4;35;1mSQL (0.004550)^[[0m ^[[0mCOMMIT^[[0m
^[[4;36;1mRole Load (0.000557)^[[0m ^[[0;1mSELECT * FROM roles WHERE
(roles.id IN (1,2)) ^[[0m
^[[4;35;1mJoin Table Columns (0.001406)^[[0m ^[[0mSHOW FIELDS FROM
roles_users^[[0m
^[[4;36;1mRole Load (0.000636)^[[0m ^[[0;1mSELECT * FROM roles INNER
JOIN roles_users ON roles.id = roles
_users.role_id WHERE (roles_users.user_id = 2 ) ^[[0m
^[[4;35;1mSQL (0.000164)^[[0m ^[[0mBEGIN^[[0m
^[[4;36;1mSQL (0.000153)^[[0m ^[[0;1mCOMMIT^[[0m
^[[4;35;1mSQL (0.000138)^[[0m ^[[0mBEGIN^[[0m
^[[4;36;1mSQL (0.000444)^[[0m ^[[0;1mDELETE FROM events_users WHERE
user_id = 2 AND event_id IN (1,6)^[[0
m
^[[4;35;1mSQL (0.000612)^[[0m ^[[0mCOMMIT^[[0m
^[[4;36;1mSQL (0.000159)^[[0m ^[[0;1mBEGIN^[[0m
^[[4;35;1mUser Load (0.000810)^[[0m ^[[0mSELECT * FROM users WHERE
(LOWER(users.email) = '[email protected]
AND users.id <> 2) LIMIT 1^[[0m
^[[4;36;1mUser Update (0.000581)^[[0m ^[[0;1mUPDATE users SET
created_at = ‘2007-07-10 16:25:56’, last_name = 'K, first_name =
'C,
email = '[email protected], updated_at = ‘2007-07-27 14:34:59’ WHERE
id
= 2^[[0m
^[[4;35;1mSQL (0.000540)^[[0m ^[[0mCOMMIT^[[0m
Redirected to http://

The models:

class User < ActiveRecord::Base
has_and_belongs_to_many :roles
has_and_belongs_to_many :events

end

class Event < ActiveRecord::Base
has_and_belongs_to_many :users

end

The view:

<% form_for :user do |f| %>

Roles

<% for role in Role.find(:all) %> <%= check_box_tag "user[role_ids][]", "#{role.id}", current_role?(role) %> <%= role.rolename %> <% end %>

Events

<% for event in Event.find(:all, :conditions => "signup = true") %> <%= check_box_tag "user[event_ids][]", "#{event.id}", current_event?(event) %> <%= event.title %> <% end %>

<%= submit_tag 'Update' %>

<% end %>

Helpers - to get the current roles/events checked:

module Admin::UserHelper

def current_role?(role)
if @user
@user.roles.include?(role)
else
false
end
end

def current_event?(event)
if @user
@user.events.include?(event)
else
false
end
end

end

And the controller:

class Admin::UserController < ApplicationController
require_dependency ‘user’

def update
@user = User.find(params[:id].to_i)
if request.post?
# get an array of roles and set the role associations
params[:user][:roles] = [] if params[:user][:roles].nil?
roles = params[:user][:roles].collect { |i| Role.find(i) }
@user.roles = roles

  # get an array of events and set the event associations
  params[:user][:events] = [] if params[:user][:events].nil?
  events = params[:user][:events].collect { |i| Event.find(i) }
  @user.events = events

  if @user.update_attributes(params[:user])
    redirect_to :action => "show", :id => @user.id
  end
end

end
end