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

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)

p11*_*00i 5

好的,所以你的问题是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.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)

如果块内部引发异常,那么您在此事务中执行的任何操作都将被回滚.