删除或覆盖由超类或 mixin 添加的 ActiveRecord 验证
-
22-09-2019 - |
题
我在 Rails 应用程序中使用 Clearance 进行身份验证。这 Clearance::User
mixin 为我的添加了一些验证 User
模型,但我想删除或覆盖其中一个。这样做的最佳方法是什么?
有问题的验证是
validates_uniqueness_of :email, :case_sensitive => false
这本身并不坏,但我需要添加 :scope => :account_id
. 。问题是,如果我将其添加到我的 User
模型
validates_uniqueness_of :email, :scope => :account_id
我明白了 两个都 验证,并且 Clearance 添加的验证比我的更严格,所以我的没有任何效果。我需要确保只有我的运行。我该怎么做呢?
解决方案 2
我最终通过以下技巧“解决”了这个问题:
- 查找错误
:email
类型属性:taken
- 检查电子邮件对于该帐户是否是唯一的(这是我想做的验证)
- 如果该电子邮件对于该帐户是唯一的,请删除错误。
听起来很合理,直到您阅读代码并发现我如何消除错误为止。 ActiveRecord::Errors
一旦添加就没有方法消除错误,所以我必须抓住它的内部结构并自己做。超级丑陋。
这是代码:
def validate
super
remove_spurious_email_taken_error!(errors)
end
def remove_spurious_email_taken_error!(errors)
errors.each_error do |attribute, error|
if error.attribute == :email && error.type == :taken && email_unique_for_account?
errors_hash = errors.instance_variable_get(:@errors)
if Array == errors_hash[attribute] && errors_hash[attribute].size > 1
errors_hash[attribute].delete_at(errors_hash[attribute].index(error))
else
errors_hash.delete(attribute)
end
end
end
end
def email_unique_for_account?
match = account.users.find_by_email(email)
match.nil? or match == self
end
如果有人知道更好的方法,我将非常感激。
其他提示
我叉的GEM并添加一个简单的检查,然后可以被覆盖。我的示例使用了关注。
关注:
module Slugify
extend ActiveSupport::Concern
included do
validates :slug, uniqueness: true, unless: :skip_uniqueness?
end
protected
def skip_uniqueness?
false
end
end
型号:
class Category < ActiveRecord::Base
include Slugify
belongs_to :section
validates :slug, uniqueness: { scope: :section_id }
protected
def skip_uniqueness?
true
end
end
最近我有这个问题,此前谷歌没有给我答案速度不够快,我发现了一个更简洁,但仍对这个问题非理想的解决方案。现在,这不会在你的情况一定的工作,因为它似乎使用预先存在的超类,但对我来说这是我自己的代码,所以我只是用一个:如果param用在超类的类型检查
def SuperClass
validates_such_and_such_of :attr, :options => :whatever, :if => Proc.new{|obj| !(obj.is_a? SubClass)}
end
def SubClass < SuperClass
validates_such_and_such_of :attr
end
在multpile子类的情况下
def SuperClass
validates_such_and_such_of :attr, :options => :whatever, :if => Proc.new{|obj| [SubClass1, SubClass2].select{|sub| obj.is_a? sub}.empty?}
end
def SubClass1 < SuperClass
validates_such_and_such_of :attr
end
def SubClass2 < SuperClass
end
我需要除去施普雷产品属性:value
验证和它似乎有一个与Klass.class_eval
和clear_validators!
的AciveRecord::Base
一个simplier溶液
module Spree
class ProductProperty < Spree::Base
#spree logic
validates :property, presence: true
validates :value, length: { maximum: 255 }
#spree logic
end
end
和这里重写它
Spree::ProductProperty.class_eval do
clear_validators!
validates :property, presence: true
end
我知道我迟到了比赛,但如何:
module Clearance
module User
module Validations
extend ActiveSupport::Concern
included do
validates :email,
email: true,
presence: true,
uniqueness: { scope: :account, allow_blank: true },
unless: :email_optional?
validates :password, presence: true, unless: :password_optional?
end
end
end
end
在一个初始化?
Errors.delete(键)删除所有误差的一个属性,我只希望删除错误的属于一个属性的特定类型。这个下面的方法可以被添加到任何模型。
如果返回消息中去除,否则无。内部数据结构被修改,以便所有其他方法应该误差去除后正常工作。
的方法来从模型后验证已运行去除错误。
def remove_error!(attribute, message = :invalid, options = {})
# -- Same code as private method ActiveModel::Errors.normalize_message(attribute, message, options).
callbacks_options = [:if, :unless, :on, :allow_nil, :allow_blank, :strict]
case message
when Symbol
message = self.errors.generate_message(attribute, message, options.except(*callbacks_options))
when Proc
message = message.call
else
message = message
end
# -- end block
# -- Delete message - based on ActiveModel::Errors.added?(attribute, message = :invalid, options = {}).
message = self.errors[attribute].delete(message) rescue nil
# -- Delete attribute from errors if message array is empty.
self.errors.messages.delete(attribute) if !self.errors.messages[attribute].present?
return message
end
用法:
user.remove_error!(:email, :taken)
方法来检查有效性,除了指定的属性和消息。
def valid_except?(except={})
self.valid?
# -- Use this to call valid? for superclass if self.valid? is overridden.
# self.class.superclass.instance_method(:valid?).bind(self).call
except.each do |attribute, message|
if message.present?
remove_error!(attribute, message)
else
self.errors.delete(attribute)
end
end
!self.errors.present?
end
用法:
user.valid_except?({email: :blank})
user.valid_except?({email: "can't be blank"})
在Rails 4,你应该能够使用skip_callback(:validate, :name_of_validation_method)
......如果你的的的一个方便的命名验证方法。 。(免责声明:我没有测试过)的如果没有,你需要攻入回调的列表,找到你想跳过一个,并使用其filter
对象
示例:强>
我正在使用站点的Rails 4.1.11和狂欢2.4.11.beta,具有从2.1.4升级狂欢。 Spree::Variant
s的一个表中我们的代码存储多个拷贝,历史的目的。
由于升级,现在validates_uniqueness_of :sku, allow_blank: true, conditions: -> { where(deleted_at: nil) }
宝石,它打破我们的代码。正如您看到的,但是,它并没有使用命名的方法来做到这一点。这是我在Spree::Variant.class_eval
块所做的:
unique_sku_filter = _validate_callbacks.find do |c|
c.filter.is_a?(ActiveRecord::Validations::UniquenessValidator) &&
c.filter.instance_variable_get(:@attributes) == [:sku]
end.filter
skip_callback(:validate, unique_sku_filter)
此出现完全删除从Variant
的链回调。
NB。我不得不使用instance_variable_get
为@attributes
,因为它不具有访问它。您可以在c.filter.options
块以及检查find
;在上面的例子中,这看起来像:
c.filter.options
#=> {:case_sensitive=>true, :allow_blank=>true, :conditions=>#<Proc:... (lambda)>}
下面是一个Rails 3的“解决方案”为我工作(如果再有人有更好的办法,请提供它!)
class NameUniqueForTypeValidator < ActiveModel::Validator
def validate(record)
remove_name_taken_error!(record)
end
def remove_name_taken_error!(record)
errors = record.errors
errors.each do |attribute, error|
if attribute == :name && error.include?("taken") && record.name_unique_for_type?
errors.messages[attribute].each do |msg|
errors.messages[attribute].delete_at(errors.messages[attribute].index(msg)) if msg.include?("taken")
end
errors.messages.delete(attribute) if errors.messages[attribute].empty?
end
end
end
end
ActsAsTaggableOn::Tag.class_eval do
validates_with NameUniqueForTypeValidator
def name_unique_for_type?
!ActsAsTaggableOn::Tag.where(:name => name, :type => type).exists?
end
end
对于我下面的代码我的模型已经足够了。我不想邮政编码来验证。
after_validation :remove_nonrequired
def remove_nonrequired
errors.messages.delete(:zipcode)
end