Rails Nested Form with Ajax
I have an interesting problem involving an object creation step in my Rails 3 music application.
I have two models (not the actual models; but for simplicity): Playlist Song
Playlist has_many Songs; Song belongs_to Playlist.
Each Playlist object must have an exact number of Songs. Each Song must belong to a Playlist.
Given this, I want to create a process for creating a Playlist that also involves creating all of the necessary Songs at the same time.
Another thing is that to get Song data, the user enters a query (which I will not be saving in the Song model), which I then gather data from an API with. This i开发者_StackOverflows the data that should be used to make the Song object. Therefore, I can't (don't think?) use a traditional form_for.
Instead, I'm using a remote form_tag. This form asks for a query and then uses an Ajax request to fetch the data, which is put into a temporary Song object and then displayed inline on the Playlist creation page using a Song view. This form is reused for all the necessary Song objects for the Playlist.
So, the idea is that when the user has entered the required number of queries (i.e. added the required number of songs to the playlist), they are presented with a new button that enables them to submit the playlist info and continue in the process. The Playlist will then be created with all the Song objects that were created via Ajax as children.
In reality, I can't figure out a way for this to work in an elegant way. Although I create the Song objects via Ajax, they aren't saved anywhere and they aren't aware of which Playlist they're supposed to be added into (because the Playlist object doesn't exist in the database yet either.) Therefore, when I go to the next step, I'm left without all the Song data. I looked into using nested forms with accepts_nested_attributes_for, but I can't figure out a way to use it with my setup (a non-model-based form using Ajax.)
So, I'm stuck. If anyone can help, it would be very much appreciated.
I've run into problems similar to this before. For the record, I have never found the remote tags useful and never end up using them for something this complicated. You're right - nested forms with accepts_nested_attributes_for were made for this kind of thing, and there's certainly a way to get it work.
What you need to do is, after your Ajax query, generate your nested Song fields on the fly with JavaScript. For example, let's say you have:
<form...>
<input type="text" name="playlist[name]" />
<div class="songs">
</div>
</form>
You will want your Ajax callback to do something like (I'm assuming jQuery here):
<script type="text/javascript">
$(function() {
var songs = $('form .songs') // Store a reference to the DOM song container
// Insert Ajaxy goodness for song lookup here.
// When it's finished, it should call add_song and pass in any data it needs
})
function add_song(song_name, artist_name, artist_id) {
var num_songs = $(songs).size()
$(songs).append('<p>'+song_name+' by '+artist_name+
'<input type="hidden" name="playlist[songs_attributes]['+num_songs+'][name]" value="'+song_name+'" />' +
'<input type="hidden" name="playlist[songs_attributes]['+num_songs+'][artist_id]" value="'+artist_id+'" /></p>')
}
</script>
There are few things going on there:
- The field name "playlist[songs_attributes]..." is what tells Rails that that field belongs to a nested attribute
- The num_songs index after "playlist[songs_attributes]..." is extremely important. Rather, it's extremely important that it be unique for every song. Think of it like an array index. So basing it off of the size of the every-increasing list of songs is perfect. (This is the trickiest and least-obvious part of dynamically creating nested form fields, IMO.)
My code probably isn't perfect, and I'm sure I didn't get your field names right anyway. But it should be a good start. Also, looking at the submitted form data in your app's development log is quite helpful for debugging this stuff.
Here are a few things to consider:
- What about your users who don't have JavaScript available? (I've been told those people exist.)
- Why do you want to couple the playlist creation process to the song creation process? What's wrong with creating a playlist as its own process? This way a playlist can be saved. Then you can create songs (since it requires gathering data from a separate API), so the songs can be saved. Then you can associate songs to a playlist. Consider saving the user's input at each step, so that if s/he has to stop in the middle, s/he won't lose all the existing data.
- If you implement a not-Ajax interface first, then it may be easier for you to pull together the pieces to create your final Ajax interface. This way you have your all your necessary resources in place. The Ajax just becomes a nice glue so that there aren't so many page refreshes.
精彩评论