Cocoon: handling nested forms

cocoon is a Rails3 gem to allow easier handling of nested forms. Nested forms are forms that handle nested models and attributes dynamically in one form. Some standard examples: a project with its tasks, an invoice with its ordered items. It is formbuilder-agnostic, so it works with standard Rails, or formtastic or simple_form.

Prerequisites

This gem uses jQuery, it is most useful to use this gem in a rails3 project where you are already using jQuery. Furthermore i would advice you to use either formtastic or simple_form. I have a sample project where I demonstrate the use of cocoon with formtastic.

Installation

Inside your Gemfile add the following: [ruby] gem "cocoon" [/ruby] Run the installation task: [ruby] rails g cocoon:install [/ruby] This will install the needed javascript file. Inside your application.html.haml you will need to add below the default javascripts: [ruby] = javascript_include_tag :cocoon [/ruby] or using erb, you write [ruby] <%= javascript_include_tag :cocoon %> [/ruby] That is all you need to do to start using it!

Usage

Suppose you have a model Project: [ruby] rails g scaffold Project name:string description:string [/ruby] and a project has many tasks: [ruby] rails g model Task description:string done:boolean project_id:integer [/ruby] Edit the models to code the relation: [ruby] class Project < ActiveRecord::Base has_many :tasks accepts_nested_attributes_for :tasks end class Task < ActiveRecord::Base belongs_to :project end [/ruby] What we want to achieve is to get a form where we can add and remove the tasks dynamically. What we need for this, is that the fields for a new/existing task are defined in a partial view called _task_fields.html. Note: the name of the partial is really important, as this plugin will look for the partial named as the singular of the relation + _fields. We will show the sample usage with the different possible form-builders.

Using formtastic

Inside our projects/_form partial we then write: [ruby] - f.inputs do = f.input :name = f.input :description %h3 Tasks #tasks = f.semantic_fields_for :tasks do |task| = render 'task_fields', :f => task .links = link_to_add_association 'add task', f, :tasks -f.buttons do = f.submit 'Save' [/ruby] and inside the _task_fields partial we write: [ruby] .nested-fields = f.inputs do = f.input :description = f.input :done, :as => :boolean = link_to_remove_association "remove task", f [/ruby] That is all there is to it! There is an example project on github implementing it called cocoon-formtastic-demo.

Using simple_form

This is almost identical to formtastic, instead of writing semantic_fields_for you write simple_fields_for. There is an example project on github implementing it called cocoon_simple_form_demo.

Using standard rails forms

I will provide a full example (and a sample project) later.

How it works

I define two helper functions:

link_to_add_association

This function will add a link to your markup that will, when clicked, dynamically add a new partial form for the given association. This should be placed below the semantic_fields_for. It takes three parameters:

  • name: the text to show in the link
  • f: referring to the containing formtastic form-object
  • association: the name of the association (plural) of which a new instance needs to be added (symbol or string).

link_to_remove_association

This function will add a link to your markup that will, when clicked, dynamically remove the surrounding partial form. This should be placed inside the partial _#{association-object-singular}_fields.

Partial

!!!Important The partial should be named _#{association-object_singular}_fields, and should start with a div of class nested-fields. There is no limit to the amount of nesting, though.

To conclude

I hope it can be of use. Let me know what you think.


Comments
Maxi 2011-03-11 13:03:18 UTC

Excelent work!! very useful gem. Thanks for share!!

Bharat Ruparel 2011-08-21 03:00:45 UTC

Hello Nathan, I have opened up an issue for two level's nested form not working with my code. Can you please give me a hand with it? Thanks. Bharat

Chris Whitman 2011-09-19 09:33:12 UTC

Great work on this gem - we've really been enjoying it so far. Could I ask how I might go about displaying one of the embedded forms by default, rather than having to actually click to add one? Also, how about requiring that at least one record be submitted to the embedded model, any idea on the general direction I'd need to take would be great!

nathanvda 2011-09-19 11:33:11 UTC

You could do something like in your <code>new</code> action: <pre> @project = Project.new @project.tasks.build </pre> just taking the standard example of projects and tasks. And to make sure you need at least one record, you will need to write a validation in the parent model. Hope this helps.

Chris Whitman 2011-11-30 02:57:02 UTC

Thanks for the response, it was helpful. I am revisiting this functionality in a new project and am trying to figure out how to add more than one field by default. Been doing some digging and trial/error but without luck. Any idea on a starting point for more than one nested field by default? I actually ran into this same issue in the last project, but ended up working around it. Thanks again!

Chris Whitman 2011-11-30 03:08:45 UTC

Scratch that, there's a demonstration of how to do this in http://railscasts.com/episodes/196-nested-model-form-part-1?autoplay=true

nathanvda 2011-11-30 07:12:09 UTC

Maybe you should out my follow-up article on nested-forms: http://www.dixis.com/?p=561 If you have more questions, shoot. Hope this helps.

Max 2012-06-21 13:12:34 UTC

Any updates on the example for using standard rails forms? I've tried to look for something but with no luck. Cheers!

Add comment

Recent comments

Tags

ruby on rails 34 ruby 26 rails3 17 rails 15 oracle 11 rspec 9 rspec2 7 jquery 7 ubuntu 5 javascript 5 windows 5 activerecord 3 refactoring 3 geoserver 3 gis 3 arrrrcamp 3 actionmailer 2 oracle spatial 2 tdd 2 postgis 2 routing 2 rvm 2 mongoid 2 csharp 2 thin 2 win32 2 gem 2 rails4 2 git 2 service 2 haml 2 cucumber 2 view testing 2 i18n 1 displaysleep 1 spatial 1 gemsets 1 wubi 1 oracle_enhanced_adapter 1 migrations 1 watchr 1 ci 1 plugins 1 coderetreat 1 ie8 1 ssl 1 oci 1 nested model form 1 wcf 1 11.04 1 jsonp 1 ruby-oci8 1 teamcity 1 engines 1 pgadmin 1 soap 1 content_for 1 word automation 1 plugin 1 capybara 1 xml 1 bootstrap 1 migrate to rails3 1 mvc 1 unity 1 rendering 1 word2007 1 x64 1 limited stock 1 fast tests 1 pl/sql 1 delayed_job 1 pdf 1 test coverage 1 optimization 1 processing 1 borland 1 method_missing 1 cross-browser 1 devise 1 schema_plus 1 mongo 1 mongrel 1 dual boot 1 usability 1 mongrel_service 1 dba 1 mission statement 1 model 1 metadata 1 rcov 1 exceptions 1 image_tag 1 attachments 1 bde 1 css 1 yield 1 ajax 1 generative art 1 rails-assets 1 coordinate systems 1 submodules 1 netzke 1 ora-01031 1 authlogic 1 postgresql 1 shopping cart 1 agile 1 fast_tagger 1 subjective 1 wice_grid 1 generators 1 nvidia 1 mongodb 1 etsyhacks 1 staleobjecterror 1 session 1 jeweler 1 wordpress hacked 1 jasmine 1 heroku 1 rjs 1 life 1 unobtrusive-javascript 1 render_anywhere 1 html5 1 rails31 1 json 1 cocoon 1 mingw32 1 observe_field 1 osx 1 actionwebservice 1 testing 1 debugging 1 strings 1