Bry*_*yce 2 ruby-on-rails ruby-on-rails-3
我有两个模型,父母和孩子(如下所述).子模型有一个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
Run Code Online (Sandbox Code Playgroud)
我遇到的问题是Child模型实例是通过Parent模型的嵌套属性创建的.当外部逻辑失败时,我希望不保存子模型和父模型,而是保存父模型.我怎样才能做到这一点?
请注意,我知道验证回调,但在这种情况下它们不适合.子模型回调必须是before_save.
编辑#1
我已经知道了交易,并且不认为有人告诉我"嘿,将其包装在外部交易中"作为有效的回复.这个问题明确是关于如何通过before_save调用解决这个问题.
为什么我不能在创建时使用验证 - 如注释中所述,逻辑的外部位需要保证仅在数据库保存之前运行.无论是否更改数据库记录,验证调用都可能发生多次,因此放置此逻辑是不合适的.
编辑#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
Run Code Online (Sandbox Code Playgroud)
这可能是一个很奇怪的rspec/factory_girl吗?
编辑#3
测试错误是因为我在Rspec中使用事务夹具.这导致测试错误地告诉我,当数据库确实不存在时,数据库中存在对象.
config.use_transactional_fixtures = true
Run Code Online (Sandbox Code Playgroud)
好的,所以你的问题是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.create!{
:name => 'MyParent'
# other attributes for Parent
:child_attributes => {
:name => 'MyChild'
# other attributes for Child
}
}
Run Code Online (Sandbox Code Playgroud)
这样,它将在同一事务中创建Parent和Child对象,因此如果您的before_save方法返回false,则将回滚父对象.
要么
如果您不能使用此格式,您可以尝试使用纯事务(doc,指南中的示例):
Parent.transaction do
p = Parent.create
raise Exception if true # any condition
end
Run Code Online (Sandbox Code Playgroud)
如果块内部引发异常,那么您在此事务中执行的任何操作都将被回滚.
| 归档时间: |
|
| 查看次数: |
3179 次 |
| 最近记录: |