Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to prevent nested create failing with scoped has_many in ActiveRecord?

A report_template has_many report_template_columns, which each have a name and an index attribute.

class ReportTemplateColumn < ApplicationRecord
  belongs_to :report_template
  validates :name, presence: true
end

class ReportTemplate < ApplicationRecord
  has_many :report_template_columns, -> { order(index: :asc) }, dependent: :destroy
  accepts_nested_attributes_for :report_template_columns, allow_destroy: true
end

The report_template_columns need to be ordered by the index column. I'm applying this with a scope on the has_many association, however doing so causes the following error:

> ReportTemplate.create!(report_template_columns: [ReportTemplateColumn.new(name: 'id', index: '1')])
ActiveRecord::RecordInvalid: Validation failed: Report template columns report template must exist
from /usr/local/bundle/gems/activerecord-5.1.4/lib/active_record/validations.rb:78:in `raise_validation_error'

If I remove the scope the same command succeeds.

If I replace the order scope with where scope that command fails in the same way, so it seems to be the presence of the scope rather than the use of order specifically.

How can I apply a scope to the has_many without breaking the nested creation?

like image 500
tommarshall Avatar asked Mar 08 '23 18:03

tommarshall


1 Answers

I believe you need the :inverse_of option added to the has_many association.

class ReportTemplate < ApplicationRecord
  has_many :report_template_columns, -> { order(index: :asc) },
           dependent: :destroy, inverse_of: :report_template
end

The api states that :inverse_of:

Specifies the name of the belongs_to association on the associated object that is the inverse of this has_many association. Does not work in combination with :through or :as options. See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.

I also like how the cocoon gem words their reason for using it:

Rails 5 Note: since rails 5 a belongs_to relation is by default required. While this absolutely makes sense, this also means associations have to be declared more explicitly. When saving nested items, theoretically the parent is not yet saved on validation, so rails needs help to know the link between relations. There are two ways: either declare the belongs_to as optional: false, but the cleanest way is to specify the inverse_of: on the has_many. That is why we write: has_many :tasks, inverse_of: :project

like image 101
ardavis Avatar answered Apr 05 '23 23:04

ardavis