Skip to content
This repository has been archived by the owner on Dec 12, 2021. It is now read-only.

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

Open
Startouf opened this issue Jan 12, 2015 · 6 comments

Comments

@Startouf
Copy link

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
Copy link
Author

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,

  • BEFORE update_attributes,
    @project.tasks #=> [#<Phase _id: 1...] the object is present once, ok
  • AFTER update_attributes,
    @project.tasks #=> [#<Phase _id: 1..., #<Phase _id: 1...], the object is present twice. Yet @project.tasks.length # 1, it reports an array size of only one object.

@Startouf
Copy link
Author

Startouf commented May 5, 2015

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
Copy link

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
Copy link

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
Copy link

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
Copy link

syrashid commented Jan 22, 2021

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!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants