Patterns for Saving Associated Model Data in Rails

Flatiron School / 19 July 2013

The following is a guest post by Mendel Kramer and originally appeared on his blog. Mendel is currently a student at The Flatiron School. You can follow him on Twitter here.

This post explores different ways of saving data associated in some way to the view we’re on.

In each of these examples, we’d like our controller’s create and update actions to remain unchanged:

Post Author

Set up: We have Posts, each of which has an Author (O2M). In the posts#new view, we’d like to choose which user to associate to this post as the author.

We can implement this using a drop-down and the select form helper.

Don’t forget to pass :author_id to the Post model’s attr_accessible.

Post Categories

Set up: We have Posts which on turn have many Categories (M2M). In the posts#new view, we’d like to choose multiple categories to associate to this post.

We can implement this using a multi-select. This time, we’ll use the collection_selecthelper.

Don’t forget to pass :category_ids to the Post model’s attr_accessible.

PS: This can be easily improved with the Select2 library to great effect. (Also, see Bonus Section, below.)

Nested Form Within Form

Set up: We have Artists which on turn have many Songs (O2M). In the artists#show view, we’d like to create songs which will automatically be associated with this artist.

Many thanks to Ryan Bates for much of the code in this example.

First, we’ll specify in the Artist model that it can accept form attributed for a Song. We will also tell it that it can destroy song objects in its form (more on that in a minute).

Now, when we post fields pertaining to songs together with our artist form data, rails will be able to figure out to create / update the song objects as well.

At this point, in our artist create / update form, we can add nested fields via the fields_forform helper, which we’ll add in a partial.

If you’re sharp, you’ll notice that there’s this checkbox with a _destroy name. This is a actually a special field in Rails. When this is checked (or otherwise evaluates to true), the associated model will get destroyed. This is specifically enabled by the allow_destroy: truewhich we passed to the accepts_nested_attributes_for method in artist.rb.

For this to work, we need to do one more thing, in the new action of the ArtistController, we need to add this line:

This works, but presents us with some problems. What if we want to create more than one?

Well, we could do this: 3.times {@artist.songs <<}, but that’s not really solving the issue; now we’re stuck creating three songs. We want to be able to add and remove any amount of songs. We also promised not to touch the controllers, a promise we just broke.

In this episode, we see how Ryan solves this problem:

First, remove the code we just added from the ArtistController. We’re not changing any controllers, right?

In the form we edited a bit earlier, leave our changes, but add this:

You might be wondering why you never saw the link_to_add_fields method before. You haven’t because it doesn’t exist yet.

Lets create it. In your application_helper.rb file, add this code:

In a nutshell, this creates a link that embeds the Song form in its data attribute the way it exists in a _?_fields.html.erb partial, where ? is what you passed in as the third param, i.e. songs. We created this partial already, but this method will work for any nested resource, so long that you name the partial correctly.

Once that done, all we have left to do is add the JS that will allow us to append the embedded form in to the DOM. We’ll also change the delete field from a checkbox to a hidden field. Instead, we’ll implement a “Remove Song” link, tied to which a “click” event handler will hide the form, and set the _destroy hidden field to true.

Let’s do it!

And our modified _song_fields.html.erb partial:

And now, everything should work.

Bonus: Add New if Doesn’t Exist with Select2

This final example is a bit of a hack, and is merely used as an exercise.

Set up: We have Recipes which on turn have many Ingredients (M2M). In the recipes#newview, we’d like to choose multiple ingredients to associate with this recipe, creating new ones if they don’t exist. This assumes that ingredient names are unique.

This example depends on the Select2 library, so download it and and put all it’s files in your assets directory, paying attention that any images referenced in the CSS will be found.

In order to be able to use the Select2 createSearchChoice method (necessary to add new items) we’ll need to operate on a hidden field. This will break the form for users that have JS disabled.

Because the hidden field will submit a string of separated ids, plus new names, and the ingredient_ids  ingredient_ids= methods expect an array, we will overwrite these methods in our model:

Now let’s add the hidden field to our form:

And, finally, the CoffeeScript:

This will populate the Select2 field with data that was embedded into the hidden field’s ingredients data attribute. It will also use the name property, as the text property that Select2 requires.

See more details on the Select2 Docs page.

That about wraps it up. Let me know if I missed any useful pattern!

1239: The social media reaction to this asteroid announcement has been sharply negative. Care to respond?

Previous Post Tracking the Results of a Rails-Powered Survey Next Post