0

The problem:

When I'm editing the contents of a nested model, it saves new entries instead of editing the ones already there.

The models:

# job.rb
class Job < ActiveRecord::Base
    # Relations
    has_many :logs, :dependent => :destroy

    accepts_nested_attributes_for :logs, allow_destroy: true, reject_if: proc { |a| a['user_id'].blank? }
end


# log.rb
class Log < ActiveRecord::Base
    belongs_to :job
    belongs_to :user

    # a single user should not be logged more than once per job
    validates :user_id, uniqueness: { scope: :job_id }
end


# user.rb
class User < ActiveRecord::Base
    has_many :logs

    validates_presence_of :name

    def self.all_active
        User.where( active: 1 )
    end

end

The Job controller:

class JobsController < ApplicationController
before_action :set_job, only: [:show, :edit, :update, :destroy]

def index
  @jobs = Job.all
end

def show
end

def new
  @job = Job.new
  10.times { @job.logs.build }
end

def edit
  ( @job.people - @job.logs.count ).times { @job.logs.build }
end

def create
  @job = Job.new(job_params)

  # Set the admin id
  @job.logs.each do |log|
    log.admin_id = current_admin.id
  end

  respond_to do |format|
    if @job.save
      format.html { redirect_to @job, notice: 'Job was successfully created.' }
      format.json { render :show, status: :created, location: @job }
    else
      format.html { render :new }
      format.json { render json: @job.errors, status: :unprocessable_entity }
    end
  end
end

def update
  respond_to do |format|
    if @job.update(job_params)
      format.html { redirect_to @job, notice: 'Job was successfully updated.' }
      format.json { render :show, status: :ok, location: @job }
    else
      format.html { render :edit }
      format.json { render json: @job.errors, status: :unprocessable_entity }
    end
  end
end

def destroy
  @job.destroy
  respond_to do |format|
    format.html { redirect_to jobs_url, notice: 'Job was successfully destroyed.' }
    format.json { head :no_content }
  end
end

private
  # Use callbacks to share common setup or constraints between actions.
  def set_job
    @job = Job.find(params[:id])
  end

  # Never trust parameters from the scary internet, only allow the white list through.
  def job_params
    params.require(:job).permit(:people, :logs_attributes => [:user_id])
  end
end

The Job Form

<%= form_for(@job) do |f| %>
<% if @job.errors.any? %>
  <div id="error_explanation">
    <h2><%= pluralize(@job.errors.count, "error") %> prohibited this job from being saved:</h2>

    <ul>
    <% @job.errors.full_messages.each do |message| %>
      <li><%= message %></li>
    <% end %>
    </ul>
  </div>
<% end %>

<div class="row">
  <div class="small-3 columns"><%= f.label :people %></div>
  <div class="small-9 columns"><%= f.integer :people %></div>
</div>

<table>
  <%= f.fields_for :logs do |builder| %>
    <%= render 'logs/form', { f: builder } %>
  <% end %>
</table>

<div class="actions">
  <%= f.submit %>
</div>

The Logs Partial:

<tr>
  <td>
    <%= f.label :user_id %><br>
    <%= f.collection_select(:user_id, User.all_active, :id, :name, { :prompt => 'Select User' } ) %>
  </td>
  <td>
    <%= f.label :performance %><br>
    <%= f.number_field :performance %>
  </td>
</tr>

Creating the job works fine. I then go to edit the entry. On the job edit screen, I see all of the logs included along with a few remaining blank log entry lines (due to the 10.times @logs.build in the Job controller).

Now, without editing anything, I click submit. I get the following error:

1 error prohibited this war from being saved:
  Logs user has already been taken

Additionally, the original entries are shown, and then below them are the same exact entries duplicated, but "red" due to the error. The list of blank entries (from 10.times) is no longer showing.

However, from the editing screen, if I change ALL of the logs' users to something else, I will not get an error. It will instead create new entries with those newly-selected users instead of modifying the current entries.

I hope I've provided enough information to get this resolved! Thanks

Wes Foster
  • 8,202
  • 4
  • 36
  • 57

1 Answers1

0

So, it turns out that the problem was caused by not whitelisting the :id parameter of the log.

I altered my job_controller:

From:

def job_params
  params.require(:job).permit(:people, :logs_attributes => [:user_id])
end

To:

def job_params
  params.require(:job).permit(:people, :logs_attributes => [:id, :user_id])
end

That was what was causing the problem with validation! This may be too complex of q/a to help anyone, but hey, it just might!

Wes Foster
  • 8,202
  • 4
  • 36
  • 57