Nested Forms from Scratch

#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


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)
    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 22 days 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 said 22 days 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 21 days 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

Login to Comment