Как мне «проверить» уничтожение в рельсах
-
02-07-2019 - |
Вопрос
При уничтожении спокойного ресурса я хочу гарантировать несколько вещей, прежде чем разрешить продолжение операции уничтожения?По сути, мне нужна возможность остановить операцию уничтожения, если я замечу, что это приведет к переводу базы данных в недопустимое состояние?Для операции уничтожения не существует обратных вызовов проверки, так как же «проверить», следует ли принять операцию уничтожения?
Решение
Вы можете вызвать исключение, которое затем перехватить.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
будет заполнено по возвращении.
Другие советы
просто примечание:
Для рельсов 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
Это то, что я сделал с 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
Ассоциации ActiveRecord has_many и has_one допускают зависимую опцию, которая гарантирует удаление связанных строк таблицы при удалении, но обычно это делается для поддержания чистоты вашей базы данных, а не для предотвращения ее недействительности.
Вы можете обернуть действие уничтожения оператором if в контроллере:
def destroy # in controller context
if (model.valid_destroy?)
model.destroy # if in model context, use `super`
end
end
Где действительный_уничтожить? — это метод вашего класса модели, который возвращает true, если соблюдены условия для уничтожения записи.
Наличие такого метода также позволит вам запретить отображение опции удаления пользователю, что улучшит взаимодействие с пользователем, поскольку пользователь не сможет выполнить незаконную операцию.
В итоге я использовал код отсюда, чтобы создать переопределение 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
Я надеялся, что это будет поддерживаться, поэтому открыл вопрос о рельсах, чтобы добавить его: