繁体   English   中英

通过子级before_save回调使父模型保存无效

[英]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验证方法中发生。 这是一种自定义验证方法技术

OP更新后:

仍然可以使用: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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM