Handling Rails Nested Form Attributes with StimulusReflex

If you've ever dabbled in web development with Ruby on Rails, you've probably encountered the challenge of dynamically adding and removing child resources in a form. Combine Rails Nested Form Attributes with StimulusReflex — a dynamic duo ready to come to your rescue!

The Challenge: Dynamic Form Management

Imagine you're building a system where you need to dynamically add chapters to a book while creating it. Traditional Rails techniques can seem clunky. You'd need multiple round trips to the server just to adjust your form. But that's where StimulusReflex can be a savior.

MVC in Play: The Working Gears

Let's break down how our models, views, and controllers come together to achieve this:

Model: Our main model is Book, which can have many chapters. It's set up to accept nested attributes for those chapters.

class Book < ApplicationRecord
  has_many :chapters
  
  accepts_nested_attributes_for :chapters
end

We also have a simple Chapter model:

class Chapter < ApplicationRecord
end

Controller: The BooksController's new action fetches an instance of Book. If it finds one in the session, it'll use that; if not, it'll create a new one.

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

View: The form in our view first lets users add a title for the book. It then dynamically allows the addition of chapter titles. A button, when clicked, uses StimulusReflex to add a new chapter field.

<%= form_for(@book, url: "#") do |form| %>
  ...
  <%= form.fields_for :chapters do |chapter_fields| %>
    ...
  <% end %>
        
  <button type="button" data-reflex="click->NestedForm#add">Add Chapter</button>
<% end %>

StimulusReflex to the Rescue!

Our Rails nested form can dynamically grow thanks to StimulusReflex. Here's how:

By defining a reflex NestedFormReflex, we set the groundwork. Within this reflex, before any action is executed, we ensure that there's a form for Book in the session:

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

  def add    
    session[:forms]["Book"].chapters.build
  end
end

The add method then leverages the magic of the .build method. This method instantiates a new chapter for our book without saving it to the database. When our form is re-rendered, this new chapter appears as a new set of fields, ready for the user to fill out!

Note: 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 the API docs

A Little Caution: Cleaning Up

Now, before we wrap up, it's vital to note a crucial caveat. After your form submission, always ensure to clean up your session (or other persistent store). We've been stashing form data there, and it's a good practice to clean up after we're done, preventing any potential data mishaps down the road.

Wrapping Up

With the powerful combination of Rails Nested Form Attributes and StimulusReflex, managing dynamic forms becomes a walk in the park. No longer do you need to wrestle with clunky, multi-step processes to add or remove child resources. And while this is just the tip of the iceberg in what you can achieve, it undoubtedly demonstrates the strength and flexibility of the Rails ecosystem. Happy coding!

Supercharge Your Rails App

Upgrade Your Tech Stack for a Smoother Dev Experience and Increased Profits.

Order a Review