删除或覆盖由超类或mixin添加的ActiveRecordvalidation

我在我的Rails应用程序中使用Clearance进行身份validation。 Clearance::User mixin为我的User模型添加了几个validation,但是我想删除或覆盖其中一个。 这样做的最佳方式是什么?

有问题的validation是

 validates_uniqueness_of :email, :case_sensitive => false 

这本身并不坏,但我需要添加:scope => :account_id 。 问题是,如果我将其添加到我的User模型

 validates_uniqueness_of :email, :scope => :account_id 

我得到了两个validation,而Clearance补充的那个比我的更严格,所以我没有效果。 我需要确保只有我的跑步。 我该怎么做呢?

我会分配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 

我最后用以下黑客“解决”了这个问题:

  1. 在以下类型的:email属性中查找错误:taken
  2. 检查该帐户的电子邮件是否唯一(这是我想要的validation)
  3. 如果此帐户的电子邮件是唯一的,请删除该错误。

在您阅读代码并了解我如何删除错误之前,这听起来很合理。 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 

如果有人知道更好的方式,我将非常感激。

我最近有这个问题,谷歌没有给我足够快的答案,我发现一个整洁但仍然不理想的解决方案来解决这个问题。 现在这不一定适用于您的情况,因为它似乎是您使用预先存在的超类,但对我来说这是我自己的代码所以我只使用了:如果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 

我需要删除Spree产品属性:valuevalidation,似乎有一个更简单的解决方案,使用Klass.class_evalclear_validators! AciveRecord::Base

 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(key)删除属性的所有错误,我只想删除属于属性的特定类型的错误。 可以将以下方法添加到任何模型中。

如果删除则返回消息,否则返回nil。 修改内部数据结构,以便在删除错误后所有其他方法应按预期工作。

根据MIT许可证发布

运行validation后从模型中删除错误的方法。

 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) …如果您一个方便命名的validation方法。 (免责声明:我没有测试过。)如果没有,你需要入侵回调列表以找到你想要跳过的回调,并使用它的filter对象。

例:

我正在使用Rails 4.1.11和Spree 2.4.11.beta的网站上工作,已经从2.1.4升级了Spree。 出于历史目的,我们的代码在一个表中存储Spree::Variant的多个副本。

自升级以来,gem现在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。 我必须为@attributes使用instance_variable_get ,因为它没有访问器。 您也可以在find块中查看c.filter.options ; 在上面的例子中,这看起来像:

 c.filter.options #=> {:case_sensitive=>true, :allow_blank=>true, :conditions=>#} 

这是一个适用于我的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 

对我来说,我的模型下面的代码就够了。 我不希望zipcodevalidation。

 after_validation :remove_nonrequired def remove_nonrequired errors.messages.delete(:zipcode) end