ryanb / nested_form

Rails plugin to conveniently handle multiple models in a single form.
MIT License
1.79k stars 505 forks source link

Duplicate fields on 'render' for already saved fields (ex: form validation failed) #346

Open Startouf opened 9 years ago

Startouf commented 9 years ago

My nested_form works fine, EXCEPT when in my controller, I render (not redirect_to) a page that contains a nested_form, with some nested_fields already saved in the database. It duplicates (only in the HTML) every nested field that has already been saved. So i end up with two hidden fields with the same ID

Model


class Etude

  has_many :echanges, dependent: :destroy
  accepts_nested_attributes_for :echanges, :allow_destroy => true

In my controller

def update
    if @project.update_attributes(etude_params)
      flash[:notice] = "Updated !"
            redirect_to @etude
    else
      flash.now[:alert] = "Ouch, duplicated nested fields" 
      render 'show'
    end
  end

def etude_params
  params.require(:etude).permit( ...
    echanges_attributes: [
            :id,
            ...
            :_destroy],

Every nested_fields in my form are more or less like this one (in this case echange(s) is the name of the association)

<%= nested_form for @etude do |f| %>

<%= render 'somepartial', f: f %>

<!-- _somepartial.html.erb -->
<ul id="echanges-list">
<%= f.fields_for :echanges, :wrapper => false do |echange| %>
<li class="fields ...">
<%= echange.link_to_remove "<i class='glyphicon glyphicon-trash'></i> Supprimer".html_safe, class:'btn btn-xs btn-danger', data: {association: 'echange'} %>
...
</li>
<% end %>
</ul>

<%= f.link_to_add(:echanges, :data => { :target => "#echanges-list" }, class: "btn btn-success") do %>
    <i class="glyphicon glyphicon-plus"></i> Ajouter un échange
<% end %>

Before I submit, I have a single field with the invisible id input (that is already saved)

<input id="etude_echanges_attributes_0_id" type="hidden" name="etude[echanges_attributes][0][id]" value="54b44f955374611148080000">

Now When I resubmit this single field, this time with validation errors, I end up with two

<input id="etude_echanges_attributes_0_id" type="hidden" name="etude[echanges_attributes][0][id]" value="54b44f955374611148080000">
<input id="etude_echanges_attributes_1_id" type="hidden" name="etude[echanges_attributes][1][id]" value="54b44f955374611148080000">

...And only validation error messages are shown on the second one

If I had 3 already saved nestd_field before rendernig validation errors, I would've ended up with 6 fields (3 duplicates of the original 3)

Did I miss something ?

Rails 4.2 Mongoid 4 Nested form 0.3.2

Startouf commented 9 years ago

Woh this is so weird. The objects in memory are completely messed up.

Let's say I have a project with a task saved in the database. Now if I render the task invalid via a nested_form and fields_for and I PATCH the results,

In the controller,

Startouf commented 9 years ago

Found the source of my problem. Eager Loading

In my controller, I was actually eager-loading before trying to update the values

@project = Project.include(:steps)
# Load my project and embedded steps
...
@project.update_attributes(project_params_with_embedded_steps)
# Will completely mess things up because @project varibale already contains eager-loaded steps!
santiagosan93 commented 4 years ago

Hi there guys, I'm having exactly the same problem Everything works fine, but when I render the :new on failed save, the fields get duplicated.

My controller looks like this tho.. And it seems that somewhere im double building my parent instance but I can't see where.

Any hints?

My controller looks like this

class CocktailsController < ApplicationController
  before_action :find_cocktail, only: [:show, :edit, :destroy]
  def index
    @cocktails = Cocktail.all
  end

  def show
    @dose = Dose.new
  end

  def new
    @cocktail = Cocktail.new
    @ingredients = Ingredient.all.map {|ing| [ing.name, ing.id]}
    @cocktail.doses.build
  end

  def create
    @cocktail = Cocktail.create(cocktail_params)
    if @cocktail.save
      redirect_to @cocktail
    else
      @ingredients = Ingredient.all.map {|ing| [ing.name, ing.id]}
      render :new
    end
  end

  def edit; end

  def update
    if @cocktail.update(cocktail_params)
      redirect_to @cocktail
    else
      render :edit
    end
  end

  def destroy
    @cocktail.delete
    redirect_to cocktails_path
  end

  private

  def find_cocktail
    @cocktail = Cocktail.find(params[:id])
  end

  def cocktail_params
    params.require(:cocktail).permit(:name, doses_attributes:[:description, :ingredient_id])
  end
end
santiagosan93 commented 4 years ago

And here is the model

class Cocktail < ApplicationRecord
  validates :name, presence: true, uniqueness: true
  has_many :doses, dependent: :destroy
  has_many :ingredients, through: :doses
  accepts_nested_attributes_for :doses
end
santiagosan93 commented 4 years ago

And this is the view

h2. Make your cocktail!
= simple_form_for(@cocktail) do |f|
  = f.input :name
  h3. Give your recipe
  .doses.form-group
    = f.simple_fields_for :doses do |t|
      = t.input :description
      = t.select :ingredient_id, @ingredients
    = f.simple_fields_for :doses do |t|
      = t.input :description
      = t.select :ingredient_id, @ingredients
    br
    br
    br
    .btn.btn-primary id="add-dose" Add another one!
  = f.submit 'Create!'
end
syrashid commented 3 years ago

You can make this work by only having one simple_fields_for in your view, and building your doses twice. So in your new action something like 2.times { cocktail.doses.build }

This SO answer helped me figure this out as I was having the same issue https://stackoverflow.com/questions/12494008/how-to-use-simple-fields-for-to-create-many-objects-of-the-same-nested-type

The idea from what I understand is how form_builder goes about generating fields for associated models. In this case, when you initially build you only associated one dose to your model, and the form builder generated two inputs for the same model. When a validation failed, there were now two associated models (doses) from the two separate input fields on your cocktail model. Then when it went to render the form again, the first time it encounters simple fields for it will generate fields for BOTH doses associated to your model, and when it got to the second simple fields for, it did it again, totaling four doses on the page, and each time you'd try and submit it would continue this pattern of exponentially increasing!