繁体   English   中英

如何使用Rails中的instance_eval子句删除验证?

[英]How to remove validation using instance_eval clause in Rails?

我想使用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访问器相同。

最简单的方法来删除所有验证:

clear_validators!

我认为这是目前最实际的解决方案(我使用的是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

当我试图这样做以从狂欢地址模型中删除电话验证时,下面是我开始工作的代码。 我添加了callback.raw_filter的类型检查,因为我只想删除电话字段上的状态验证器。 我还必须添加它,因为它在尝试针对Spree :: Address模型中指定的其他验证器运行时会失败,该函数没有callback.raw_filter的'attributes'键,因此抛出异常。

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(...) ,但是我无法弄清楚正确的参数咒语以使其发挥作用。

当您在一行中声明多个属性的验证时,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应用程序上工作。

对于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-friendly表单:

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

这限制了对指定类及其后代的验证回调的执行。

一种解决方案是扩展验证:

#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

需要注意的是,必须包含对validate的调用的gem 之前使用重新定义的validates方法加载类。 为此,在railsguides中建议使用“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.1中,

我能够做_validate_callbacks.clear。 在我的情况下,我想删除所有宝石的验证,所以我可以创建自己的。 我在一个修补到课程中的模块中完成了这个。

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

如果您不想在Parent类中进行任何更改,请首先清除子类中的所有验证,并将所有必需的验证从父类复制到子类

  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

想要补充一点,如果你试图清除模型实例而不是整个模型类 )的验证,不要执行my_dummy._validate_callbacks.clear ,因为这将清除每个实例(以及将来的实例)的验证您的Dummy模型类。

对于实例(如果您想稍后恢复验证),请尝试以下操作:

  1. 创建验证回调的副本(如果以后要恢复): my_dummy_validate_callbacks = my_dummy._validate_callbacks.clone

  2. 实例上的验证回调设置为空: my_dummy._validate_callbacks = {}

  3. my_dummy验证上免费做你想做的my_dummy

  4. 恢复回调: my_dummy._validate_callbacks = my_dummy_validate_callbacks

如果你真的想这样做,那么这里将是一个开始挖掘的好地方: 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

您可以根据需要将此代码放入模型或控制器中。

每个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钩子中设置必需的字段,而不是禁用验证。

由于您不需要这些必填字段,因此请使用满足验证的一些虚拟数据进行设置,并将其遗忘。

没有丑陋的猴子修补。

如果您可以在原始模型上编辑约束以在其上放置: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
}

我需要更多地查看代码和帮助,但是我正在考虑可能检查类的验证器列表,然后修改要更改的验证条目以添加:if => :some_function条件:if => :some_function

你只需要为生产做一次(所以它可以放在初始化器中,但是对于开发,你需要将它放在模型中,或者每次相应模型都会加载的其他地方(也许)一个观察者?)。

(当我来研究它时,我将用更多信息编辑答案。)

假设Dummy的原始实现是在引擎中定义的,那么有一个讨厌的hack会做你想要的。 在应用程序中定义Dummy,以防止Dummy的原始实现被自动加载。 然后将源加载到Dummy并删除执行验证的行。 评估修改后的源代码。

将以下内容放入app / 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

如果它是常规gem而不是引擎,则应用相同的概念,只需要从gem根而不是引擎根加载Dummy的源。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM