Check out my consultancy offering at RailsReviews.com!

NestedFormReflex

A reflex to construct a form that wraps a has_many relationship with nested attributes on the fly.

How?

  • New children are instantiated by calling .build on the has_many association
  • fields_for expands to all children if a child_attributes= setter is present (which is the case if accepts_nested_attributes_for is set) - see API docs

Caveat

Clean up your session (or other persistent store) after form submission.

app/reflexes/nested_form_reflex.rb

class NestedFormReflex < ApplicationReflex
  before_reflex do
    session[:forms] ||= {}
    session[:forms]["Book"] ||= Book.new
  end
  
  def add    
    session[:forms]["Book"].chapters.build
  end
end

app/controllers/books_controller.rb

class BooksController < ApplicationController
  def new
    @book = session.fetch(:forms, {}).fetch("Book", Book.new)
  end
end

app/models/book.rb

class Book < ApplicationRecord
  has_many :chapters
  
  accepts_nested_attributes_for :chapters
end

app/models/chapter.rb

class Chapter < ApplicationRecord
end

app/views/books/new.htm.erb

<%= form_for(@book, url: "#") do |form| %>
  <div class="mb-3 row">
    <%= form.label :title, class: "col-sm-2 col-form-label" %>
    <div class="col-sm-10">
      <%= form.text_field :title, class: "form-control", placeholder: "Enter a book title" %>
    </div>
  </div>
    
  <hr/>
    
  <%= form.fields_for :chapters do |chapter_fields| %>
    <h4>Chapter <%= chapter_fields.index + 1 %></h4>
    <div class="mb-3 row">
      <%= chapter_fields.label :title, class: "col-sm-2 col-form-label" %>
      <div class="col-sm-10">
        <%= chapter_fields.text_field :title, class: "form-control", placeholder: "Enter a chapter title", data: {reflex_permanent: true} %>
      </div>
    </div>                
  <% end %>
        
  <button type="button" class="btn btn-outline-primary" data-reflex="click->NestedForm#add">Add Chapter</button>
<% end %>