Best way to do a dynamic has_many nested form?

I’m suspecting maybe this can’t easily be done with turbo but… Say you have a parent model that has many children. I’m trying to make a form with an “add child” button to dynamically insert a nested form. My initial approach was a button that uses turbo streams to hit up a controller endpoint in the rails app which then appends a form partial to the existing form. This kinda works but… Rails fields_for requires access to the parent form object in order to properly set up the field names and indexes for multiple added children, and if I’m rendering the form via turbo and inserting it I will not have that.

Before I go down to many bad roads, is there a recommended way to do this? So far my thoughts are:

  1. Instead of inserting a nested form, just re-render the entire form with a new nested child section when you hit the add button (could get messy keeping any existing form data in place)
  2. Just use stimulus to insert the html dynamically into the form keeping track of index parameters as more children are added.
  3. Stick with the turbo stream approach to insert the nested form, but instead of using fields_for manually name the fields correctly using stimulus to keep track of the index every time the add child button is pressed.

Any suggestions on the best approach?

These articles could help:

Using Turbo: Nested Forms With Turbo (without dependencies) | Rails Designer
Using Stimulus: Building Nested Forms in Rails with Stimulus | Rails Designer

Interesting, that first one has some good ideas, it had not occurred to me to use a timestamp instead of an incrementing index for child elements to make the logic of adding multiple much simpler…

Approach 1.), to re-render the entire form with a new nested child section, should be possible with this technique: the “add child” is a button that submits the form with a special parameter; when that parameter is present, the controller populates a model instance with the incoming params, does not try to validate nor save it but instead calls build to add a new empty child item and re-renders the form.

# button inside the form:
<button name="refresh_form" value="add_child">Add child</button>

# controller:
def create
  @parent = Parent.new(parent_params)

  if params[:refresh_form] == "add_child"
    @parent.children.build
    render :new
  else
    # ... normal controller action
  end
end

def parent_params
  params.expect(parent: [..., children_attributes: [[ :id, ... ]]])
end 

Since the whole form is posted, already filled out values are not lost, including previously added child fields. I’ve tested this locally with a form that is wrapped in a turbo frame. Theoretically this could even work without turbo, but would cause a bumpy full page reload on “add child”.

In the past GitHub - nathanvda/cocoon: Dynamic nested forms using jQuery made easy; works with formtastic, simple_form or default forms was very popular but Building Nested Forms in Rails with Stimulus | Rails Designer feels much more modern and requires no dependencies.

1 Like