Rails: Easy Multiple Selects
Large version here
Again, the geek content is high in here. If you don't give a rat's ass about web developement frameworks, or don't know what one is, you should just finish this visit with a look at the pretty picture.
First, and foremost, Rails is sweet. If you've ever spent any time dealing with a web development framework, as soon as you dig into Rails you will understand these people know the way that is it supposed to be. I remarked to a co-worker that the reason I like Rails is that the database, if done correctly, is transparent. You only need to deal with the logic of the objects, and their persistant storage is secondary.
Jerry and I have been embarking on a black hat operation to sneak more Rails into our application. Recently, we've been looking at an alarming alert mechanism. Within the workings of this particular application I came across some implementation details regarding Rails that I thought I'd share with those that found me through google, just in case I've delt with this problem before you have.
Implementation details have been changed to protect the innocent.
Let's pretend we need to keep track of rubber duckies and the bathtubs that they are found within. Being that I'm coming from a DBA background I'd like to make the database objects first
create table rubber_duckies (id int not null auto_increment, name char(64), primary key (id)); create table bathtubs (id int not null auto_increment, name char(64), primary key (id));
Rails has an amazing amount of magic to deal with references. We want a rubber ducky to be able to go to multiple bathtubs (You never know when Ernie will visit), and a bathtub should be able to house more than a single ducky. This n-to-m relationship can only be encapsulated in a reference table, so create the table:
create table bathtubs_rubber_duckies (bathtub_id int not null, rubber_ducky_id int not null);
You need to put the references in alpha-numeric order for this work. So because we are dealing with bathtubs and rubber duckies we need to respect that bathtubs are first in alphanumeric order. Of note: You can't put an ID column on a reference table, or Rails will puke.
Now we need to tell the Rails models that we have a relationship between the database objects. So after you've generated the scaffold you can edit app/model/bathtub.rb to read:
class Bathtub < ActiveRecord::Base
has_and_belongs_to_many :rubber_duckies
end
and app/model/rubber_ducky.rb to be:
class RubberDucky < ActiveRecord::Base
has_and_belongs_to_many :bathtubs
end
Now that we've established that a ducky can be found in multiple bath tubs and that a bathtub can have multiple duckies, we're finally down to the meat. Most of the hard work is already done because of the strict naming we used in our tables. Firing up the console at this point will show you that a rubber_ducky object will have a collection of bathtubs attached, and vice versa.
From the front end, we want to be able to assign a Rubber Ducky to any number of bathtubs through a multiple select box, so we modify app/views/rubber_duckies/_form.rhtml and add:
<p><b>Bathtubs</b></p>
<% selected = @rubber_ducky.bathtubs.collect {|bt| bt.id.to_i } %>
<select name="rubber_ducky[bathtub_ids][]" size="3", mutliple="multiple">
<%= options_from_collection_for_select Bathtub.find_all, 'id', 'name', selected%>
</select>
The naming of your multiple select with tell rails that the information you are sending back is a list of bathtub identifiers for your ducky that you are editing or creating.
Now, we have support for a multiple select that allows for a subset of a collection to be saved to an object. I've dealt with other frameworks, and with most it's not this nice.




