Search code examples
ruby-on-railserror-handlingruby-on-rails-5

Couldn't find <Model> without an ID - rails 5


I have two models: RegisterHour and Employee

This is my model RegisterHour:

class RegisterHour < ApplicationRecord
   belongs_to :employee, class_name: 'Employee', foreign_key: 'employee_id'
end

And this my model: Employee

class Employee < ApplicationRecord
   has_many :register_hours, dependent: :destroy
end

The mapping of my routes for this case

resources :employees do
   resources :register_hours
end

This my controller:

class RegisterHoursController < ApplicationController
  before_action :set_employee
  before_action :set_hour, :set_employee_hour

  def index
   json_response(@employee.register_hours)
  end

  private
   def set_hour
     @register_hour = RegisterHour.find(params[:id])
   end

   def set_employee
     @employee = Employee.find(params[:employee_id])
   end

   def set_employee_hour
     @register_hour = @employee.register_hours.find_by(id: params[:id]) if @employee
   end


   def hour_params
    params.permit(:status)
   end
 end

So, when I run the route: http://my_url_in_local/employees/1/register_hours I got this error:

{
  "message": "Couldn't find RegisterHour without an ID"
}

I'm sooo confused because I have another controller with the same structure to manage admins and employees, and it works, but this controller don't.

I'm newbie with rails that's my confusion, if anyone can help me to understand what I'm doing wrong I gonna be super happy :)

Thank you!!


Solution

  • Looking to how your routes are defined, you have something like this:

    employee_register_hours GET    /employees/:employee_id/register_hours(.:format)     register_hours#index
                            POST   /employees/:employee_id/register_hours(.:format)     register_hours#create
     employee_register_hour GET    /employees/:employee_id/register_hours/:id(.:format) register_hours#show
                            PATCH  /employees/:employee_id/register_hours/:id(.:format) register_hours#update
                            PUT    /employees/:employee_id/register_hours/:id(.:format) register_hours#update
                            DELETE /employees/:employee_id/register_hours/:id(.:format) register_hours#destroy
                  employees GET    /employees(.:format)                                 employees#index
                            POST   /employees(.:format)                                 employees#create
                   employee GET    /employees/:id(.:format)                             employees#show
                            PATCH  /employees/:id(.:format)                             employees#update
                            PUT    /employees/:id(.:format)                             employees#update
                            DELETE /employees/:id(.:format)                             employees#destroy
    

    If you see there, the URI routing the index action in RegisterHoursController only states that an employee_id in the params is expected. But in your private set_hour method, you're expecting that the params contain an id corresponding to an existing RecordHour record in your database:

    RegisterHour.find(params[:id])
    

    There's where your error is happening. As you're not sending that id, find is raising a ActiveRecord::RecordNotFound error.

    You have a simple solution, that's to remove that method and everything related to it. So, your RegisterHoursController would end up in something much more simple:

    class RegisterHoursController < ApplicationController
      def index
        @employee = Employee.find(params[:employee_id])
        json_response(@employee.register_hours)
      end
    
      private
    
      def hour_params
        params.permit(:status)
      end
    end
    

    Why? As you don't receive a RegisterHour id, then set_hour isn't necessary, this way set_employee_hour also becomes unnecessary, because @employee.register_hours.find_by(id: params[:id]) is always going to return nil.

    Removing that, you end up with set_employee, which can be just moved to the action where it belongs, allowing you to remove the before_action callback.

    Notice, if you're using only the index action in RegisterHoursController, then you can avoid generating all other unneeded routes:

    resources :employees do
      resources :register_hours, only: :index
    end