web-dev-qa-db-ja.com

Railsのinstance_eval句を使用して検証を削除するにはどうすればよいですか?

Instance_evalを使用して既存のクラスを拡張したいと思います。元の定義には検証が含まれており、特定のフィールドの存在が必要です。

class Dummy < ActiveRecord::Base
  validates :field, :presence => true 
end

次に、instance_eval(または実際には他のメソッド)を使用して、これをオプションに変更します。

Dummy.instance_eval do
  ...
end

検証を削除するための適切な構文は何でしょうか。したがって、このフィールドはオプションです。コントローラーやビューで奇妙なハックを行う代わりに、モデルレイヤーで直接これを実行したいと思います。 instance_evalの使用は実際には必須ではありませんが、私が知る限り、これは一般にRailsのクラスを拡張するための最良の方法です。

編集#1

一般的に、元のクラスはgemの一部であり、フォークしたり、特定のリリースに結び付けたりしたくありません。一般的な原因はそれほど重要ではありません。元のモデルを編集するだけでは、モンキーパッチよりもはるかに悪い結果になります。

38
mdrozdziel

私は解決策を見つけました、それがどれほどしっかりしているかはわかりませんが、私の場合はうまくいきます。 @aVengerは実際に彼の答えに近かった。 _validatorsアクセサーには、リフレクションに使用される情報のみが含まれ、実際のバリデーターコールバックは含まれていません。これらは_validate_callbacksアクセサーに含まれており、_validations_callbacksと混同しないでください。

Dummy.class_eval do
  _validators.reject!{ |key, _| key == :field }

  _validate_callbacks.reject! do |callback|
    callback.raw_filter.attributes == [:field]
  end
end

これにより、:fieldのすべてのバリデーターが削除されます。より正確にしたい場合は、検証コールバックの_validatorsアクセサーと同じraw_filterの特定のバリデーターを拒否できます。

28
Nicolas Buduroi

これが現時点で最も実際的な解決策だと思います(私はRails 4.1.6)を使用しています:

# Common ninja
class Ninja < ActiveRecord::Base
  validates :name, :martial_art, presence: true
end

# Wow! He has no martial skills
Ninja.class_eval do
  _validators[:martial_art]
    .find { |v| v.is_a? ActiveRecord::Validations::PresenceValidator }
    .attributes
    .delete(:martial_art)
end
11
18augst

削除する最も簡単な方法all検証:

clear_validators!
11
tosh

スプリーアドレスモデルから電話の検証を削除するためにこれを実行しようとしていたので、以下は私が動作するようになったコードです。電話フィールドのプレゼンスバリデーターのみを削除したかったので、callback.raw_filterのタイプチェックを追加しました。また、callback.raw_filterの 'attributes'キーを持たないSpree :: Addressモデルで指定された他のバリデーターのいずれかに対して実行しようとすると失敗するため、これを追加する必要がありました。そのため、例外がスローされました。

Spree::Address.class_eval do

   # Remove the requirement on :phone being present.
  _validators.reject!{ |key, _| key == :phone }

  _validate_callbacks.each do |callback|
    callback.raw_filter.attributes.delete :phone if callback.raw_filter.is_a?(ActiveModel::Validations::PresenceValidator)
  end

end
9
Blake

私も同様の問題を抱えており、以下を使用してそれを乗り越えることができました。

_class MyModel << Dummy
  # erase the validations defined in the plugin/gem because they interfere with our own
  Dummy.reset_callbacks(:validate)
  ...
end
_

これはRails 3.0の下にあります。警告:すべての検証が削除されるため、保持したい他の検証がある場合はDummy.skip_callback(...)を試すことができますが、理解できませんでしたそれを機能させるための議論の正しい呪文。

4
Jeff Gran

1行に複数の属性の検証を宣言すると、aVengerによる回答に問題が発生します。

validates :name, :message, :presence => true

これは、この行が属性フィルターに複数の属性を持つraw_filterを作成するためです。

Model.send(:_validate_callbacks)
=> [#<ActiveSupport::Callbacks::Callback:0xa350da4 @klass=Model(...), ... , @raw_filter=#<ActiveModel::Validations::PresenceValidator:0x9da7470 @attributes=[:name, :message], @options={}>, @filter="_callback_before_75", @compiled_options="true", @callback_id=76>]

その配列から目的の属性を削除し、属性のないコールバックを拒否する必要があります

Dummy.class_eval do

  _validators.reject!{ |key, _| key == :field }

  _validate_callbacks.each do |callback|
    callback.raw_filter.attributes.delete :field
  end

  _validate_callbacks.reject! do |callback|
    callback.raw_filter.attributes.empty? || 
      callback.raw_filter.attributes == [:field]
  end
end

私はこれをRails 3.2.11アプリで動作させています。

3
Brenes

1つの解決策は、検証を拡張することです。

#no need of instance_eval just open the class
class Dummy < ActiveRecord::Base
  #validates :field, :presence => true 

  def self.validates(*attributes)
    if attributes.first == :field #=> add condition on option if necessary
      return # don't validate
    else
      super(*attributes) #let normal behavior take over
    end
  end
end

いいえ、それはモンキーパッチではなく、動作を拡張または装飾することです。 Rails 3.1は、特にこの種の敏捷性を可能にするために、モジュールを含む「多重継承」の概念に基づいて構築されています。

アップデート#2

注意点の1つは、再定義されたvalidatesメソッドを使用してクラスをロードする必要があることですbefore validatesの呼び出しを含むgem。これを行うには、 railsguides で提案されているように、require "Rails/all"の後にconfig/application.rb内のファイルを要求します。そんな感じ :

require File.expand_path('../boot', __FILE__)

require 'Rails/all' # this where Rails (including active_record) is loaded
require File.expand_path('../dummy' __FILE__) #or wherever you want it

#this is where the gems are loaded...
# the most important is that active_record is loaded before dummy but...
# not after the gem containing the call to validate :field
if defined?(Bundler)
  Bundler.require *Rails.groups(:assets => %w(development test))
end

それが今うまくいくことを願っています!

3
charlysisto

Rails 4.2(〜5.0)の場合、次のモジュールをメソッドとともに使用できます。

module ValidationCancel
   def cancel_validates *attributes
      attributes.select {|v| Symbol === v }.each do |attr|
         self._validators.delete( attr )

         self._validate_callbacks.select do |callback|
            callback.raw_filter.try( :attributes ) == [ attr ] ;end
         .each do |vc|
            self._validate_callbacks.delete( vc ) ;end ;end ;end ;end

注:filternは関連付けのシンボル、または特定のバリデーターである可能性があるため、#tryを使用する必要があります。

次に、クラス宣言でRails対応のフォームを使用できます。

class Dummy
   extend ValidationCancel
   cancel_validates :field ;end

注:バリデーターの削除はクラス全体とその子孫にグローバルに影響するため、このような方法でバリデーターを削除するために使用することはお勧めしません。代わりに、次のように特定のルールにif句を追加します。

module ValidationCancel
   def cancel_validates *attributes
      this = self
      attributes.select {|v| Symbol === v }.each do |attr|
         self._validate_callbacks.select do |callback|
            callback.raw_filter.try( :attributes ) == [ attr ] ;end
         .each do |vc|
            ifs = vc.instance_variable_get( :@if )
            ifs << proc { ! self.is_a?( this ) } ;end ;end ;end ;end

これにより、指定されたクラスとその子孫の検証コールバックの実行が制限されます。

追加したいのですが、モデルの検証インスタンス上モデルクラス全体ではない)をクリアしようとしている場合は、my_dummy._validate_callbacks.clearを実行しないでください。 、それにより、Dummyモデルクラスのすべてのインスタンス(および将来のインスタンス)の検証がクリアされます。

インスタンスのみの場合(および後で検証を元に戻したい場合)、次のことを試してください。

  1. validateコールバックのコピーを作成します(後で元に戻す場合):my_dummy_validate_callbacks = my_dummy._validate_callbacks.clone

  2. インスタンスvalidate callbacksを空に設定します:my_dummy._validate_callbacks = {}

  3. my_dummy検証無料でやりたいことをしてください!

  4. コールバックを元に戻します:my_dummy._validate_callbacks = my_dummy_validate_callbacks

1
DexCurl

元のモデルの制約を編集して:if =>:some_functionを設定できる場合は、呼び出す関数の動作を簡単に変更してfalseを返すことができます。私はこれをテストしました、そしてそれはかなり簡単に働きます:

class Foo < ActiveRecord::Base

  validates :field, :presence => true, :if => :stuff


  attr_accessor :field

  def stuff
      return true;
  end

end

そしてどこか別の場所:

Foo.class_eval {
  def stuff
    false
  end
}
1
xaxxon

親クラスに変更を加えたくない場合は、最初に子クラスのすべての検証をクリアし、必要なすべての検証を親クラスから子クラスにコピーします

  class Dummy < ActiveRecord::Base
      validates :property, presence: true
      validates :value, length: { maximum: 255 }
  end

そして子クラスでそれをオーバーライドします

Dummy.class_eval do    
  clear_validators!
  validates :property, presence: true
end
1
Arvind singh

Rails 4.1、

_validate_callbacks.clearを実行できました。私の場合、gemのすべての検証を削除して、独自の検証を作成できるようにしました。クラスにパッチされたモジュールでこれを行いました。

Module #Name
   extend ActiveSupport::Concern
   included do
        _validate_callbacks.clear
        #add your own validations now
   end
end
1
psulightning

本当にこれを実行したい場合は、ここで掘り始めるのが良いでしょう: https://github.com/Rails/rails/blob/ed7614aa7de2eaeba16c9af11cf09b4fd7ed6819/activemodel/lib/active_model/validations/validates.rb# L82

ただし、正直なところ、ActiveModelの内部は、私が棒で突っついている場所ではありません。

1
thomasfedb

@dummy.save_without_validationメソッドを使用して検証を完全にスキップしてみませんか?私はこのようなことを好む:

if @dummy.valid?
  @dummy.save # no problem saving a valid record
else
  if @dummy.errors.size == 1 and @dummy.errors.on(:field)
    # skip validations b/c we have exactly one error and it is the validation that we want to skip
    @dummy.save_without_validation 
  end
end

必要に応じて、このコードをモデルまたはコントローラーに配置できます。

1
Teddy

コードとヘルプを詳しく調べる必要がありますが、クラスのバリデーターのリストを調べてから、変更する検証のエントリを変更して:if => :some_function条件付きで追加できる可能性があります。それに。

本番環境では1回だけ実行する必要があります(初期化子内に配置できますが、開発用では、モデル、または対応するモデルがロードされるたびに読み込まれる別の場所に配置する必要があります(おそらくオブザーバー?)。

(私はそれを研究するようになったときに、より多くの情報で答えを編集します。)

0
Daemin

すべてのRailsバリデーター、事前定義またはカスタム)はオブジェクトであり、#validate(record)メソッドに応答することが期待されます。このメソッドにモンキーパッチまたはスタブを適用できます。

# MyModel.validators_on(:attr1, :attr2, ...) is also useful
validator = MyModel.validators.detect do |v|
  validator_i_am_looking_for?(v)
end

def validator.validate(*_)
  true
end

# In RSpec you can also consider:
allow(validator).to receive(:validate).and_return(true)

Rails 5.1でテスト済み。

あなたがしていることを理解していない限り、これをしないでください;)

0
skalee

これは質問に直接答えるものではありませんが、そのような状況で考慮すべきオプションがあります。検証を無効にする代わりに、before_validationフックに必須フィールドを設定できます。

これらの必須フィールドは必要ないため、検証を満たすダミーデータを設定し、忘れてください。

醜いモンキーパッチはありません。

0
fkoessler