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

问题描述 投票:28回答:6

如果有孩子,我试图阻止记录被销毁。

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

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

callback ruby-on-rails-4
6个回答
25
投票

我会尝试下面的代码:

  1. 使用has_many:通过关联进行付款
  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

45
投票

在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

30
投票

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

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


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对象。


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

0
投票

在Rails 5中你还可以:

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

submission.destroy # => submission
submission.destroyed? # => true/false
© www.soinside.com 2019 - 2024. All rights reserved.