Как удалить валидацию с помощью предложения instance_eval в Rails? - PullRequest
36 голосов
/ 25 сентября 2011

Я хотел бы улучшить существующий класс, используя instance_eval. Там оригинальное определение содержит проверки, которые требуют наличия определенных полей, а именно:

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

Теперь я хочу изменить это на необязательное с помощью instance_eval (или любого другого метода, действительно):

Dummy.instance_eval do
  ...
end

Какой будет правильный синтаксис для удаления проверки, поэтому поле является необязательным. Я бы предпочел сделать это непосредственно на уровне модели, вместо этого делать странные хаки в контроллерах или представлениях. Использование instance_eval на самом деле не требуется, но, насколько я знаю, это, как правило, лучший способ улучшить классы в Rails.

Редактировать # 1

В целом - оригинальный класс является частью гема, и я не хочу его разветвлять или привязывать к конкретному выпуску. Общая причина не очень важна. Простое редактирование оригинальной модели имеет гораздо худшие последствия, чем исправление обезьян

Ответы [ 18 ]

28 голосов
/ 30 июня 2012

Я нашел решение, не уверен, насколько оно твердое, но в моем случае оно работает хорошо. @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 проверочных обратных вызовов.

11 голосов
/ 27 апреля 2015

Самый простой способ удалить все проверки:

clear_validators!
10 голосов
/ 17 ноября 2014

Я думаю, что это наиболее актуальное решение на данный момент (я использую рельсы 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
9 голосов
/ 31 января 2013

Поскольку я пытался сделать это, чтобы убрать проверку телефона из модели адреса Шпрее, ниже приведен код, который я получил для работы. Я добавил проверку типа для 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
4 голосов
/ 04 июня 2012

У меня была похожая проблема, и я смог ее обойти, используя:

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(...), но я не смог выяснить правильное заклинание аргументов, чтобы заставить это работать.

3 голосов
/ 10 января 2013

Ответ от 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 голосов
/ 25 апреля 2016

Для рельсов 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

Примечание: поскольку фильтр может быть символом ассоциации или определенным валидатором, поэтому мынужно использовать #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

Это ограничивает выполнение обратного вызова проверки для указанного классаи его потомки.

3 голосов
/ 05 октября 2011

Одним из решений является продление валидации:

#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

Одно предупреждение: вы должны загрузить класс с переопределенным методом validates перед гемом, содержащим вызов validates. Для этого требуется файл в 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

Надеюсь, это работает сейчас!

1 голос
/ 11 октября 2011

Почему бы не использовать метод @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 голос
/ 25 мая 2014

In Rails 4.1,

Мне удалось сделать _validate_callbacks.clear. В моем случае я хотел удалить все проверки для драгоценного камня, чтобы я мог создать свою собственную. Я сделал это в модуле, который был исправлен в классе.

Module #Name
   extend ActiveSupport::Concern
   included do
        _validate_callbacks.clear
        #add your own validations now
   end
end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...