删除或覆盖由超类或mixin添加的ActiveRecord验证

时间:2010-02-22 08:54:13

标签: ruby-on-rails ruby validation clearance

我在我的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添加的限制比我的更严格,所以我没有效果。我需要确保只有我的运行。我该怎么做?

9 个答案:

答案 0 :(得分:9)

我会分配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 :(得分:6)

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

  1. 在类型:email
  2. :taken属性上查找错误
  3. 检查此帐户的电子邮件是否唯一(这是我想要的验证)
  4. 如果该帐户的电子邮件是唯一的,则
  5. 删除错误。
  6. 听起来很合理,直到您阅读代码并发现我如何删除错误。 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
    

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

答案 2 :(得分:4)

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

答案 3 :(得分:4)

我需要删除Spree产品属性:value验证,似乎有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

答案 4 :(得分:1)

我知道我已经迟到了,但是怎么样:

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

在初始化程序中?

答案 5 :(得分:1)

Errors.delete(key)删除属性的所有错误,我只想删除属于属性的特定类型的错误。以下方法可以添加到任何模型中。

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

MIT License

下发布

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

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"})

答案 6 :(得分:1)

在Rails 4中,您应该可以使用skip_callback(:validate, :name_of_validation_method) ...如果您拥有一个方便命名的验证方法。 (免责声明:我没有测试过。)如果没有,你需要入侵回调列表以找到你要跳过的回调,并使用它的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。我必须使用instance_variable_get作为@attributes,因为它没有访问者。您也可以检查c.filter.options块中的find;在上面的例子中,这看起来像:

c.filter.options
#=> {:case_sensitive=>true, :allow_blank=>true, :conditions=>#<Proc:... (lambda)>}

答案 7 :(得分:-1)

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

答案 8 :(得分:-2)

对我来说,我的模型下面的代码已经足够了。我不希望邮政编码验证。

after_validation :remove_nonrequired

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