Как мне "проверить" на уничтожение в рельсах

При уничтожении восстанавливаемого ресурса я хочу гарантировать несколько вещей, прежде чем я позволю продолжить операцию уничтожения? В принципе, мне нужна возможность остановить операцию уничтожения, если я замечу, что это приведет к тому, что база данных перейдет в недопустимое состояние? Для операции уничтожения нет обратных вызовов проверки, так как же «проверить», следует ли принять операцию уничтожения?

Связанный: stackoverflow.com/questions/5520320/validate-before-destroy

Deepak N 08.11.2011 20:31
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
82
1
52 752
11
Перейти к ответу Данный вопрос помечен как решенный

Ответы 11

Ответ принят как подходящий

Вы можете вызвать исключение, которое затем поймаете. 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 будет заполнен при возврате.

Обратите внимание, что там, где теперь написано «... хорошо, продолжайте и уничтожайте», вам нужно поставить «super», чтобы фактически вызвался исходный метод уничтожения.

Alexander Malfait 19.08.2009 19:13

errors.add_to_base устарела в Rails 3. Вместо этого вы должны сделать errors.add (: base, "message").

Ryan 06.01.2012 04:50

Rails не проверяет перед уничтожением, поэтому before_destroy должен вернуть false, чтобы отменить уничтожение. Просто добавлять ошибки бесполезно.

graywh 10.04.2012 21:50

С Rails 5 false в конце before_destroy бесполезен. С этого момента вы должны использовать throw(:abort) (@see: weblog.rubyonrails.org/2015/1/10/This-week-in-Rails/…).

romainsalles 07.03.2016 00:57

Ваш пример защиты от потерянных записей может быть решен намного проще с помощью has_many :booking_payments, dependent: :restrict_with_error.

thisismydesign 26.11.2019 13:24

Вы также можете использовать обратный вызов before_destroy, чтобы вызвать исключение.

Связи ActiveRecord has_many и has_one позволяют использовать зависимую опцию, которая будет обеспечивать удаление связанных строк таблицы при удалении, но обычно это делается для того, чтобы ваша база данных была чистой, а не для предотвращения ее недействительности.

Другой способ позаботиться о подчеркивании, если они являются частью имени функции или подобного, - заключить их в обратные кавычки. Тогда это будет отображаться как код like_so.

Richard Jones 01.02.2013 05:37

Спасибо. Ваш ответ привел меня к еще одному поиску варианта типы зависимых, на который ответили здесь: stackoverflow.com/a/25962390/3681793

bonafernando 12.06.2019 00:53

Существуют также параметры dependent, которые не позволяют удалить объект, если он создаст потерянные записи (это более актуально для вопроса). Например. dependent: :restrict_with_error

thisismydesign 26.11.2019 13:27

Вы можете заключить действие уничтожения в оператор «if» в контроллере:

def destroy # in controller context
  if (model.valid_destroy?)
    model.destroy # if in model context, use `super`
  end
end

Где valid_destroy? - это метод класса модели, который возвращает истину, если выполняются условия для уничтожения записи.

Наличие такого метода также позволит вам предотвратить отображение опции удаления для пользователя, что улучшит взаимодействие с пользователем, поскольку пользователь не сможет выполнить незаконную операцию.

хороший улов, но я предполагал, что этот метод находится в контроллере, полагаясь на модель. Если бы это было в модели, определенно вызвали бы проблемы

Toby Hede 17.03.2011 02:30

хе-хе, извините за это ... Я понимаю, что вы имеете в виду, я только что увидел "метод в вашем классе модели" и быстро подумал "ах, ах", но вы правы - уничтожьте на контроллере, это сработает. :)

jenjenut233 18.03.2011 00:42

все хорошо, на самом деле лучше быть предельно ясным, чем усложнять жизнь какого-то бедного новичка плохой ясностью

Toby Hede 18.03.2011 02:57

Я тоже думал о том, чтобы сделать это в Контроллере, но он действительно принадлежит Модели, поэтому объекты не могут быть уничтожены с консоли или любого другого Контроллера, которому может потребоваться уничтожить эти объекты. Держите это СУХОЕ. :)

Joshua Pinter 15.07.2018 05:58

При этом вы все равно можете использовать свой оператор if в действии destroy вашего контроллера, за исключением того, что вместо вызова if model.valid_destroy? просто вызовите if model.destroy и позвольте модели определить, было ли уничтожение успешным и т. д.

Joshua Pinter 15.07.2018 05:59

просто примечание:

Для рельсов 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

Проблема с этим подходом заключается в том, что обратный вызов before_destroy, кажется, называется после, все booking_payments были уничтожены.

sunkencity 18.03.2012 19:01

Связанный билет: github.com/rails/rails/issues/3458 @sunkencity, вы можете объявить before_destroy перед объявлением ассоциации, чтобы временно избежать этого.

lulalala 23.04.2013 10:11

Ваш пример защиты от потерянных записей может быть решен намного проще с помощью has_many :booking_payments, dependent: :restrict_with_error.

thisismydesign 26.11.2019 13:25

Согласно руководству rails, обратные вызовы before_destroy могут и должны быть размещены перед ассоциациями с зависимым_destroy; это вызывает обратный вызов до того, как будут вызваны связанные разрушения: guides.rubyonrails.org/…

grouchomc 26.08.2020 14:52

В итоге я использовал код отсюда, чтобы создать переопределение 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

Это имеет дополнительное преимущество, позволяющее упростить скрытие / отображение кнопки удаления в пользовательском интерфейсе.

У меня есть эти классы или модели

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

Теперь, когда вы удаляете предприятие, этот процесс проверяет, есть ли продукты, связанные с предприятиями. Примечание: вы должны написать это в верхней части класса, чтобы сначала проверить его.

Это то, что я сделал с 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

Это хорошая статья, объясняющая такое поведение в Rails 5: blog.bigbinary.com/2016/02/13/…

Yaro Holodiuk 07.09.2016 14:15

Ваш пример защиты от потерянных записей может быть решен намного проще с помощью has_many :qrcodes, dependent: :restrict_with_error.

thisismydesign 26.11.2019 13:25

Используйте проверку контекста 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, см. Эта проблема

thesecretmaster 19.07.2019 16:16

Я надеялся, что это будет поддерживаться, поэтому я открыл проблему с рельсами, чтобы добавить его:

https://github.com/rails/rails/issues/32376

Положение дел по состоянию на 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

Поскольку для отмены выполнения требуется Rails 5, throw(:abort): https://makandracards.com/makandra/20301-cancelling-the-activerecord-callback-chain

prepend: 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 вместо немедленного завершения процесса уничтожения.

Paul Odeon 17.11.2020 18:13

Другие вопросы по теме