Как «проверять» на уничтожение в рельсах


65

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

  0

Связанный: http://stackoverflow.com/questions/5520320/validate-before-destroy 08 ноя. 112011-11-08 16:31:22

55

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

Например:

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 теперь будет возвращать ложь, и myBooking.errors будет заполняться по возвращении.

  0

Гм, что случилось с просто проверить ассоциацию booking_payments, которая должна быть определена на этой модели вместо вызова BookingPayment .count, приводящий к уродливому коду? 24 сен. 082008-09-24 00:37:25

  0

Я отредактировал свой ответ соответственно. Извините, я вытащил это из какого-то старого кода ... 24 сен. 082008-09-24 18:26:27

+3

Обратите внимание, что там, где теперь говорится «... ОК, идите вперед и уничтожьте», вам нужно поставить «супер», поэтому на самом деле вызывается метод первоначального уничтожения. 19 авг. 092009-08-19 15:13:02

+23

Я не думаю, что метод обратного вызова работает больше. 17 июн. 102010-06-17 06:58:37

+3

errors.add_to_base устарел в Rails 3. Вместо этого вы должны делать errors.add (: base, "message"). 06 янв. 122012-01-06 00:50:30

+8

Rails не проверяет перед уничтожением, поэтому before_destroy должен будет вернуть false для его отмены уничтожения. Просто добавление ошибок бесполезно. 10 апр. 122012-04-10 17:50:11

  0

Обновлен код для отражения комментариев, но не был зарегистрирован. Теперь изменения исчезли и кнопка редактирования тоже. Не знаю, пробилось ли оно. 13 авг. 142014-08-13 09:15:46

+15

С Rails 5, 'false' в конце' before_destroy' бесполезен. С этого момента вы должны использовать 'throw (: abort)' (@see: http://weblog.rubyonrails.org/2015/1/10/This-week-in-Rails/#halting-callback-chains-by- метательное-aborthttpsgithubcomrailsrailspull17227). 06 мар. 162016-03-06 21:57:50


3

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


5

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

+1

+1 для «\ _» не понимал, что это вариант на SO 25 сен. 092009-09-25 18:47:32

+1

. Другой способ позаботиться о символах подчеркивания, если они являются частью имени функции или аналогичного, заключается в том, чтобы обернуть их в обратные ссылки. Это будет отображаться тогда как код 'like_so'. 01 фев. 132013-02-01 01:37:07


5

Вы можете обернуть разрушающего действия в "если" заявление в контроллере:

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

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

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

+6

Бесконечная петля, кто-нибудь? 16 мар. 112011-03-16 20:19:02

+1

хороший улов, но я предполагал, что этот метод находится в контроллере, откладывая модель. Если бы это было в модели, это определенно вызовет проблемы 16 мар. 112011-03-16 23:30:31

  0

хе-хе, извините, что ... Я понимаю, что вы имеете в виду, я только что увидел «метод на вашем классе модели» и быстро подумал «о, о», но вы правы - уничтожьте на контроллере это будет работать нормально. :) 17 мар. 112011-03-17 21:42:50

  0

все хорошо, на самом деле лучше быть очень ясным, а не затруднять жизнь бедного новичка с плохой ясностью. 17 мар. 112011-03-17 23:57:10


46

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

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

Проблема с этим подходом заключается в том, что обратный вызов before_destroy, по-видимому, называется * после того, как все блокировки были уничтожены. 18 мар. 122012-03-18 15:01:52

+4

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


4

Я закончил с использованием кода здесь, чтобы создать 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 

Это Добавленная преимущество сделать тривиальным, чтобы скрыть/показать кнопку удаления на ui


3

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

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 

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


13

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

Это хорошая статья, объясняющая это поведение в Rails 5: http://blog.bigbinary.com/2016/02/13/rails-5-does-not-halt-callback-chain-when-false-is- returned.html 07 сен. 162016-09-07 11:15:31


1

Использование 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