before_destroy callback не останавливает запись от удаления
Я пытаюсь предотвратить уничтожение записи, если есть дети.
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.
Однако: check_for_payments в обратном вызове Submission before_destroy не прекращает удаление Представление.
Как я могу приостановить подачу с момента уничтожения платежей?
Ответы
Ответ 1
Я бы попробовал код ниже, где у меня есть:
- использовал has_many: через ассоциацию для платежей
- избегали ненужного извлечения записей котировок и платежей с помощью
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
private
def check_for_payments
if payments.any?
errors[:base] << "cannot delete submission that has already been paid"
return false
end
end
end
Ответ 2
В 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
Ответ 3
http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html порядок обратных вызовов (я изменил формулировку на этот конкретный пример)
Иногда код требует, чтобы обратные вызовы выполнялись в определенном порядке. Например, перед тем, как котировки будут уничтожены с помощью опции +dependent: destroy+, должен быть выполнен обратный вызов before_destroy (check_for_payments в этом случае).
В этом случае проблема заключается в том, что при выполнении обратного вызова before_destroy котировка недоступна, так как обратный вызов destroy сначала выполняется. Чтобы избежать этого, вы можете использовать опцию prepend для обратного вызова 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
Он прошел !. Надеюсь, это поможет.
Ответ 4
Насколько я знаю, когда уничтожается вызов объекта класса (Submission
), имеющего связь с dependent => :destroy
, и если какой-либо обратный вызов в связанной модели не работает, в вашем случае " Quotation
, то объект класса " Submission
" все равно будут удалены.
Поэтому, чтобы предотвратить это поведение, мы должны использовать методы, о которых я могу сейчас думать:
1) Вместо возвращения ложна в Quotation#check_for_payments
, вы могли бы вызвать исключение и обработать его изящно в Submission
модели, которая будет делать полный ROLLBACK
, и не позволить какой - либо записи будут уничтожены.
2) Вы можете проверить, является ли какие - либо quotations
для Submission
, например, иметь payment
/payment_notification
в Submission#check_for_payments
сам метод, который предотвратил бы исключить Submission
объекта.
Ответ 5
Убедитесь, что 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
Ответ 6
В Rails 5 вы также можете:
def destroy
quoted? && has_payments? ? self : super
end
submission.destroy # => submission
submission.destroyed? # => true/false