[英]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建立在“多继承”的概念之上,包含模块,特别是为了实现这种敏捷性。
需要注意的是,必须在包含对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
模型类。
对于实例(如果您想稍后恢复验证),请尝试以下操作:
创建验证回调的副本(如果以后要恢复): my_dummy_validate_callbacks = my_dummy._validate_callbacks.clone
将实例上的验证回调设置为空: my_dummy._validate_callbacks = {}
在my_dummy
验证上免费做你想做的my_dummy
!
恢复回调: 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.