【发布时间】:2015-10-27 13:07:47
【问题描述】:
我发现 ActiveRecord 似乎不必要地验证子记录的情况。提前道歉,因为这很复杂。
这涉及通过以前使用过但未以任何方式更改的关联。它发生在 3.2 到最近的 master 上。我不确定这是导致意外行为的设计决策,还是某种错误。
我从实际代码中减少了一个测试用例,如下所示:
型号:
class A < ActiveRecord::Base
belongs_to :b
has_many :cs, :through => :b
before_validation { puts "A" }
end
class B < ActiveRecord::Base
has_many :as
has_many :cs
before_validation { puts "B" }
end
class C < ActiveRecord::Base
belongs_to :b
before_validation { puts "C" }
end
迁移:
class AddABC < ActiveRecord::Migration
def change
create_table :as do |t|
t.references :b
end
create_table :bs do |t|
end
create_table :cs do |t|
t.references :b
end
end
end
在空数据库上运行时触发它的简化测试用例是这样的:
b = B.create!
c = C.create!
b.cs << c
a = A.new
a.b = b
a.cs.first
puts "X"
a.valid?
给出输出:
B
C
C
X
A
C
这表明验证 A 会验证其 C。
现在研究了这个我知道has_many :validate => false 选项,并使用它,问题就消失了。但在我看来,这里发生的事情比这更多——请耐心等待。
:validate 如果为 false,则在保存父对象时不验证关联对象。默认为真。
但我觉得这很令人困惑,因为这显然不能表示所有记录。如果我从来没有得到关联(从上面的代码中删除a.cs.first),或者我得到它但从不使用它(替换为a.cs),它将不会验证对象。这是因为它通过lib/active_record/autosave_association.rb 中的validate_collection_association 包含代码:
def validate_collection_association(reflection)
if association = association_instance_get(reflection.name)
if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
records.each_with_index { |record, index| association_valid?(reflection, record, index) }
end
end
end
这一切都取决于从关联缓存中获取的association_instance_get。没有缓存意味着没有要验证的记录。
我试图做一个更简单的has_many,只设置一个引用A的B模型,但是我需要在A之前创建B,然后如果我尝试A将不再是新记录保存它,这段代码可以防止出现问题,因为调用的分支将不再是第一个:
def associated_records_to_validate_or_save(association, new_record, autosave)
if new_record
association && association.target
elsif autosave
association.target.find_all(&:changed_for_autosave?)
else
association.target.find_all(&:new_record?)
end
end
我能想到的唯一真正的解释是只验证加载的记录是因为 ActiveRecord 的目的是只验证更改的记录。我真的希望它验证当且仅当它要保存时,因此仅保存更改记录的默认自动保存选项应该会阻止验证。
我找到了 related ticket 和提交 27aa4dda7d89ce733(我认为还没有在任何版本中)进行了更改,但没有从我的测试中解决这个特定问题。但是它确实包含以下表达式:
!record.persisted? || record.changed? || record.marked_for_destruction?
如果我将此条件添加到validate_collection_association 的最内层循环,那么问题就会消失,ActiveRecord 测试仍在我的机器上通过。
这在我的项目中是一个重大的性能问题,因为有问题的模型只应该在管理员中进行验证,其中自定义验证中使用的未索引字段是可以接受的,因为它很少被保存,因此我判断索引它会过度索引(它不仅仅是一个字段)。显然,在大多数情况下,这种过度验证不会那么严重,而且它似乎只发生在非常特定的情况下,所以这可能是一个错误。
所以,虽然我对正在发生的事情有一个好主意,但我并不完全确定应该发生什么,这就是为什么我没有将此作为 ActiveRecord 票提交。你认为这是一个错误吗?为什么它会这样工作?验证选项的真正用途是什么?如果这是一个错误,您能解释一下为什么代码会这样工作,以及为什么会超出范围吗?什么情况下我的代码更改为 ActiveRecord 以上会中断?
【问题讨论】:
-
我很想看看在关联上添加 inverse_of: 是否会改变这种行为。 TBH 不过,我不抱太大希望。
-
在我的测试中没有。
标签: ruby-on-rails validation activerecord