Подтвердить что ты не робот

Как удалить проверку с помощью экземпляра 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

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

4b9b3361

Ответ 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

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

Ответ 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 вместо корня ядра.