33

如果有孩子,我试图防止记录被破坏。

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 会阻止删除报价记录。

但是,提交 before_destroy 回调中的 :check_for_payments 不会阻止提交被删除。

我怎样才能阻止提交的付款被销毁?

4

6 回答 6

67

Rails 5中,您必须这样做,throw :abort否则它将无法工作。(甚至返回false

此外,您应该添加prepend: true回调,以确保它在dependent: :destroy父模型上运行之前运行。

像这样的东西应该工作:

class Something < ApplicationRecord

  before_destroy :can_destroy?, prepend: true

  private

  def can_destroy?
    if model.something?
      self.errors.add(:base, "Can't be destroy because of something")
      throw :abort
    end
  end
end
于 2017-01-13T15:48:33.643 回答
33

http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html 排序回调(我把措辞改成了这个具体的例子)

有时代码需要回调以特定顺序执行。例如,一个 before_destroy 回调(在这种情况下是 check_for_payments)应该在报价被 +dependent:destroy+ 选项销毁之前执行。

在这种情况下,问题是在执行 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

它通过了!我希望这会有所帮助。

于 2013-10-05T19:53:35.883 回答
28

我会尝试下面的代码:

  1. 使用 has_many :through 支付关联
  2. any?通过使用不使用导致使用关联计数器缓存(如果已定义)或关联大小(如果已加载)并在需要时不使用 SQL COUNT 的块来避免不必要的报价和付款记录检索。
  3. 避免列举引用
  4. 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
于 2013-10-02T12:15:28.013 回答
1

据我所知,当对Submission具有关联的 Class()的对象调用 destroy 时dependent => :destroy,如果关联模型中的任何回调失败,在您的情况下QuotationSubmission类对象仍将被删除。

因此,为了防止这种行为,我们必须采用我目前能想到的方法:

Quotation#check_for_payments1)您可以引发异常并在模型中优雅地处理它,而不是返回 false in Submission,这将做一个完整的ROLLBACK,并且不会让任何记录被破坏。

2)您可以检查quotations一个Submission实例是否有payment/ payment_notificationinSubmission#check_for_payments方法本身,这将防止删除Submission对象。

于 2013-10-05T11:02:17.057 回答
1

确保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
于 2013-10-05T12:14:52.470 回答
0

在 Rails 5 中,您还可以:

def destroy
  quoted? && has_payments? ? self : super
end

submission.destroy # => submission
submission.destroyed? # => true/false
于 2017-11-15T10:20:57.717 回答