#197
Jan 18, 2010

Nested Model Form Part 2

Add and remove nested model fields dynamically through JavaScript using either Prototype or jQuery.
Tags: forms views
Download (20.2 MB, 12:40)
alternative download for iPod & Apple TV (15.9 MB, 12:40)

Resources

<!-- _form.html.erb -->
<p><%= link_to_add_fields "Add Question", f, :questions %></p>

<!-- _question_fields.html.erb -->
<%= link_to_remove_fields "remove", f %>
...
<p><%= link_to_add_fields "Add Answer", f, :answers %></p>

<!-- _answer_fields.html.erb -->
<%= link_to_remove_fields "remove", f %>
// application.js
function remove_fields(link) {
  $(link).previous("input[type=hidden]").value = "1";
  $(link).up(".fields").hide();
}

function add_fields(link, association, content) {
  var new_id = new Date().getTime();
  var regexp = new RegExp("new_" + association, "g")
  $(link).up().insert({
    before: content.replace(regexp, new_id)
  });
}

// application_jquery.js
function remove_fields(link) {
  $(link).prev("input[type=hidden]").val("1");
  $(link).closest(".fields").hide();
}

function add_fields(link, association, content) {
  var new_id = new Date().getTime();
  var regexp = new RegExp("new_" + association, "g")
  $(link).parent().before(content.replace(regexp, new_id));
}
# helpers/application_helper.rb
module ApplicationHelper
  def link_to_remove_fields(name, f)
    f.hidden_field(:_destroy) + link_to_function(name, "remove_fields(this)")
  end
  
  def link_to_add_fields(name, f, association)
    new_object = f.object.class.reflect_on_association(association).klass.new
    fields = f.fields_for(association, new_object, :child_index => "new_#{association}") do |builder|
      render(association.to_s.singularize + "_fields", :f => builder)
    end
    link_to_function(name, h("add_fields(this, \"#{association}\", \"#{escape_javascript(fields)}\")"))
  end
end

RSS Feed for Episode Comments 68 comments

1. mikhailov Jan 18, 2010 at 00:09

Realy nice!
Thanks Ryan


2. Steve Jan 18, 2010 at 01:38

Another helpful episode!


3. Jamie, Baymard Institute Jan 18, 2010 at 01:57

Great alternative to some ajaxed up solution.


4. Chris Jan 18, 2010 at 02:29

Another excellent tutorial. Looking forward to trying the nested_form plugin that you're working on.


5. Kikan Jan 18, 2010 at 02:49

Nice !

I searched for two months why I couldn't nest an add field in another without having a double javascript escaping, resulting to a messy display in the view.

The "simple" h() you use is the solution and I could get rid of my bug now !

An I like the use of "class" and the separation of Javascript / Ruby, and your convention of naming fields with _fields. It's very clean : I will cleanup my code doing this now.

Thanks a lot.


6. Aditya Sanghi Jan 18, 2010 at 03:31

Hi Ryan,

Looking forward to the nested_form plugin. We already love the Formtastic forms you introduced a few weeks back. I hope we can continue to use that when we try out the nested_form plugin.

Cheers,
Aditya


7. RamOnRails Jan 18, 2010 at 03:53

Ryan! you are very good rubyist!


8. Marcin Jan 18, 2010 at 04:04

Awesome!


9. Nelson Rojas Jan 18, 2010 at 04:31

Great post! Vitamins for start this week :)


10. Samuel Tonini Jan 18, 2010 at 05:13

Just a great screencast as always. :) thanks Ryan.


11. BurmajaM Jan 18, 2010 at 05:55

Nice and useful cast as always. Tnx Ryan.

For those of you that are searching for unobtrusive jQuery version of this functionality, take a look at last answer at http://stackoverflow.com/questions/1704142/unobtrusive-dynamic-form-fields-in-rails-with-jquery.

Hope this helps


12. David Souza Jan 18, 2010 at 06:20

One thing I would say about this is that it is probably better and more intuitive keeping the "remove" part of things separate from the concept of nested forms, don't use "_destroy" fields.

Associations can always be destroyed with standard destroy paths using the Restful API. If you want spiffy dynamic forms, then use AJAX calls to actually destroy the model when you click the link rather than just hiding it and hoping the user knows to update the form.

Another big plus is that the system would still work without javascript which is not true in this case.

Just my two cents.


13. elioncho Jan 18, 2010 at 07:47

class Message < ActiveRecord::Base
  belongs_to :author
  accepts_nested_attributes_for :author
end

I'm using the above and it's working perfectly. The message gets saved with an author_id attribute which references the author (tha author record gets saved too).

The problem:

Sometimes I don't want to save the author because the author already exists (I just want to reference it). So, I just want the message to get saved with the author_id reference. How can I override or modify the normal saving behaviour when using the accepts_nested_attributes_for method?


14. Rick Jan 18, 2010 at 08:53

is there going to be screencast on nested forms that covers validation?


15. Ivan Jan 18, 2010 at 09:14

What if I need to have a 'observe field' in the child class?

For instance, we have a named list with many books (one list has many books) and when one types the ISBN to add it to the list (the number of existent books is too big for a selection field) the system must display the name and price of the book.


16. Javi Jan 18, 2010 at 12:00

Hi, Ryan.

Correct if I'm wrong, but, shouldn't the code in the ApplicationHelper be on this page?

Thanks.


17. Tony Jan 18, 2010 at 14:19

Is this Rails3 compatible?


18. Reza Jan 18, 2010 at 15:40

nitpick, Ryan. The size of the movie files is swapped. Af tirst I'm getting curious, thinking "the iPod file is bigger than the .MOV ?", and proved otherwise. :) Downloading now. Thank you for good screencast, as always.


19. Peter D Jan 18, 2010 at 15:57

Excellent! Like Kikan I was stuck on the escaping problem.. h() never occurred to me as a solution. Thanks once again for a great screencast! :D


20. Tom Jan 18, 2010 at 22:40

Fantastic screencast!

Anyone notice a bug with polymorphic nested tables?

1. create nested attributes on polymorphic model.
2. Add new item, with new sub item (polymorphic) but cause an error on the sub item (eg leave a validated field blank/wrong).
3. Returns to page with errors, anytime trying to submit this fails because of the missing polymorphic values being set.

At the moment, not sure how to fix it, but only enabling the nested model forms on edit screens of the parent item.

Thanks for the screencast!


21. sy0-201 Jan 18, 2010 at 23:19

It's just a part of


22. Artur Jan 19, 2010 at 04:47

really nice screencast! just implementing this stuff in my new app


23. Joost Saanen Jan 19, 2010 at 05:13

Another great cast. Thx Ryan! Gonna use it in my applications soon


24. mcansky Jan 19, 2010 at 05:44

really nice screencast arrives just perfectly for one project

but I'm getting a strange error with an apparently non existent association in what would be the same level as :answers, is there any specific rails version involved ? I'm using 2.3.5


25. DGM Jan 19, 2010 at 10:42

@David Souza - There are a couple of ways to think about that issue - some people may not expect any changes until they hit submit, as that's how web pages normally work, at least up until recently.

I like the idea of instant changes too, but perhaps more use feedback should be included, like flashing a message saying it has been deleted, when doing a ajax request...


26. -jul- Jan 19, 2010 at 23:17

I have done it with Haml, Formtastic and jQuery too:

http://blog.js.hu/2009/06/15/add-form-fragments/

Maybe it's a bit old and doesn't have the latest changes but it's still working.


27. Matthew Jan 19, 2010 at 23:56

Very interesting stuff Ryan,

What are your thoughts on using this with a has_and_belongs_to_many relationship? It appears that it aught to work, yet there is no documentation for it. Seems rails doesn't support using this in that way?


28. Patrick Klingemann Jan 20, 2010 at 12:34

@elioncho

I had the same problem. I finally decided to monkey patch some of the nested attributes functionality to add an option :associate_existing to accepts_nested_attributes_for which takes a proc, the result of which is an instance of the associated model. This works pretty well, but it isn't tested. Good luck. Just plop this file into config/initializers

http://gist.github.com/282223


29. Kent Jan 21, 2010 at 01:10

Excellent as always! Keep up the good work!


30. Denis Jan 21, 2010 at 13:25

hey,

Thanks you so much for all your work Ryan!!

for the rails 2.3.4 use _delete instead of _destroy.

this was fix in 2.3.5 see https://rails.lighthouseapp.com/projects/8994/tickets/2889-rename-_delete-to-_destroy-in-nested-attributes


31. mrk Jan 21, 2010 at 13:58

anyone want to take a stab at create a questionnaire from the survey?


32. mrk Jan 21, 2010 at 14:05

Meaning, create a simple model and use formtastic to create all of the questions (from the created survey)... I am stuck on a clean way to do this.


33. Squiddhartha Jan 21, 2010 at 17:23

This stuff looks extremely handy, and I sure wish it had been available three years ago! However, I have to ask... is there any way of getting the same effects without JavaScript?

Yes, I know, we live in an AJAX world now and everybody should just use JavaScript, but it still seems somehow "purer" to me to not *require* it...


34. Dave Jan 22, 2010 at 11:18

I'm curious about how to use Ryan's Populator plug-in with this... and polymorphic associations. I have something sorta working but it seems hacky and brittle.

@Tom, check out Ryan's noted update on Episode 75 for validation of nested attributes.


35. Ryan Bates Jan 23, 2010 at 16:51

@Reza, @Javi, fixed, thanks!

@Rick, validations is something I still need to investigate further, but if I find the need I will consider doing a third part of this series which covers validations. Thanks for the suggestion.

@Ivan, I don't think observe field will work right out of the box with this due to the complexity nesting brings. However you can probably take a look at the generated JavaScript and modify it to work for you.

@Tony, I haven't tested it with Rails 3 yet, but I would guess much of this is still the same.

@Tom, I have yet to investigate polymorphic associations with this, but I'll consider doing an episode on it, thanks for bringing it up.

@mcansky, it should work in Rails 2.3.5, that is the version I'm using. Try downloading the full source code for this episode and see if that works for you. Then compare it to your code to find the difference.

@Matthew, I haven't tried it with HABTM associations and I'm not certain if accepts_nested_attributes_for works with it. I just recommend trying and seeing for yourself.

@Squiddhartha, the previous episode shows as much as one could do without JavaScript. The only major thing missing is the dynamic adding of records which requires JavaScript in order to insert HTML dynamically.

If you want to support it without JavaScript you'll have to handle adding records in a separate step, likely after the parent model is created.

@Dave, I'm not certain what you are referring to with the Populator gem. It works at a lower level than forms so this doesn't really apply there. It also doesn't use ActiveRecord so it can't make use of accepts_nested_attributes_for.


36. Neil Jan 24, 2010 at 13:11

I am having a bit of trouble. I get an "unknown attribute:" error.
my new action creates one empty set of nested fields and this saves fine. when I edit the record and add a new set and save I get the error. Any Ideas? newby in trouble :( Also just for info my models are tax_code and tax_rate with _ .
Thanks in Advance


37. Manolo Blahnik shoes Jan 24, 2010 at 22:51

I'm curious about how to use Ryan's Populator plug-in with this... and polymorphic associations. I have something sorta working but it seems hacky and brittle.

@Tom, check out Ryan's noted update on Episode 75 for validation of nested attributes.


38. Erik Jan 25, 2010 at 14:46

Great episodes. I am quite new & have a small problem:
I used your nifty_scaffold which adds attr_accessible to the models which I think is then blocking access to the nested attributes.

I have found other web sites referencing this issue saying the solution is to add :modelname_attributes as a parameter to attr_accessible. I have changed modelname to the name of my model, but can't get this to work. I don't get an error, I just don't get my nested fields. Removing the attr_accessible call completely, it all works fine. Is the order of the code in the model important?

Any help appreciated.


39. Erik Jan 25, 2010 at 14:57

Fixed my problem:
I had to specify my nested model name as plural. The other website example I was reading was only a has_one example.

My nested model is called ChecklistItem

so in my top model I have:
attr_accessible :checklist_items_attributes
has_many :checklist_items
accepts_nested_attributes_for :checklist_items

I have left out my other attr_accessible params and other command options for simplicity.


40. Robert Jan 28, 2010 at 01:13

Nice, I was planning on doing something like this but I didnt really figure out how to do it. This solution looks very nice, I think I'll steal it :)


41. bart Jan 30, 2010 at 22:02

Great screencast as usual.

I spent the day trying to figure out how to use this with single table inheritance. I have the situation where I have a form with many different but quite similar entries. e.g. cars trucks bikes. The information I want to gather is just slightly different for each one.

I've been beating my head against it all day and I think it could d be done by changing the helper code to enstantiate the correct form builder and load the correct partial.

Has anyone done something similar and can give any pointers? It feels like the sort of problem that has a very succinct answer if one only knew what it was :).


42. cheap adidas shoes Jan 31, 2010 at 19:19

Adidas Shoes Online Shop-Hot Selling Adidas Shoes & Cheap Adidas Shoes


43. Steve Feb 02, 2010 at 13:38

What if you want a survey to always have at least 1 question field by default?

So when you create a survey, no questions are added.

But when you edit that survey, at least one question field is displayed. When you update the survey, the new question you entered is saved.

I can't currently get this to work. I think update_attributes won't save the new question, but I'm totally lost on how to get it regardless.


44. Ali Feb 03, 2010 at 09:47

I get the following error when I try to use add fields:
undefined method `klass' for nil:NilClass

what seems to be the problem?


45. Ali Feb 03, 2010 at 09:50

Sorry
My bad. Ignore the previous one.


46. Werner Feb 06, 2010 at 07:25

Works fine. My Problem: If I change from a textfield to a datetime select - which is what I need..on clicking the remove field it is disapearing from the view, but not deleting the date in the database.
Any idea? +thanks
Werner


47. paese Feb 07, 2010 at 06:19

The JS won't work with formtastic because of the structure. Unfortunately, I haven't found a working equivalent.

(speaking of prototype)


48. paese Feb 07, 2010 at 06:28

Never mind, got one. Maybe someone can need id..

http://gist.github.com/297483


49. paese Feb 07, 2010 at 07:16

me again ;)

if someone got the 'add' part working with formtastic, please let me know...


50. DerNalia Feb 08, 2010 at 10:57

I can remove / modify the question / answer stuff..
But when I add them, it will show the text boxes for the everything, but when I click submit, nothing is saved to the database. =/

I think it has something to do with :
   def link_to_add_fields(name, f, association)
  
   #create a new instance of the object, association
     new_object = f.object.class.reflect_on_association(association).klass.new
     print new_object
    
     fields = f.fields_for(association, new_object, :child_index => "new_#{association}") do |builder|
     #render the form partial
       render(association.to_s.singularize + "_fields", :f => builder)
       end
      
     link_to_function(name, h("add_fields(this, \"#{association}\", \"#{escape_javascript(fields)}\")"))
    
   end

there are no errors. It just doesn't commit.
I noticed that your code also has jquery 1.3.2, So I added it to my project in the same spot (don't know if the program adds it automatically though... i haven't added it.. i tried... and nothing happened)

any help you could provide would be awesome!


51. john mccaffrey Feb 08, 2010 at 17:08

Great screencast!
I was able to immediately apply it to a project I'm working on, and get a pretty complicated story done quickly.

(Unfortunately I didn't see some of the recent forks, and ended up doing the unobtrusive jquery stuff myself)

What is the best way to test the helper methods?
I would like to keep our code coverage up, and assert that the js escaping and new_record ids are being assigned, and I tend to test helper methods explicitly, but in this case I'm not sure how to test the 'link_to_add_fields' method, which calls out to the render method.

Has anyone else added tests for these methods? (hopefully I'm missing something obvious)
thanks!


52. Battur Feb 09, 2010 at 01:38

Thank you very much for your dedication to these wonderful screen casts. My little team has started developing apps(for the group companies we work for) with Ruby on Rails for a month, and the speed and the joy just went up by huge amount.

When we face a challenge, we prefer railscast than book, because it's well explained, and very practical. And it works!

We must say "Thank you, Ryan!".


53. Tim Chater Feb 11, 2010 at 08:56

Another great screencast!

I know that validations aren't dealt with here... as far as I can tell they _just work_, with one issue: if a nested record has been removed and the form submission fails validation, that record will be visible again (although it will still be deleted when the valid form is submitted).

I think this is simply because we need a javascript function to run when the body loads which checks for any hidden delete fields with a value of 1 and hides the associated row. Can anyone conjure up the appropriate javascript? Thanks.


54. Tim Chater Feb 11, 2010 at 09:08

I should add that this only happens when updating a record which already has nested child records.


55. Tim Chater Feb 17, 2010 at 08:59

I've worked out a solution to this now.

You just need to add a (conditional) style to the "fields" div to set "display: none" if f.object._destroy


56. Daniel Feb 18, 2010 at 18:41

FYI: Rails3 no longer has a link_to_function.

One option seems to be:
rails plugin install git://github.com/rails/prototype_legacy_helper.git

button_to_function is another option.


57. Chris Masters Feb 20, 2010 at 04:41

I was also wondering if anyone has used the nested_form plugin with formtastic successfully?

Great tutorial as always, many thanks!


58. grary stimon Feb 27, 2010 at 10:12

Thanks Ryan -- really useful.

How would the javascript function change in the case of several child model objects to be enumerated?

So, to extend on your example, what if Survey had models: photo_of_respondent, questions, survey_worker, etc.?

Thanks, Grar


59. darkfall gold Feb 28, 2010 at 18:16

useful,thank you.you guys are kindness.


60. KT Mar 01, 2010 at 10:08

Great cast, as always! I'm wondering if Ryan or anyone has built the user-end of the survey...take survey/show results, etc...?


61. coachbags Mar 03, 2010 at 00:31

Coach owns exquisite design.coach handbags are much hot among women. so try to buy one of coach bags.
   http://bagworlds.com/


62. Leo Mar 04, 2010 at 06:27

I get the following error when I try to use add fields:

undefined method `klass' for nil:NilClass

what seems to be the problem ?


63. thb Mar 05, 2010 at 13:51

Like said in comment #11 I had done quite the same before inspired by this stack overflow thread. Thanks Ryan for bringing this up with 2 levels of nesting. Here is a unobstrusive jQuery version of the screencast : http://github.com/thb/surveysays


64. Robert Head Mar 08, 2010 at 11:08

Thanks so much, Ryan. Excellent screencasts.

Gotta express my frustration, though. It's absurd that this is so complex. This is probably Rails' most egregious inadequacy.

Why am I spending all morning learning plumbing that should be trivial. I don't mean to be a schmo, but we could do this in one minute in WebObjects in 1997.


65. RubNub Mar 08, 2010 at 12:37

Tried it on Rails 3, there is no link_to_function in Rails 3, can anyone offer me a Rails 3 version of this solution?

Thanks in Advance


66. kamagra Mar 09, 2010 at 01:23

thanks for the share


67. m65 field jacket Mar 09, 2010 at 01:24

nice post


68. phlegx Mar 09, 2010 at 07:06

Hi Ryan!

>> NICE CAST! <<

But I have problems with validations. If I catch a form error, the error messages appears but all added questions (e.g. this code) disappear. How can I solve this problem?

Thx

Add your comment:

(SKIP THIS ONE)

(required)

(not shown)


(use pastie or gist for code)

sponsored by:
if you want to help:
required:
Get Quicktime Player
Give Back to Open Source