代码之家  ›  专栏  ›  技术社区  ›  Theo

删除或重写由超类或mixin添加的ActiveRecord验证

  •  20
  • Theo  · 技术社区  · 15 年前

    我在Rails应用程序中使用许可证进行身份验证。这个 Clearance::User mixin向我的 User 模型,但有一个我想删除或重写。最好的方法是什么?

    所讨论的验证是

    validates_uniqueness_of :email, :case_sensitive => false
    

    这本身也不错,但我需要补充一点 :scope => :account_id . 问题是如果我把这个添加到 用户 模型

    validates_uniqueness_of :email, :scope => :account_id
    

    我得到 二者都 验证,加上一个清除比我的限制更大,所以我的没有效果。我要确保只有我自己的。我该怎么做?

    9 回复  |  直到 7 年前
        1
  •  8
  •   kim3er    10 年前

    我将分叉宝石并添加一个简单的检查,然后可以覆盖。我的示例使用了一个关注点。

    关注:

    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
    
        2
  •  6
  •   Theo    15 年前

    我最终通过以下黑客程序“解决”了这个问题:

    1. 在上查找错误 :email 类型的属性 :taken
    2. 检查此帐户的电子邮件是否唯一(这是我要做的验证)
    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
    

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

        3
  •  4
  •   Dan    14 年前

    我最近遇到了这个问题,在谷歌没有足够快地给我答案之后,我找到了一个更整洁但仍然不理想的解决方案。现在,这在您的情况下不一定有效,因为您似乎使用了预先存在的超级类,但对我来说,这是我自己的代码,所以我只是在超级类中使用了带有类型检查的:if-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
    

    如果是多个文件子类

    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
    
        4
  •  4
  •   Andrew Shatnyy    10 年前

    我需要移除Spree产品属性 :value 验证,似乎有一个更简单的解决方案 Klass.class_eval clear_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
    
        5
  •  1
  •   Adam Becker    11 年前

    我知道我比赛迟到了,但是怎么样:

    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
    

    在初始值设定项中?

        6
  •  1
  •   mindriot    11 年前

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

    如果删除则返回消息,否则为零。内部数据结构会被修改,因此在删除错误后,所有其他方法都应按预期工作。

    根据 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"})
    
        7
  •  1
  •   PJSCopeland    7 年前

    在Rails 4中,您应该能够使用 skip_callback(:validate, :name_of_validation_method) …如果你 一种方便命名的验证方法。 (免责声明:我没有测试过。) 如果没有,您需要黑入回调列表以找到您想要跳过的回调,并使用它 filter 对象。

    例子:

    我在一个使用Rails 4.1.11和Spree2.4.11.beta的站点上工作,已经将Spree从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 整个链条。

    铌。我不得不使用 instance_variable_get 对于 @attributes ,因为它没有访问器。你可以检查 c.filter.options find 块;在上面的示例中,如下所示:

    c.filter.options
    #=> {:case_sensitive=>true, :allow_blank=>true, :conditions=>#<Proc:... (lambda)>}
    
        8
  •  -1
  •   Sheldon    13 年前

    这里有一个Rails3的“解决方案”,对我很有效(如果有更好的方法,请再次提供!)

    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
    
        9
  •  -2
  •   Canbey Bilgili    11 年前

    对于我来说,下面的代码就足够了。我不想让zipcode验证。

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