Как удалить проверку с помощью экземпляра instance_eval в Rails?
Я хотел бы расширить существующий класс, используя instance_eval. Там исходное определение содержит проверку, которая требует наличия определенных полей, т.е.
class Dummy < ActiveRecord::Base
validates :field, :presence => true
end
Теперь я хочу изменить это на необязательный, используя instance_eval (или любой другой метод, действительно):
Dummy.instance_eval do
...
end
Каким будет правильный синтаксис для удаления проверки, поэтому поле является необязательным. Я предпочел бы сделать это непосредственно на уровне модели, вместо этого делаю странные хаки в контроллерах или представлениях. Использование instance_eval действительно не требуется, но, насколько я знаю, это, как правило, лучший способ повысить класс в Rails.
Изменить # 1
В целом - оригинальный класс является частью драгоценного камня, и я не хочу его разветвлять и не привязывать к определенному выпуску. Общая причина не очень важна. Простое редактирование исходной модели имеет гораздо худшие последствия, чем исправление обезьян.
Ответы
Ответ 1
Я нашел решение, не уверен, насколько он твердый, но он хорошо работает в моем случае. @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
для проверки обратных вызовов.
Ответ 2
Самый простой способ удалить все проверки:
clear_validators!
Ответ 3
Я думаю, что это самое актуальное решение в данный момент (я использую рельсы 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
Ответ 4
Как я пытался сделать это, чтобы удалить проверку телефона из модели шаблона spree, ниже приведен код, который я должен работать. Я добавил проверку типа для callback.raw_filter, потому что я только хотел удалить валидатор присутствия в поле телефона. Я также должен был добавить его, потому что он не сработает при попытке выполнить работу с одним из других валидаторов, указанным в модели Spree:: Address, у которой не было ключа "атрибутов" для callback.raw_filter, поэтому было исключено исключение.
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
Ответ 5
У меня была аналогичная проблема, и я смог пройти мимо нее, используя:
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(...)
, но я не мог понять правильное заклинание аргументов, чтобы сделать эту работу.
Ответ 6
Одним из решений является расширение проверки:
#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
Одно предупреждение - вы должны загрузить класс с переопределенным методом проверки перед камнем, содержащим вызов для проверки. Для этого требуется файл в config/application.rb после запроса "rails/all", как предлагается в railsguides. Что-то вроде этого:
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
Надеюсь, что сейчас это работает!
Ответ 7
Ответа на этот вопрос aVenger есть проблемы, когда вы объявляете проверки более чем одного атрибута в строке:
validates :name, :message, :presence => true
Это потому, что эта строка создает raw_filter с несколькими атрибутами в атрибуте 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.
Ответ 8
Для рельсов 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
.
Затем мы можем использовать рельсовую форму в объявлении класса:
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
Это ограничивает выполнение обратного вызова проверки для указанного класса и его потомков.
Ответ 9
Если вы действительно хотите это сделать, здесь было бы неплохо начать копать: https://github.com/rails/rails/blob/ed7614aa7de2eaeba16c9af11cf09b4fd7ed6819/activemodel/lib/active_model/validations/validates.rb#L82
Однако, если честно, внутри ActiveModel нет места, где я буду тыкать палкой.
Ответ 10
Почему бы не использовать метод @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
Вы можете поместить этот код в свою модель или в контроллер, в зависимости от ваших потребностей.
Ответ 11
В Rails 4.1,
Я смог сделать _validate_callbacks.clear. В моем случае я хотел, чтобы все проверки на удаленный камень были удалены, поэтому я мог бы создать свой собственный. Я сделал это в модуле, который был исправлен в классе.
Module #Name
extend ActiveSupport::Concern
included do
_validate_callbacks.clear
#add your own validations now
end
end
Ответ 12
Если вы не хотите вносить изменения в родительский класс, сначала очистите все проверки в дочернем классе и скопируйте все необходимые проверки с родительского класса на дочерний класс
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
Ответ 13
Хотелось добавить, что если вы пытаетесь очистить проверки от экземпляра вашей модели (не весь класс модели), не делайте my_dummy._validate_callbacks.clear
, поскольку это очистит проверки на каждого экземпляра (и будущего экземпляра) вашего класса модели Dummy
.
Только для экземпляра (и если вы хотите восстановить проверки позже), попробуйте следующее:
-
Создайте копию проверочных обратных вызовов (если вы хотите восстановить их позже):
my_dummy_validate_callbacks = my_dummy._validate_callbacks.clone
-
Установите для проверки правильности обратных вызовов на экземпляре:
my_dummy._validate_callbacks = {}
-
Сделайте то, что вы хотите на my_dummy
без проверки!
-
Восстановите обратные вызовы: my_dummy._validate_callbacks = my_dummy_validate_callbacks
Ответ 14
Если вы можете отредактировать ограничение на исходную модель, чтобы поместить в нее: 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
}
Ответ 15
Мне нужно больше смотреть в код и помогать, но я не могу проверить список валидаторов класса, а затем изменить запись для проверки, которую вы хотите изменить, чтобы добавить a :if => :some_function
.
Вам нужно сделать это только один раз для производства (чтобы его можно было помещать в инициализатор, но для разработки вам нужно поместить его в модель или где-то еще, что будет загружаться каждый раз, когда соответствующая модель (возможно, наблюдатель?).
(Я отредактирую ответ с дополнительной информацией, когда приеду, чтобы исследовать его.)
Ответ 16
Каждый валидатор 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.
Не делайте этого, пока не поймете, что делаете;)
Ответ 17
Это не дает прямого ответа на вопрос, но здесь есть вариант, который вы должны рассмотреть в такой ситуации: вместо отключения проверки вы можете установить обязательные поля в before_validation
.
Поскольку вам не нужны эти обязательные поля, установите для них фиктивные данные, удовлетворяющие валидации, и забудьте о них.
Никаких уродливых обезьян.
Ответ 18
Предполагая, что исходная реализация Dummy определена в движке, есть неприятный хак, который будет делать то, что вы хотите. Определите "Dummy" в вашем приложении, чтобы исходная реализация Dummy не загружалась автоматически. Затем загрузите источник в Dummy и удалите строку, которая выполняет проверку. Eval измененный источник.
Добавьте в приложение /models/dummy.rb следующее:
class Dummy < ActiveRecord::Base
end
# Replace DummyPlugin with name of engine
engine = Rails::Application::Railties.engines.find { |e| e.class == DummyPlugin::Engine }
dummy_source = File.read File.join(engine.config.root, "app", "models", "dummy.rb")
dummy_source = dummy_source.gsub(/validates :field, :presence => true.*/, "")
eval dummy_source
Если это обычный жемчужина вместо двигателя, то будет применяться одна и та же концепция, просто нужно будет загрузить источник для Dummy из корня gem вместо корня ядра.