I was recently called upon to make a horrible select input for a Ruby on Rails project - essentially, there was this model, let’s call it a Snafu, and one Snafu could share an attachment to any number of models.
This select was difficult because I couldn’t just have a selection of record ID’s - I would have to use both the model type as well as the model ID in order to be able to track down these records when the form was submitted. Here’s how I did it:
**Step 1: Rendering the select ** First of all, we need to render a selection box for the ‘New Snafu’ form. This selection box should be populated by a number of model collections, keyed by an identifier that specifies both the model name and the model ID. Time for a model method, and a helper!
Now that we have these helpers, we have a grouped collection of options, in which each option tag within the select will look something like ‘
<option value="1-Foo">Foo #1</option>’ - let’s render our select.
This is a basic form of course - your form will have all the other attributes you need, but the key thing to notice here is that we aren’t trying to load against our Polymorphic ‘item’ association within the form - instead, we’re just going to send through a ‘item_identifier’ parameter that we can use to find the item in our model. Let’s take a look at the sections we need.
First of all, we need to have the Polymorphic association in our model, if you haven’t already done this.
Next of all, we need to add an
attr_writer to our model - this will hold the ‘item_identifier’ from our form submission until we are ready to use it.
Finally, we need to add a
before_validation filter to associate the record identified by our item_identifier with our model instance:
Let’s take a look at what we’ve done there - really, the meat of it is in our set_item method, that gets called before validations run (so that we will have a real item set before we may need to run validations on that item). First of all, set_item checks that we have set a item_identifier - if we haven’t we don’t want to run into Nil exceptions! Given an item_identifier is present, we want to split our identifier (Remember, this is in the format id-class name), into two parts. Finally, we do a little Ruby magic to get the class using Kernel.const_get, and then call
find on it with the ID that we want. If anything goes wrong with this bit (The class not existing, for example), then we just set item to nil.
There we have it then - it’s a horrible situation, but I feel like it’s a pretty good approach. The logic is where it should be (models and helpers), the views and controllers feel clean, and it’s flexible to be reused pretty easily.
As a final tweak, there’s one more change you may want to make - that is setting the selected item when we return to our form. If you take a look at the helper method we defined above, you’ll notice that we already support a selected option - we just need to pass this in. To do this, we want to add a method to our ‘Snafu’ class that will return a string of the item id and the item name concatenated with a dash - i.e., the format that our select box in the form is expecting. Go ahead and add the method now:
With that method, can can just change our select input in the form to make use of this value:
There we are! Now when we show the form, the selected item will appear, just as we wanted.