Search code examples
ruby-on-railsnested-formssimple-form-for

nested_form shows extra fields after error messages


I'm using simple_nested_form_for to build a form with nested fields. Fields are added dynamically

When rendering the form with errors (via create) the nested fields go wrong.

The same nested fields are shown multiple times and with the wrong index values in the name elements.

For example the FormBuilder index in the nested field is initially a random number such as 1454793731550. After re-rendering they simply become normal increments 0-n.

Why is the FormBuilder index initially a random number?

Any suggestion what could be going on here?

  def new
    @transaction = current_company.transactions.build
    @transaction.subtransactions.build
  end

  def create
    @transaction = current_company.transactions.new(transaction_params)

    if @transaction.save
      redirect_to dashboard_url
    else
      @transaction.subtransactions.build
       render :action => 'new'
    end

Solution

  • The index is the child_index of the nested fields. This is simply a way for Rails to individually identify the various field names of the HTML form elements:

    <%= f.fields_for :association do |a| %>
      <%= a.text_field :x %> #-> "child_index" id will either be sequential (0,1,2)
    <% end %>
    

    The child_index doesn't matter. As long as it's unique, it should be passed to the controller as follows:

    params: {
      association_attributes: {
        0: { x: "y", z: "0" },
        1: { ... }
      }
    }
    

    A trick often used is to set the child_index to Time.now.to_i, which allows you to add new fields out of scope:

    <%= f.fields_for :association, child_index: Time.now.to_i do |a| %>
    

    In regards your new action with the likely issue is that your subtransactions object is being built each time (irrespective of whether the instance is populated with previous data).

    We've had this issue before, and I believe that we solved it with a conditional:

    def new
      @transaction = current_company.transactions.build
      @transaction.subtransactions.build unless @transaction.errors.any?
    

    This should maintain the object's integrity through the submission process. IE if an error occurs, I believe Rails stores the associated object in memory (like it does with the parent).