How to neatly handle multiple checkboxes with Rails

This weekend, I have a written my first Rails application to handle
technical reports at school. The model is very simple, and the only
part I don't know how to deal with is the association between authors
and reports.

Right now, the edit view for the reports has a table with all the
authors in the database and a checkbox for each one of them. If the
list of authors grows too big, this part is going to be a bit too
rough and unusable. How can I handle this "The Rails Way"?

I thought a very neat solution would be to use an input field like the
ones GMail has for the address book. Is that difficult to implement
using Rails and Ajax? Any ideas on how to do that? Are any ActionView
helpers available to do this? How can I dinamically add more input
fields (pushing an "add author" button)?

In case this solution is way too far from being done, how can I handle
checking and unchecking the different authors in that table on the
Controller side? What I would need to do is remove the unchecked
authors and add the checked ones. This involves iterating over the
collection of authors and checkboxes and compare the previous values
with the current ones (which may render the code a little bit
complicated). How do you all usually solve this problem? Is there any
solution for this available in Ruby?

Thanks to everyone,
Ed

···

--
Encontrá a "Tu psicópata favorito" http://tuxmaniac.blogspot.com

"Tener una amiga en Ginebra es como tener quinotos en almibar o uvas en ron."
"Programming is like sex... make one mistake, and support it the rest
of your life."
"Defeat is an accomplishment not even the best of us could achieve."

The functionality of checkboxes is the same as that of a multiple-select field, though the UI is different.

I have two tables in a habtm relationship; following is the (hacked-together) code that I've used (which happens to use selects, but which would be easy to modify for checkboxes).

The short answer is:
When you are saving, get a list of all the items currently stored for the relationship into an array, get the list of all the new items desired into a new array, and then do a two-way difference to figure out which relationships must be removed and which must be added.

Here's the relevant parts of my code:

# POSTGRESQL SCHEMA
create table roles (
   id serial primary key,
   name varchar(100) not null
);

create table acts (
   id serial primary key,
   action_path varchar(100) not null
);

create table acts_roles (
   act_id integer references acts on delete cascade,
   role_id integer references roles on delete cascade,
   primary key ( act_id, role_id )
);

#FILE app/model/act.rb
class Act < ActiveRecord::Base
   has_and_belongs_to_many :roles
end

#FILE app/model/role.rb
class Role < ActiveRecord::Base
   has_and_belongs_to_many :acts
   has_and_belongs_to_many :users

   #This really should be done for all habtm, IMO
   def users=( new_array )
     self.remove_users( self.users - new_array )
     self.users << ( new_array - self.users )
     self.users
   end

   #This really should be done for all habtm, IMO
   def acts=( new_array )
     self.remove_acts( self.acts - new_array )
     self.acts << ( new_array - self.acts )
     self.acts
   end
end

#FILE app/controllers/role.rb
class RoleController < ApplicationController
   def edit
     @role = Role.find_by_id @params[ :id ]
     @page_title = "Edit Role '#{@role.name}'"

     @all_actions = Act.find_all.sort_by{ |act| act.action_path }
     @actions_allowed = @role.acts
   end
        def update
     role = Role.find( role_atts["id"] )
     role.acts = @params['role']['acts'].collect{ |id| Act.find id }
     role.save
   end
end

#FILE app/views/role/edit.rhtml
<!-- snip -->
<fieldset id="actions" class="two_column">
   <legend><strong>Actions</strong> permitted by <%=@role.name%></legend>
   <select name="role[acts]" multiple="multiple" size="<%=select_length%>">
   <%=options_from_collection @all_actions, @role.acts, :action_path %>
   </select>
</fieldset>

#FILE app/helpers/application.rb
module ApplicationHelper

   # Creates a list of HTML option tags based on a collection of model instances
   # with the ability to provide a second collection of elements which should be selected
   def options_from_collection( all_items, selected, text_method=:name, value_method=:id )
     use_include = selected.respond_to?( :include? ) && !selected.is_a?( String )

     all_items.inject() do |options, element|
       val = element.send( value_method )
       text = element.send( text_method )
       is_selected = use_include ? selected.include?( element ) : val == selected
       if is_selected
         options << "<option value=\"#{html_escape(val.to_s)}\" selected=\"selected\">#{html_escape(text.to_s)}</option>"
       else
         options << "<option value=\"#{html_escape(val.to_s)}\">#{html_escape(text.to_s)}</option>"
       end
     end.join( "\n" )
   end
end

···

On Apr 26, 2005, at 1:54 PM, Edgardo Hames wrote:

In case this solution is way too far from being done, how can I handle
checking and unchecking the different authors in that table on the
Controller side? What I would need to do is remove the unchecked
authors and add the checked ones. [...] How do you all usually solve this problem?

Quite a bit, yeah. Gavin, I'm old, so go slow, but you've completely lost me.

If I iterate over a check_box tag (using a render_partial_collection),
what I get is something like:

<input type="checkbox" name="object[method]" value="2"> foo
<input type="checkbox" name="object[method]" value="3"> bar
<input type="checkbox" name="object[method]" value="4"> baz
<input type="checkbox" name="object[method]" value="10"> blurfle
...

but if I select multiple, params has only 1, due to the same key in each one:

@params => {object => {method => 2}} (or 3, or 4, or 10, but never
more than one of those)

How do I structure the checkbox tag (or can I?) so that I can end up
with some indication that (ex.) both "foo" and "bar" were checked?

···

On 4/29/05, Gavin Kistner <gavin@refinery.com> wrote:

On Apr 26, 2005, at 1:54 PM, Edgardo Hames wrote:
> In case this solution is way too far from being done, how can I handle
> checking and unchecking the different authors in that table on the
> Controller side? What I would need to do is remove the unchecked
> authors and add the checked ones. [...] How do you all usually solve
> this problem?

The functionality of checkboxes is the same as that of a
multiple-select field, though the UI is different.

I think the name should be object[method] or something like that,
check this out: http://www.eye.cc/fortopic941.html

···

On Thu, Jun 09, 2005 at 12:43:46PM +0900, Michael Campbell wrote:

On 4/29/05, Gavin Kistner <gavin@refinery.com> wrote:
If I iterate over a check_box tag (using a render_partial_collection),
what I get is something like:

<input type="checkbox" name="object[method]" value="2"> foo
<input type="checkbox" name="object[method]" value="3"> bar
<input type="checkbox" name="object[method]" value="4"> baz
<input type="checkbox" name="object[method]" value="10"> blurfle
...

but if I select multiple, params has only 1, due to the same key in each one:

--
Best regards,
Chris Eidhof