I posted an ancestor of my question on the Boston.rb mailing list a while back. I’ve crafted a solution using several techniques I found through lots of searching. I’d appreciate any comments on the implementation.
My goal is to have a select-like menu that meets these requirements:
- Populate the choices with a collection from the database.
- Typing into the field displays choices that include the given text (search).
- Allow the user to add items not already on the list.
The Harvest Chosen library provides a nice jQuery select menu with search. The koenpunt fork of the library includes the ability to add options. Following the advice given when I asked on the mailing list, I’m using accepts_nested_attributes_for
to create new options added by the user.
The main model in my demo app (available on github and heroku) is Doctor. Each Doctor can be assigned a Specialty via a select menu.
<%= f.fields_for :specialty do |specialty_form| %>
<%= specialty_form.collection_select :id,
Specialty.all, :id, :name,
{include_blank: true},
{class: 'chosen-select'}
%>
<% end %>
Using id
as the specialty method resulted in errors from the specialty_attributes setter: ActiveRecord::RecordNotFound:
Couldn't find Specialty with ID=3 for Doctor with ID=
I found some advice (and Rails source code) saying id
cannot be mass-assigned via the setter. That led me to advice to implement the setter myself.
def specialty_attributes=(attrs)
self.specialty =
Specialty.find_by_id(attrs[:id]) ||
Specialty.new(name: attrs[:id])
end
The new setter finds the existing Specialty if the user selected one from the list. If not found,
it means that the user entered a new option, so a new specialty is assigned – “moving” the id given to the name
attribute. This works, but I already have four options-addable fields on the
production form, and there will probably be more. Also my setter doesn’t do nearly as much as the one Rails generates, but I don’t have need for things like reject and destroy. Well, not yet.
I also had to add the include_id
option to the fields_for
call, or the hidden field it generates conflicts with the select field, causing edits to the Specialty to be ignored.
f.fields_for :specialty, include_id: false do |specialty_form|
All input is welcome. Thanks!