При уничтожении успокаивающего ресурса я хочу гарантировать несколько вещей, прежде чем разрешу продолжить операцию уничтожения? В принципе, мне нужна возможность остановить операцию уничтожения, если я замечу, что это приведет к тому, что база данных перейдет в недопустимое состояние? Для операции уничтожения нет обратных вызовов проверки, так как же можно «проверить», следует ли принять операцию уничтожения?
ruby-on-rails
ruby
callback
Стивен Кейгл
источник
источник
Ответы:
Вы можете вызвать исключение, которое затем поймаете. Rails обертывает операции удаления в транзакции, что помогает в этом.
Например:
class Booking < ActiveRecord::Base has_many :booking_payments .... def destroy raise "Cannot delete booking with payments" unless booking_payments.count == 0 # ... ok, go ahead and destroy super end end
В качестве альтернативы вы можете использовать обратный вызов before_destroy. Этот обратный вызов обычно используется для уничтожения зависимых записей, но вместо этого вы можете создать исключение или добавить ошибку.
def before_destroy return true if booking_payments.count == 0 errors.add :base, "Cannot delete booking with payments" # or errors.add_to_base in Rails 2 false # Rails 5 throw(:abort) end
myBooking.destroy
теперь вернет false иmyBooking.errors
будет заполнен при возврате.источник
false
в концеbefore_destroy
бесполезен. С этого момента вы должны использоватьthrow(:abort)
(@see: weblog.rubyonrails.org/2015/1/10/This-week-in-Rails/… ).has_many :booking_payments, dependent: :restrict_with_error
просто примечание:
Для рельсов 3
class Booking < ActiveRecord::Base before_destroy :booking_with_payments? private def booking_with_payments? errors.add(:base, "Cannot delete booking with payments") unless booking_payments.count == 0 errors.blank? #return false, to not destroy the element, otherwise, it will delete. end
источник
has_many :booking_payments, dependent: :restrict_with_error
Так я поступил с Rails 5:
before_destroy do cannot_delete_with_qrcodes throw(:abort) if errors.present? end def cannot_delete_with_qrcodes errors.add(:base, 'Cannot delete shop with qrcodes') if qrcodes.any? end
источник
has_many :qrcodes, dependent: :restrict_with_error
Связи ActiveRecord has_many и has_one позволяют использовать зависимую опцию, которая будет обеспечивать удаление связанных строк таблицы при удалении, но обычно это делается для поддержания вашей базы данных в чистоте, а не для предотвращения ее недействительности.
источник
like_so
.dependent
варианты, которые не позволяют удалить объект, если он создаст потерянные записи (это более актуально для вопроса). Egdependent: :restrict_with_error
Вы можете заключить действие уничтожения в оператор «if» в контроллере:
def destroy # in controller context if (model.valid_destroy?) model.destroy # if in model context, use `super` end end
Где valid_destroy? - это метод вашего класса модели, который возвращает истину, если выполняются условия для уничтожения записи.
Наличие такого метода также позволит вам предотвратить отображение опции удаления для пользователя, что улучшит взаимодействие с пользователем, поскольку пользователь не сможет выполнить незаконную операцию.
источник
if
оператор вdestroy
действии вашего контроллера, за исключением тогоif model.valid_destroy?
, что вместо вызова просто вызовитеif model.destroy
и позвольте модели определять, было ли уничтожение успешным и т. Д.Положение дел в Rails 6:
Это работает:
before_destroy :ensure_something, prepend: true do throw(:abort) if errors.present? end private def ensure_something errors.add(:field, "This isn't a good idea..") if something_bad end
validate :validate_test, on: :destroy
не работает: https://github.com/rails/rails/issues/32376Поскольку
throw(:abort)
для отмены выполнения требуется Rails 5 : https://makandracards.com/makandra/20301-cancelling-the-activerecord-callback-chainprepend: true
требуется, чтобыdependent: :destroy
он не запускался до выполнения проверки: https://github.com/rails/rails/issues/3458Вы можете выудить это вместе из других ответов и комментариев, но я не нашел ни одного из них полным.
В качестве дополнительной заметки многие использовали
has_many
отношение в качестве примера, в котором они хотят убедиться, что не удаляют какие-либо записи, если это приведет к созданию потерянных записей. Решить эту проблему гораздо проще:has_many :entities, dependent: :restrict_with_error
источник
before_destroy :handle_destroy, prepend: true; before_destroy { throw(:abort) if errors.present? }
позволит пропускать ошибки из других проверок before_destroy вместо немедленного завершения процесса уничтоженияВ итоге я использовал код отсюда, чтобы создать переопределение can_destroy на activerecord: https://gist.github.com/andhapp/1761098
class ActiveRecord::Base def can_destroy? self.class.reflect_on_all_associations.all? do |assoc| assoc.options[:dependent] != :restrict || (assoc.macro == :has_one && self.send(assoc.name).nil?) || (assoc.macro == :has_many && self.send(assoc.name).empty?) end end end
Это имеет дополнительное преимущество, позволяющее упростить скрытие / отображение кнопки удаления в пользовательском интерфейсе.
источник
Вы также можете использовать обратный вызов before_destroy, чтобы вызвать исключение.
источник
У меня есть эти классы или модели
class Enterprise < AR::Base has_many :products before_destroy :enterprise_with_products? private def empresas_with_portafolios? self.portafolios.empty? end end class Product < AR::Base belongs_to :enterprises end
Теперь, когда вы удаляете предприятие, этот процесс проверяет, есть ли продукты, связанные с предприятиями. Примечание. Вы должны написать это в верхней части класса, чтобы сначала проверить его.
источник
Используйте проверку контекста ActiveRecord в Rails 5.
class ApplicationRecord < ActiveRecord::Base before_destroy do throw :abort if invalid?(:destroy) end end
class Ticket < ApplicationRecord validate :validate_expires_on, on: :destroy def validate_expires_on errors.add :expires_on if expires_on > Time.now end end
источник
on: :destroy
, посмотрите эту проблемуЯ надеялся, что это будет поддерживаться, поэтому я открыл проблему с рельсами, чтобы добавить его:
https://github.com/rails/rails/issues/32376
источник