Rails 5 Active Record Record Invalid Error
Asked Answered
H

2

5

I have two Rails model namely Invoice and Invoice_details. An Invoice_details belongs to Invoice, and an Invoice has many Invoice_details. I'm not able to save Invoice_details via Invoice model using accepts_nested_attributes_for in Invoice.

I get the following error :

(0.2ms)  BEGIN
(0.2ms)  ROLLBACK
Completed 422 Unprocessable Entity in 25ms (ActiveRecord: 4.0ms)         
ActiveRecord::RecordInvalid (Validation failed: Invoice details invoice must exist):
app/controllers/api/v1/invoices_controller.rb:17:in `create'

Below is the code snippet for invoice.rb :

class Invoice < ApplicationRecord
  has_many :invoice_details
  accepts_nested_attributes_for :invoice_details
end

The code snippet for invoice_details.rb :

class InvoiceDetail < ApplicationRecord
  belongs_to :invoice
end

The Controller code :

class Api::V1::InvoicesController < ApplicationController
  respond_to :json

  def index
    comp_id = params[:comp_id]
    if comp_id
      invoices = Invoice.where(:company_id => comp_id)
      render json: invoices, status: 201
    else
      render json: { errors: "Company ID is NULL" }, status: 422
    end
  end

  def create
    Rails.logger.debug invoice_params.inspect
    invoice = Invoice.new(invoice_params)
    if invoice.save!
      render json: invoice, status: 201
    else
      render json: { errors: invoice.errors }, status: 422
    end
  end

  def invoice_params
    params.require(:invoice).permit(:total_amount,:balance_amount, :customer_id, invoice_details_attributes:[:product_id])
  end
end

The raw JSON data passed to the controller :

{
    "invoice":{
        "total_amount":"100",
        "balance_amount":"0",
        "customer_id":"1",
        "invoice_details_attributes":[{
            "product_id":"4"
        }]
    }
}

invoice_details schema

|id | invoice_id | product_id | created_at | updated_at|

invoice schema

|id| total_amount |balance_amount | generation_date | created_at | updated_at | customers_id|
Handrail answered 17/6, 2017 at 13:11 Comment(4)
Try adding invoice_id in invoice_details_attributes of invoice_paramsAnhydrite
Can you post your schema for the invoice detail? It seems like something is going wrong with the autosave there.Soult
@Anhydrite good catch, I forgot rails 5 now validates the belongs_to unless directed otherwise.Soult
Did that worked?Anhydrite
H
2

I still don't know the reason why the above thing was not working however when I explicitly declared a bi-directional relation between the two models by using inverse_of.

class Invoice < ApplicationRecord
  has_many :invoiceDetails, inverse_of: :invoice
  accepts_nested_attributes_for :invoiceDetails
end

It seemed Rails was not setting the invoice attribute on the invoice_details before attempting to save it, triggering the validation errors. This is a bit surprising as other has_many relations should have the same save mechanics and were working just fine.

After a bit of googling I found few posts on this behaviour occurring in the older version of Rails, I don't know if that still exists in the new version. Further can be looked in these links :

  1. https://github.com/rails/rails/issues/6161#issuecomment-8615049
  2. https://github.com/rails/rails/pull/7661#issuecomment-8614206
  3. https://github.com/rails/rails/issues/6161#issuecomment-6330795
Handrail answered 18/6, 2017 at 4:43 Comment(0)
A
6

ActiveRecord::RecordInvalid (Validation failed: Invoice details invoice must exist):

The error is due to you are not permitting invoice_id while saving invoice_detail for invoice

In Rails 5, the presence of associated object will be validated by default. You can bypass this validation by setting optional :true

From the Guides

If you set the :optional option to true, then the presence of the associated object won't be validated. By default, this option is set to false.

Solution:

Either permit invoice_id in invoice_details_attributes of invoice_params

def invoice_params
  params.require(:invoice).permit(:total_amount,:balance_amount, :customer_id, invoice_details_attributes: [:product_id, :invoice_id])
end

OR

If you wish not to, then set optional :true

class InvoiceDetail < ApplicationRecord
  belongs_to :invoice, optional :true
end
Anhydrite answered 17/6, 2017 at 13:23 Comment(2)
I did added invoice_id to permit clause but it did not worked, got the same error. Also I do not want invoice details to be optional as at least one detail should be present for invoice.Handrail
'def invoice_params params.require(:invoice).permit(:total_amount,:balance_amount, :customer_id, invoice_details_attributes:[:invoice_id,:product_id]) end'Handrail
H
2

I still don't know the reason why the above thing was not working however when I explicitly declared a bi-directional relation between the two models by using inverse_of.

class Invoice < ApplicationRecord
  has_many :invoiceDetails, inverse_of: :invoice
  accepts_nested_attributes_for :invoiceDetails
end

It seemed Rails was not setting the invoice attribute on the invoice_details before attempting to save it, triggering the validation errors. This is a bit surprising as other has_many relations should have the same save mechanics and were working just fine.

After a bit of googling I found few posts on this behaviour occurring in the older version of Rails, I don't know if that still exists in the new version. Further can be looked in these links :

  1. https://github.com/rails/rails/issues/6161#issuecomment-8615049
  2. https://github.com/rails/rails/pull/7661#issuecomment-8614206
  3. https://github.com/rails/rails/issues/6161#issuecomment-6330795
Handrail answered 18/6, 2017 at 4:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.