before_destroy回调不会停止记录被删除

时间:2013-09-27 07:06:38

标签: callback ruby-on-rails-4

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

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并不会阻止提交被删除。

如何通过销毁付款来停止提交?

6 个答案:

答案 0 :(得分:43)

Rails 5 中,你必须my-slideshow否则它将无效。 (甚至返回throw :abort

这样的事情应该有效:

false

答案 1 :(得分:30)

http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html 订购回调(我改变了这个具体例子的措辞)

  

有时代码需要回调在特定的情况下执行   订购。例如,before_destroy回调(check_for_payments in   这个案例)应该在报价被销毁之前执行   + dependent:destroy +选项。

     

在这种情况下,问题是before_destroy回调是   执行时,引用不可用,因为destroy回调   先被执行。您可以使用前置选项   before_destroy回调以避免这种情况。

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

它过去了!我希望有所帮助。

答案 2 :(得分:23)

我会尝试下面的代码:

  1. 使用了has_many:通过关联付款
  2. 通过使用any?而不使用阻止导致使用关联计数器缓存(如果已定义),或者如果已加载关联的大小而导致使用SQL COUNT(如果需要)失败,则避免对报价和付款进行不必要的记录检索。
  3. 避免列举报价
  4. 避免直接测试q.payment关联代理的真实性/存在性,这对has_xxx不起作用。如果您要测试在线使用q.payment.present?
  5. 尝试以下操作,看看你如何:

    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
    

答案 3 :(得分:1)

据我所知,当对与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对象。

答案 4 :(得分: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

答案 5 :(得分:0)

在Rails 5中你也可以:

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

submission.destroy # => submission
submission.destroyed? # => true/false