Rails Nested Attributes with Scoped Uniqueness Validation of Association

This is going to be a long one, so grab your favorite beverage…

Problem

I have a fairly standard belongs_to  and has_many model association. And I want to save nested attributes along with the parent when creating a new parent record. But, I don’t want the user to be able to submit duplicate values for the child records.

Sounds easy. That’s what I thought when I started.

My Solution

 Models

We have an Organization and a SubUnit models, pretty standard stuff here.

Controller

The controller is simple (standard actions removed). Keep scrolling down.

View

Now it’s getting interesting.

Your form should look like this.

/organizations/new

Polluting Links with Javascript

Like everyone else who has started down the road to nested attribute bliss, you have watched Ryan Bates’ Railscast on Complex Forms. It’s superlative.

But there’s one thing I didn’t like… the heavy handed approach of using link_to_function. Especially when including a fair amount of escaped html into an onclick script. I’m not saying it’s wrong, just that I believe html should stay where it belongs… in the DOM. Heck, they even created a gem nested_form.

I think we can make it simpler, painless, elementary.

That’s where the following little ditty comes in handy. We are creating the exact same field structure that is used for entering a SubUnit, but hiding the html so that we can use JavaScript to copy-and-append to the visible entry area.

Line #1: hide the div.

Line #2: use fields_for to generate the proper field id and name attributes so that it is submitted as a child object to our Organization. Notice the SubUnit.new? That prevents this block from iterating over all the @organization.sub_units that may be built, either as a new object (controller line #5), or when re-rendered due to a validation error with @organization.

Line #2: don’t forget the child_index: ‘new_sub_unit’ option! Without it, rails will attempt to index the fields id and name attributes with numbers, which in this case would be 0 (zero), and mess up the prior sub_unit fields. IOW, you will end up with two params[:organization][:sub_units_attributes][0] elements.

If you don’t understand all the details, take a few minutes and read the ActiveRecord::NestedAttributes::ClassMethods.

Now for some JS magic:

Pretty simple, bind to the click events of the add-sub-unit-button and remove-sub-unit-button to functions.

add_sub_unit function copies the new-sub-unit-fields div, replacing the text of ‘new-sub-unit’ in the id and name attributes of the input/label elements with an integer.

remove_sub_unit function hides the input field group, and set the ‘_destroy’ hidden field to “1” (‘true’ also works) so that it will not be created when the form is submitted.

Inspiration for this JavaScript goes to Ryan Bates.

Status Check

At this point we have a parent model, Organizations, with nested attributes for children SubUnits. When creating a new Organization, you can add and remove SubUnits. The entire relationship is submitted with a single form post.

Eliminate Duplicates in the DOM

Everyone else does it in model validations, manipulation of the params hash, or some other convoluted way. But I decided to eliminate duplicates before the submission of the parent form. Pretty easy, actually.

When the Create Organization button is clicked, the above JavaScript loops through all the SubUnit form field elements, marking duplicate entries hidden ‘_destroy’ field with ‘true’, thus preventing them from being saved. Could you just remove the elements? Sure.

Inspiration for the JavaScript remove duplicates in an array to Roman Bataev in this post.

Screen Shot 2012-12-21 at 4.38.21 PM

If you watch your logs, you’ll see that all the SubUnits attributes are submitted, but only those marked with the hidden field ‘_destroy’ == ‘false’ will be created.

The astute reader will also notice that the SubUnit model is attempting to validate the uniqueness of the name attribute, scoping it to a non-existant Organization. I admit it would be best to skip this validation, but I will leave that exercise to you, humble reader.

Sample App

What? You wanted a demo app on the GitHubs? You shall be rewarded, post haste.

Synopsis

The Good

  • Submission of a parent Organization with children SubUnits utilizing nested attributes.
  • Duplicate SubUnits are removed.
  • Darn simple, easy to reproduce. Pretty darn unobtrusive, too.
  • No funky helpers to jack some html into a link element.
  • Is it better than Ryan Bates’ solution? No. Just simpler.

The Bad

  • Only works on creating a new Organization. Does not work for editing an Organization with the ability to add additional SubUnits or destroy existing SubUnits. Future post?
  • My JavaScript is not abstracted to make it easily reusable. Fork it, and submit a patch if you so desire.
  • The SubUnit validation should be skipped when creating through an Organization.
  • No fallback if JavaScript is disabled.

Additional Reading

 

Posted in Rails. Tagged with , .