[英]Invalidating parent model save through child before_save callback
我有两个模型,父母和孩子(如下所述)。 子模型有一个before_save
回调before_save
来处理某些外部逻辑,如果遇到任何错误,则该回调函数会使正在保存的模型无效。
class Parent < ActiveRecord::Base
has_one :child
accepts_nested_attributes_for :child
validates :child, :presence => true
validates_associated :child
end
class Child < ActiveRecord::Base
belongs_to :parent
before_save :external_logic
validates :parent, :presence => true
def external_logic
begin
# Some logic
rescue
#Invalidate child model
errors.add(:base, "external logic failed")
return false
end
end
end
我遇到的问题是,通过父模型的嵌套属性创建了子模型实例。 当外部逻辑失败时,我希望不保存子模型和父模型,而是单独保存父模型。 我该如何实现?
请注意,我知道验证回调,但在这种情况下不适合使用。 子模型回调必须为before_save。
编辑#1
我已经对交易有所了解,并且不认为有人告诉我“嘿,将其围绕外部交易”是有效的回答。 这个问题明确地与如何通过before_save调用解决此问题有关。
为什么我不能在create上使用验证-如注释中所述,需要保证逻辑的外部位只在数据库保存之前运行。 无论是否更改数据库记录,验证调用都可能发生多次,因此放置此逻辑是不合适的。
编辑#2
好的,显然让before_save
返回false确实可以防止父级被保存。 我已经通过控制台并实际检查数据库进行了验证。 但是,我的rspec测试告诉我其他情况,这很奇怪。 特别是,这失败了:
describe "parent attributes hash" do
it "creates new record" do
parent = Parent.create(:name => "name", :child_attributes => {:name => "childname"})
customer.persisted?.should be_false
end
end
难道是rspec / factory_girl有点怪异?
编辑#3
测试错误是因为我在Rspec中使用事务性固定装置。 这导致测试错误地告诉我对象确实在数据库中被持久保存。
config.use_transactional_fixtures = true
好的,所以您的问题出在ActiveRecord :: Callbacks订单上。
如您在链接页面上看到的, 首先处理验证 , 如果验证成功,则运行before_save回调 。 before_save
是您可以假设通过所有验证的地方,因此您可以操纵位数据或基于其他属性填充自定义属性。 像这样的东西。
因此,您只能为Child
模型说些什么:
validate :external_logic
,只删除before_save :external_logic
回调。
这等效于您要执行的操作。 当创建一个Parent
实例时,如果Child
对象无法通过验证,将会出错,这将在您的:external_logic验证方法中发生。 这是一种自定义验证方法技术 。
仍然可以使用:validate
方法。 您可以将其设置为仅在具有以下条件的create
上运行:
validate :external_logic, :on => :create
。
如果遇到问题,则还需要在update
运行它,这是默认行为。 验证仅在.create和.update上运行。
或如果您要坚持before_save:
The whole callback chain is wrapped in a transaction. If any before callback method returns exactly false or raises an exception, the execution chain gets halted and a ROLLBACK is issued; after callbacks can only accomplish that by raising an exception.
我看到您确实return false
所以它应该可以正常工作。 您如何使用Parent.create!
方法? 那里的论点是什么?
确保以类似方式使用它(假设.name
是Parent和Child的属性):
Parent.create!{
:name => 'MyParent'
# other attributes for Parent
:child_attributes => {
:name => 'MyChild'
# other attributes for Child
}
}
这样,它将在同一事务中创建父对象和子对象,因此,如果before_save
方法返回false,则将回滚父对象。
要么
如果您不能使用这种格式,则可以尝试使用纯事务( doc , 指南中的示例 ):
Parent.transaction do
p = Parent.create
raise Exception if true # any condition
end
如果在该块内引发异常,则在此事务内执行的任何操作都将回滚。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.