web-dev-qa-db-ja.com

railsでdestroyで「検証」する方法

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

72
Stephen Cagle

例外を発生させてからキャッチできます。 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は、戻り時に設定されます。

63
Airsource Ltd

ただのメモ:

Rails 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
47
workdreamer

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
18

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

6
go minimal

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

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

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

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

5
Toby Hede

最終的に、ここからのコードを使用して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で削除ボタンを非表示/表示するのが簡単になるという追加の利点があります

4
Hugo Forte

これらのクラスまたはモデルがあります

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

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

3
Mateo Vidal

Before_destroyコールバックを使用して例外を発生させることもできます。

3
MattW.

Rails 5.で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
2
swordray

これがサポートされることを望んでいたので、Railsの問題を追加して追加しました:

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

1
ragurney