Question

I am trying to update a nested form. The client table is updated successfully but the location table isn't update. Instead of its creating a new location. Do you guys have any solution ? I've already spend one day on it.

My models:

class Client < ActiveRecord::Base
  has_many :locations, :dependent => :destroy

  accepts_nested_attributes_for :locations, :allow_destroy => true, :update_only => true
end

class Location < ActiveRecord::Base
  belongs_to :client
end

Selectcom::Application.routes.draw do
  resources :clients
end

My Controller:

class ClientsController < ApplicationController

  before_action :set_client, only: [:edit, :update]

  def index
    @clients = Client.paginate(page: params[:page])
  end

  def new
    @client = Client.new
  end

  def create
    @client = Client.new(client_params)

    if @client.save
      flash[:success] = "Client added successfully"
      redirect_to clients_path
    else
      render 'new'
    end
  end

  def edit
  end

  def update
    if @client.update(only_client_params)
      flash[:success] = "Job updated successfully"
      redirect_to clients_path
    else
      render 'edit'
    end
  end

  private

  def set_client
    @client = Client.find(params[:id])  
  end

  def client_params
    params.require(:client).permit(
      :name,
      :phone,
      :fax,
      :url,
      :address,
      :city,
      locations_attributes: [
        :site,
        :fax,
        :phone,
        :url,
        :address,
        :_destroy
      ]
    )
  end
end

This is the client's edit.html.erb form

<%= form_for(@client, class: 'form-horizontal') do |f| %>
  <%= render(partial: 'client_field', locals: {f: f}) %>

  <%= f.fields_for :locations do |l| %>
    <%= l.hidden_field :client_id, value: @client.id %>
    <%= l.hidden_field :_destroy %>
    <%= l.text_field :site, class: 'form-control' %>

  <% end %>
 <%= f.submit "Save", class: "btn lg-button" %>
<% end %> 
Was it helpful?

Solution

Actually, you have two issues here. One in your Model and other in your Controller permitted params. Let's dig into them:

1) Model

The update_only option is ignored when used with collection association (it is your case), as said in Rails documentation:

For a one-to-one association, this option allows you to specify how nested attributes are to be used when an associated record already exists. In general, an existing record may either be updated with the new set of attribute values or be replaced by a wholly new record containing those values.

By default the :update_only option is false and the nested attributes are used to update the existing record only if they include the record's :id value. Otherwise a new record will be instantiated and used to replace the existing one.

However if the :update_only option is true, the nested attributes are used to update the record's attributes always, regardless of whether the :id is present. The option is ignored for collection associations.

So, the first step would be removing the update_only option of your Client class, because it will be ignored since you have a has_many association (collection association) with locations:

class Client < ActiveRecord::Base
  has_many :locations, :dependent => :destroy

  accepts_nested_attributes_for :locations, :allow_destroy => true
end

2) Controller

You have to permit the :id key for the :locations_attributes array. Since your update_only option in the model was ignored, Rails needs the param to tell that it is a record that exists and it's being updated rather than created.

You can solve your issue using the following in your client_params method (note the addition of the id key in your :locations_attributes key):

def client_params
  params.require(:client).permit(
    :name,
    :phone,
    :fax,
    :url,
    :address,
    :city,
    locations_attributes: [
      :id, # Should be present; otherwise, Rails thinks that is a new record
      :site,
      :fax,
      :phone,
      :url,
      :address,
      :_destroy
    ]
  )
end

I hope it helps !!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top