#70 Nested Forms from Scratch


Learn how to handle multiple models in a single form with accepts_nested_attributes_for and learn how to add and remove nested records through JavaScript.
rails form javascript 9:48


Terminalrails g scaffold todo_list name
rails g model task todo_list:references name completed:boolean due:date
models/todo_list.rbclass TodoList < ApplicationRecord
  has_many :tasks, dependent: :destroy
  accepts_nested_attributes_for :tasks, allow_destroy: true, reject_if: proc { |att| att['name'].blank? }
  # accepts_nested_attributes_for :tasks, allow_destroy: true, reject_if: :all_blank
models/task.rbclass Task < ApplicationRecord
  belongs_to :todo_list, optional: true
helpers/application_helper.rbmodule ApplicationHelper
  def link_to_add_row(name, f, association, **args)
    new_object = f.object.send(association).klass.new
    id = new_object.object_id
    fields = f.simple_fields_for(association, new_object, child_index: id) do |builder|
      render(association.to_s.singularize, f: builder)
    link_to(name, '#', class: "add_fields " + args[:class], data: {id: id, fields: fields.gsub("\n", "")})
application.js$(document).on('turbolinks:load', function() {

  $('form').on('click', '.remove_record', function(event) {
    return event.preventDefault();

  $('form').on('click', '.add_fields', function(event) {
    var regexp, time;
    time = new Date().getTime();
    regexp = new RegExp($(this).data('id'), 'g');
    $('.fields').append($(this).data('fields').replace(regexp, time));
    return event.preventDefault();
<%= simple_form_for(@todo_list) do |f| %> <%= f.error_notification %>   <div class="form-inputs">     <%= f.input :name %>   </div>   <table class='table'>     <thead>       <tr>         <th></th>         <th>Task Name</th>         <th>Completed</th>         <th>Due</th>       </tr>     </thead>     <tbody class='fields'>       <%= f.simple_fields_for :tasks do |builder| %>         <%= render 'task', f: builder %>       <% end %>     </tbody>   </table>   <div class="form-actions">     <%= f.button :submit %>     <%= link_to_add_row('Add Task', f, :tasks, class: 'btn btn-primary') %>   </div> <% end %>

<tr>   <td>     <%= f.input_field :_destroy, as: :hidden %>     <%= link_to 'Delete', '#', class: 'remove_record' %>   </td>   <td><%= f.input :name, label: false %></td>   <td><%= f.input :completed, label: false %></td>   <td><%= f.input :due, label: false, as: :string %></td> </tr>

Simon Kiteley said 11 months ago:

I'm really torn to whether this approach gives a good user experience. Its so easy to hit back or refresh the browser and loose what you have entered. I tend to use ajax and save as I go along. Wonder what other think?

Is a great tutorial though :)

kobaltz PRO said 11 months ago:

From a UX perspective, it is consistent with how, by default, other forms work. You're able to refresh the browser or hit the back button to navigate away from normal forms without warning and losing any field changes.

My approach is usually to provide some form validations via javascript. So if the user adds a record or if they modify existing fields on the page, set a variable in JS. If the user navigates away from the page before saving, then show them an alert to prevent leaving the page on accident.

Mike Belyakov said 11 months ago:

Good video!

The line


will work good only of the form have 1 hidden field. So, better to fetch _delete input with other selector

aramsm said 10 months ago:

Hello guys,

Im trying to build a dinamyc form for the same ToDo app, with the difference that it has Devise making user auth system. The thing is I cant use coffe.js, cocoon, SimpleForm or any other gem (except for jQuery). So my form is not working and I dont know what is causing it (clicking on add does nothing).

My project is: https://github.com/aramsm/miniapp-autoseg

Login to Comment