Search code examples
ruby-on-railsruby-on-rails-5form-forstrong-parametersnested-resources

Nested resource [Model] reference must exist error on form submit


I've looked at all kinds of threads on the same error and tried different things people suggested, but can't seem to figure this one out. I'm using Rails 5 and keep getting this error when I try to submit my form:

Step reference must exist

Here are my routes

Rails.application.routes.draw do
  resources :jobs do
    resources :steps, except: [:index], controller: 'jobs/steps'
      member do
        patch :complete
      end
  end

  get '/job/:id/next_step', to: 'jobs#next_step', as: 'next_step_jobs'

  root "pages#home"
end

I have a Job model and Step model

class Job < ActiveRecord::Base
  has_many :steps, dependent: :destroy
  accepts_nested_attributes_for :steps

  def step_completed?
    !steps.last.completed_at.blank?
  end
end

class Step < ActiveRecord::Base
  belongs_to :job, optional: true
  belongs_to :step_reference
end

Here are both controllers

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

  # GET /jobs
  def index
    @dashboard = Dashboard.new
  end

  # GET /jobs/:id
  def show
  end

  # GET /jobs/new
  def new
    @job = Job.new
  end

  # GET /jobs/:id/edit
  def edit
  end

  # POST /jobs
  def create
    @job = Job.create(job_params)
    if @job.save
      # redirect to the next_step_path to ask questions
      redirect_to next_step_jobs_path(@job), flash: { success: "Job successfully added" }
    else
      redirect_to new_job_path, flash: { danger: @job.errors.full_messages.join(", ") }
    end
  end

  # PATCH /jobs/:id
  def update
    if @job.update(job_params)
      redirect_to jobs_path, flash: { success: "Job successfully updated" }
    else
      redirect_to edit_jobs_path(@job), flash: { danger: @job.errors.full_messages.join(", ") }
    end
  end

  # GET /jobs/:id/next_step
  def next_step
    # get question number from query string
    @q = params[:q]
    # create new instance of step
    @next = Next.new
    # pull the next_step object with question and answers to pass to the view
    @next_step = @next.next_step(@job, @q)

    if @next_step[:answers][0][:text].nil?
      redirect_to new_job_step_path(@job)
    else
      @next_step
    end
  end

  private
    def set_job
      @job = Job.find(params[:id])
    end

    def job_params
      params.require(:job).permit(:company, :position, :contact, :contact_email,
      :posting_url, steps_attributes: [:step_reference_id, :job_id, :completed_at, :next_step_date])
    end
end



class Jobs::StepsController < ApplicationController
  before_action :set_job
  before_action :set_step, only: [:show, :edit, :update, :complete]

  # GET /jobs/:job_id/steps/:id
  def show
  end

  # GET /jobs/:job_id/steps/new
  def new
    @step = @job.steps.new
  end

  # GET /jobs/:job_id/steps/:id/edit
  def edit
  end

  # POST /jobs/:job_id/steps/
  def create
    @step = @job.steps.create(step_params)
    if @step.save
      redirect_to jobs_path, flash: { success: "Next step successfully added." }
    else
      redirect_to jobs_path, flash: { danger: @step.errors.full_messages.join(", ") }
    end
  end

  # PATCH /jobs/:job_id/steps/:id
  def update
    if @step.update(step_params)
      redirect_to jobs_path, flash: { success: "Next step successfully updated." }
    else
      redirect_to edit_job_step_path, flash: { danger: @step.errors.full_messages.join(", ") }
    end
  end

  # PATCH /jobs/:job_id/steps/:id/complete
  def complete
    if @step.update_attribute(:completed_at, Date.today)
      redirect_to jobs_path, flash: { success: "Job step completed" }
    else
      redirect_to jobs_path, flash: { danger: @step.errors.full_messages.join(", ") }
    end
  end

  private
    def set_job
      @job = Job.find(params[:job_id])
    end

    def set_step
      @step = Step.find(params[:id])
    end

    def step_params
      params.require(:step).permit(:step_reference_id, :job_id, :completed_at, :next_step_date)
    end
end

And here's my view for /jobs/:job_id/steps/new

<h1 class="text-xs-center">New Step</h1>

<%= form_for @step, url: job_steps_path(@job) do |f| %>
  <div class="form-group">
    <%= f.label :next_step_date %>
    <%= f.date_field :next_step_date, class: "form-control", id: "add-next-date-field" %>
  </div>
  <%= f.submit 'Save', class: "btn btn-primary" %>
<% end %>

So the form should be submitting to the StepsController's create method, but when it submits I get the "Step reference must exist" error. Based on other threads it seems like this might have to do with the strong parameters, but everything looks pretty standard to me.

Here's the log from terminal

Started POST "/jobs/1/steps" for ::1 at 2017-01-27 14:28:46 -0500
Processing by Jobs::StepsController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"0hYvZArkUXj7WZw9L04ovYUSm4txz14pk8POgvJxNsdvvQ7bwQLusz5WW2u0wDGgT81k6mwDkMZLyaFIVxZW5w==", "step"=>{"next_step_date"=>"2017-01-31"}, "commit"=>"Save", "job_id"=>"1"}
  Job Load (0.2ms)  SELECT  "jobs".* FROM "jobs" WHERE "jobs"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
   (0.1ms)  begin transaction
  StepReference Load (0.1ms)  SELECT  "step_references".* FROM "step_references" WHERE "step_references"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
   (0.1ms)  commit transaction
   (0.1ms)  begin transaction
   (0.0ms)  rollback transaction
Redirected to http://localhost:3000/jobs
Completed 302 Found in 28ms (ActiveRecord: 1.0ms)

I've been fooling around with this for hours, so if anyone can shed some light I'd be extremely greatful!!!


Solution

  • I think your problem isn't with your params, but rather the way you've built your form. Typically, the way to do forms with nested attributes is to use the fields_for helper (info here).

    So your form should look more like this:

    <%= form_for @job do |f| %>
      <%= f.fields_for :steps do |steps| %>
        <div class="form-group">
          <%= steps.label :next_step_date %>
          <%= steps.date_field :next_step_date, class: "form-control", id: "add-next-date-field" %>
        </div>
      <% end %>
      <%= f.submit 'Save', class: "btn btn-primary" %>
    <% end %>