将我的应用程序从Rails 4.2.8移动到5.2.3后,插入失败了
Billings event must exist
应用程序接收一个带有一个事件和许多相关账单的级联哈希值,并且应该在一个事务中将其放入数据库中;这确实在以前工作过。
class Event < ActiveRecord::Base
has_many :billings, -> { where('1 = 1') }, dependent: :destroy
accepts_nested_attributes_for :billings
validates_associated :billings
end
class Billing < ActiveRecord::Base
belongs_to :event
validates_presence_of :event_id, on: :update
end
class EventsController < ApplicationController
def kC
@event = Event.new(event_params)
if @event.save
[doesn't get here anymore]
end
end
end
账单没有控制器,它们只通过相关事件存在。
快速分析在文档中提到了这一点
belongs_to :event, optional: true
会避免这个错误,确实如此。但这对我来说似乎是非常错误的,因为在这个应用中,如果没有他们的活动,账单绝不可能存在,它不是可选的!但那么,什么是正确的解决方案?
进一步分析显示:所有验证都得到处理,但从未达到before_create()回调。在某些内部地方添加了“必须存在”错误,它不是来自我的代码。
此外,当创建一个只有上面显示的代码的模板时,我发现有问题的代码是范围-> { where('1 = 1') }
在实际应用中,这是一个更复杂(也更有用)的术语,但这个简单且看似透明的术语会引发问题。
这里有很多类似的问题,但是,很多人都有这种情况,其中关联确实是可选的,有些具有非标准的命名(我认为我没有,因为它之前有效),而且我没有找到这种情况所属模型通过一个完全处理。
在Rails 5中,每当我们定义belongs_to
关联时,都需要默认存在关联的记录。如果相关记录不存在,则会触发验证错误。要删除此默认行为,我们可以使用Rails 5附带的new_framework_defaults.rb初始化程序。
(有关更多信息,请查看此https://github.com/rails/rails/pull/18937)
从旧版本的Rails升级到Rails 5时,我们可以通过运行bin/rails app:update
任务来添加此初始化程序。
这个新添加的初始化程序具有以下配置标志,用于处理默认行为
Rails.application.config.active_record.belongs_to_required_by_default = true
我们可以通过将其值设置为false来关闭此行为
Rails.application.config.active_record.belongs_to_required_by_default = false
我找到了似乎正确的解决方案:
class Event < ActiveRecord::Base
has_many :billings, -> { where('1 = 1') }, dependent: :destroy, inverse_of: :event
accepts_nested_attributes_for :billings
validates_associated :billings
end
以这种方式添加此inverse_of:
选项可以解决问题。
初步的根本原因分析:
inverse_of
选项的(稀疏)文档建议将其添加到belongs_to
功能中;它没有提到将它添加到has_many
(它也不会阻止它)。在这种情况下,将它添加到belongs_to
并不会改进,文档中的用例也不适用于此处。
尽管如此,文档提到了关联的“自动猜测”,并且在AssociationReflection::INVALID_AUTOMATIC_INVERSE_OPTIONS
中声明的某些情况下会省略这种自动猜测。在源代码中搜索这个术语会导致私有方法can_find_inverse_of_automatically?()
,很明显,范围也会导致自动猜测被省略。
似乎以某种方式解开累积插入需要精确定位“inverse_of”(自动或编码),否则它会认为拥有关系是不存在的 - 后者,由于Rails 5中提到的变化,现在导致验证错误。