Railsでの破棄を「検証」するにはどうすればよいですか?


81

Restfulリソースの破棄時に、破棄操作の続行を許可する前に、いくつかのことを保証したいですか?基本的に、データベースが無効な状態になることに気付いた場合、破棄操作を停止する機能が必要ですか?破棄操作には検証コールバックがないので、破棄操作を受け入れる必要があるかどうかをどのように「検証」するのでしょうか。


回答:


70

例外を発生させて、それをキャッチすることができます。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これでfalsemyBooking.errorsが返され、戻り時に入力されます。


3
「...わかりました、先に進んで破棄してください」と表示されている場合は、「スーパー」を入力する必要があるため、元のdestroyメソッドが実際に呼び出されることに注意してください。
Alexander Malfait 2009

3
errors.add_to_baseはRails3で非推奨になりました。代わりにerrors.add(:base、 "message")を実行する必要があります。
ライアン

9
Railsは破棄する前に検証しないため、破棄をキャンセルするには、before_destroyがfalseを返す必要があります。エラーを追加するだけでは意味がありません。
greywh 2012

24
Rails 5では、falseの最後にあるbefore_destroyは役に立ちません。これからはthrow(:abort)(@see:weblog.rubyonrails.org/2015/1/10/This-week-in-Rails/…)を使用する必要があります。
romainsalles 2016年

1
孤立したレコードに対する防御のあなたの例では、経由してはるかに容易に解決することができますhas_many :booking_payments, dependent: :restrict_with_error
thisismydesign

48

注:

レール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
このアプローチの問題は、すべてのbooking_paymentsが破棄された後にbefore_destroyコールバックが呼び出されように見えることです。
サンケンシティ2012年

4
関連チケット:github.com/rails/rails/issues/3458 @sunkencityこれを一時的に回避するために、アソシエーション宣言の前にbefore_destroyを宣言できます。
lulalala 2013

1
孤立したレコードに対する防御のあなたの例では、経由してはるかに容易に解決することができますhas_many :booking_payments, dependent: :restrict_with_error
thisismydesign

レールガイドによると、before_destroyコールバックは、dependent_destroyとの関連付けの前に配置できます。これにより、関連する破棄が呼び出される前にコールバックがトリガーされます。guides.rubyonrails.org
…–grouchomc20年

20

それは私がRails5でしたことです:

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

3
これはRailsの5で、この動作を説明する素晴らしい記事です:blog.bigbinary.com/2016/02/13/...
やろHolodiuk

1
孤立したレコードに対する防御のあなたの例では、経由してはるかに容易に解決することができますhas_many :qrcodes, dependent: :restrict_with_error
thisismydesign

6

ActiveRecordアソシエーションhas_manyとhas_oneは、関連するテーブル行が削除時に削除されることを確認する依存オプションを許可しますが、これは通常、データベースが無効になるのを防ぐのではなく、データベースをクリーンに保つためです。


1
アンダースコアが関数名などの一部である場合、アンダースコアを処理するもう1つの方法は、アンダースコアをバッククォートで囲むことです。その後、コードとして表示されますlike_so
リチャードジョーンズ

ありがとうございました。あなたの答えは、およそ別の検索に私を導いた依存の種類、ここで答えたオプション:stackoverflow.com/a/25962390/3681793
bonafernando

dependent孤立したレコードが作成される場合にエンティティの削除を許可しないオプションもあります(これは質問により関連性があります)。例えばdependent: :restrict_with_error
thisismydesign

5

コントローラの「if」ステートメントで破棄アクションをラップできます。

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

valid_destroyはどこですか?レコードを破棄するための条件が満たされた場合にtrueを返すモデルクラスのメソッドです。

このような方法を使用すると、ユーザーに削除オプションが表示されないようにすることもできます。これにより、ユーザーが不正な操作を実行できなくなるため、ユーザーエクスペリエンスが向上します。


1
良いキャッチですが、私はこのメソッドがコントローラーにあり、モデルに依存していると想定していました。それがモデルにあった場合、間違いなく問題が発生します
Toby Hede 2011年

ふふ、申し訳ありませんが...意味がわかります。「モデルクラスのメソッド」を見て、すぐに「ええと」と思いましたが、その通りです。コントローラーで破棄すれば、問題なく動作します。:)
jenjenut233 2011年

すべて良い、実際には、いくつかの貧しい初心者の生活を明確でなく困難にするよりも、非常に明確にする方が良い
Toby Hede 2011年

1
コントローラーでも実行することを考えましたが、実際にはモデルに属しているため、コンソールや、それらのオブジェクトを破棄する必要のある他のコントローラーからオブジェクトを破棄することはできません。乾いた状態に保ちます。:)
Joshua Pinter 2018

ことでは、あなたはまだ使用することができ、言ったifでステートメントをdestroy代わりに呼び出すのを除いて、あなたのコントローラのアクションif model.valid_destroy?だけで呼び出す、if model.destroyなど、成功したかどうかを破壊するモデルハンドルとlet
ジョシュア・ピンター

5

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)実行をキャンセルするにはRails5が必要なので、https//makandracards.com/makandra/20301-cancelling-the-activerecord-callback-chain

prepend: truedependent: :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の検証からのエラーが終わるのではなく通過することができます
ポール・オデオン

4

私はここからコードを使用してactiverecordにcan_destroyオーバーライドを作成することになりました: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の削除ボタンを表示/非表示にするのが簡単になるという追加の利点があります



2

私はこれらのクラスまたはモデルを持っています

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

これで、エンタープライズを削除すると、このプロセスはエンタープライズに関連付けられた製品があるかどうかを検証します。注:最初に検証するには、クラスの先頭にこれを書き込む必要があります。


1

Rails5でActiveRecordコンテキスト検証を使用します。

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.