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”=>“xx@example.com”},
“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) = 'xx@example.com
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 = 'xx@example.com, 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](http://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](http://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](http://user.id)
  end 
end

end
end