如果有孩子,我试图阻止记录被销毁。
class Submission < ActiveRecord::Base
has_many :quotations, :dependent => :destroy
before_destroy :check_for_payments
def quoted?
quotations.any?
end
def has_payments?
true if quotations.detect {|q| q.payment}
end
private
def check_for_payments
if quoted? && has_payments?
errors[:base] << "cannot delete submission that has already been paid"
false
end
end
end
class Quotation < ActiveRecord::Base
#associations
belongs_to :submission
has_one :payment_notification
has_one :payment
before_destroy :check_for_payments
private
def check_for_payments
if payment_notification || payment
errors[:base] << "cannot delete quotation while payment exist"
return false
end
end
end
当我测试此代码时,before_destroy:check_for_payments会阻止删除Quotation记录。
但是:提交before_destroy回调中的:check_for_payments不会停止提交被删除。
如何通过销毁付款来停止提交?
我会尝试下面的代码:
any?
避免不必要的记录检索报价和付款,这导致使用关联计数器缓存(如果已定义),或者如果已经加载则关联的大小,并且如果需要则失败SQL COUNT。q.payment
关联代理的真实性/存在性,这对has_xxx不起作用。如果你想测试存在使用q.payment.present?
尝试以下操作,看看你如何去:
class Submission < ActiveRecord::Base
has_many :quotations,
inverse_of: :submission,
dependent: :destroy
has_many :payments,
through: :quotations
before_destroy :check_for_payments, prepend: true
private
def check_for_payments
if payments.any?
errors[:base] << "cannot delete submission that has already been paid"
return false
end
end
end
在Rails 5中你必须使用throw :abort
否则它将无法工作。 (甚至返回false
)
这样的事情应该有效:
class Something < ApplicationRecord
before_destroy :can_destroy?
private
def can_destroy?
if model.something?
errors[:base] << "Can't be destroy because of something"
throw :abort
end
end
http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html订购回调(我改变了这个具体例子的措辞)
有时代码需要回调以特定顺序执行。例如,在引用被+ dependent:destroy +选项销毁之前,应该执行before_destroy回调(在这种情况下为check_for_payments)。
在这种情况下,问题是当执行before_destroy回调时,引用不可用,因为首先执行destroy回调。您可以在before_destroy回调上使用prepend选项来避免这种情况。
before_destroy :check_for_payments, prepend: true
我用上面描述的相同模型制作了一个新应用程序,然后进行了提交测试。它非常难看,我只是在学习......
class Submission < ActiveRecord::Base
has_many :quotations, :dependent => :destroy
before_destroy :check_for_payments, prepend: true
def quoted?
quotations.any?
end
def has_payments?
true if quotations.detect {|q| q.payment }
end
private
def check_for_payments
if quoted? && has_payments?
errors[:base] << "error message"
false
end
end
end
class Quotation < ActiveRecord::Base
belongs_to :submission
has_one :payment_notification
has_one :payment
before_destroy :check_for_payments
private
def check_for_payments
if payment_notification || payment
errors[:base] << "cannot delete quotation while payment exist"
return false
end
end
end
require 'test_helper'
class SubmissionTest < ActiveSupport::TestCase
test "can't destroy" do
sub = Submission.new
sub.save
quote = Quotation.new
quote.submission_id = sub.id
quote.save
pay = Payment.new
pay.quotation_id = quote.id
pay.save
refute sub.destroy, "destroyed record"
end
end
它过去了!我希望有所帮助。
据我所知,当对与Submission
有关联的类(dependent => :destroy
)的对象调用destroy时,如果相关模型中的任何回调失败,在你的情况下Quotation
,那么Submission
类对象仍将被删除。
所以为了防止这种行为,我们必须考虑到目前可以想到的方法:
1)不是在Quotation#check_for_payments
中返回false,而是可以引发异常并在Submission
模型中优雅地处理它,这将完成ROLLBACK
,并且不会让任何记录被破坏。
2)您可以检查quotations
实例中的任何Submission
是否在payment
方法本身中具有payment_notification
/ Submission#check_for_payments
,这将阻止删除Submission
对象。
确保quoted?
和has_payments?
不会返回false。
有关调试,请试试这个
def check_for_payments
raise "Debugging #{quoted?} #{has_payments?}" # Make sure both are true
if quoted? && has_payments?
errors[:base] << "cannot delete submission that has already been paid"
false
end
end
在Rails 5中你还可以:
def destroy
quoted? && has_payments? ? self : super
end
submission.destroy # => submission
submission.destroyed? # => true/false