← Back to Upcase

Handling new association objects with accepts_nested_attributes_for


(Paul Morganthall) #1

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:

  1. Populate the choices with a collection from the database.
  2. Typing into the field displays choices that include the given text (search).
  3. 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!


(Geoff Harcourt) #2

Hi @slothbear, before you go too far down this road, you might want to consider alternate approaches to accepts_nested_attributes_for. After lots of frustration with quirky behavior in accepts_nested_attributes_for (particularly with respect to validation logic that relies on more than one model at a time), I have started building behavior for more complicated multi-model forms through a form object.

Here’s a couple good intros to form objects.

The first one is from CodeClimate (see #3, but the whole post is interesting): http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/

The second is from Pivotal Labs:
http://pivotallabs.com/form-backing-objects-for-fun-and-profit/

I used behavior like this recently to build a complicated ballot form for judges at a competition. Each judge’s ballot had to be pre-populated with some information, and there was some complicated validation behavior.

Hope this helps!

-Geoff


(Paul Morganthall) #3

Thanks for the suggestions & articles.