This is 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
|
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.
1 2 |
|
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_select
helper.
1 2 |
|
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).
1 2 3 4 5 |
|
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_for
form helper, which we’ll add in a partial.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
1 2 3 4 5 6 |
|
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: true
which 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:
1
|
|
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 << Song.new}
, 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:
1
|
|
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:
1 2 3 4 5 6 7 8 |
|
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!
1 2 3 4 5 6 7 8 9 10 11 |
|
And our modified _song_fields.html.erb
partial:
1 2 3 4 5 6 |
|
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#new
view, 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:
1 2 3 4 5 6 7 8 9 |
|
Now let’s add the hidden field to our form:
1 2 |
|
And, finally, the CoffeeScript:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
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!
All Revisions to this document are visible here