Instance_evalを使用して既存のクラスを拡張したいと思います。元の定義には検証が含まれており、特定のフィールドの存在が必要です。
class Dummy < ActiveRecord::Base
validates :field, :presence => true
end
次に、instance_eval(または実際には他のメソッド)を使用して、これをオプションに変更します。
Dummy.instance_eval do
...
end
検証を削除するための適切な構文は何でしょうか。したがって、このフィールドはオプションです。コントローラーやビューで奇妙なハックを行う代わりに、モデルレイヤーで直接これを実行したいと思います。 instance_evalの使用は実際には必須ではありませんが、私が知る限り、これは一般にRailsのクラスを拡張するための最良の方法です。
編集#1
一般的に、元のクラスはgemの一部であり、フォークしたり、特定のリリースに結び付けたりしたくありません。一般的な原因はそれほど重要ではありません。元のモデルを編集するだけでは、モンキーパッチよりもはるかに悪い結果になります。
私は解決策を見つけました、それがどれほどしっかりしているかはわかりませんが、私の場合はうまくいきます。 @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
の特定のバリデーターを拒否できます。
これが現時点で最も実際的な解決策だと思います(私は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
削除する最も簡単な方法all検証:
clear_validators!
スプリーアドレスモデルから電話の検証を削除するためにこれを実行しようとしていたので、以下は私が動作するようになったコードです。電話フィールドのプレゼンスバリデーターのみを削除したかったので、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
私も同様の問題を抱えており、以下を使用してそれを乗り越えることができました。
_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(...)
を試すことができますが、理解できませんでしたそれを機能させるための議論の正しい呪文。
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アプリで動作させています。
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は、特にこの種の敏捷性を可能にするために、モジュールを含む「多重継承」の概念に基づいて構築されています。
注意点の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
それが今うまくいくことを願っています!
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
モデルクラスのすべてのインスタンス(および将来のインスタンス)の検証がクリアされます。
インスタンスのみの場合(および後で検証を元に戻したい場合)、次のことを試してください。
validateコールバックのコピーを作成します(後で元に戻す場合):my_dummy_validate_callbacks = my_dummy._validate_callbacks.clone
インスタンスのvalidate callbacksを空に設定します:my_dummy._validate_callbacks = {}
my_dummy
検証無料でやりたいことをしてください!
コールバックを元に戻します:my_dummy._validate_callbacks = my_dummy_validate_callbacks
元のモデルの制約を編集して: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
}
親クラスに変更を加えたくない場合は、最初に子クラスのすべての検証をクリアし、必要なすべての検証を親クラスから子クラスにコピーします
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
Rails 4.1、
_validate_callbacks.clearを実行できました。私の場合、gemのすべての検証を削除して、独自の検証を作成できるようにしました。クラスにパッチされたモジュールでこれを行いました。
Module #Name
extend ActiveSupport::Concern
included do
_validate_callbacks.clear
#add your own validations now
end
end
本当にこれを実行したい場合は、ここで掘り始めるのが良いでしょう: https://github.com/Rails/rails/blob/ed7614aa7de2eaeba16c9af11cf09b4fd7ed6819/activemodel/lib/active_model/validations/validates.rb# L82
ただし、正直なところ、ActiveModelの内部は、私が棒で突っついている場所ではありません。
@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
必要に応じて、このコードをモデルまたはコントローラーに配置できます。
コードとヘルプを詳しく調べる必要がありますが、クラスのバリデーターのリストを調べてから、変更する検証のエントリを変更して:if => :some_function
条件付きで追加できる可能性があります。それに。
本番環境では1回だけ実行する必要があります(初期化子内に配置できますが、開発用では、モデル、または対応するモデルがロードされるたびに読み込まれる別の場所に配置する必要があります(おそらくオブザーバー?)。
(私はそれを研究するようになったときに、より多くの情報で答えを編集します。)
すべての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でテスト済み。
あなたがしていることを理解していない限り、これをしないでください;)
これは質問に直接答えるものではありませんが、そのような状況で考慮すべきオプションがあります。検証を無効にする代わりに、before_validation
フックに必須フィールドを設定できます。
これらの必須フィールドは必要ないため、検証を満たすダミーデータを設定し、忘れてください。
醜いモンキーパッチはありません。