Удаление или переопределение проверки ActiveRecord, добавленной суперклассом или миксином - PullRequest
21 голосов
/ 22 февраля 2010

Я использую Clearance для аутентификации в своем приложении Rails. Clearance::User mixin добавляет пару проверок к моей User модели, но есть одна из них, которую я хотел бы удалить или переопределить. Каков наилучший способ сделать это?

Проверка правильности

validates_uniqueness_of :email, :case_sensitive => false

, что само по себе неплохо, но мне нужно добавить :scope => :account_id. Проблема в том, что если я добавлю это к моей User модели

validates_uniqueness_of :email, :scope => :account_id

Я получаю обе проверки, и добавление одного клиренса является более строгим, чем у меня, поэтому мое не имеет никакого эффекта Мне нужно убедиться, что работает только моя. Как мне это сделать?

Ответы [ 9 ]

9 голосов
/ 24 июля 2014

Я бы раскошелил 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
6 голосов
/ 22 февраля 2010

Я закончил тем, что "решил" проблему следующим хаком:

  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

Если кто-нибудь знает лучший способ, я был бы очень признателен.

4 голосов
/ 17 июля 2014

Мне нужно было удалить свойство продукта 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
4 голосов
/ 22 июля 2010

У меня недавно была эта проблема, и после того, как Google не дал мне достаточно быстрых ответов, я нашел более точное, но все еще не идеальное решение этой проблемы. Теперь это не обязательно будет работать в вашем случае, так как кажется, что вы используете уже существующие суперклассы, но для меня это был мой собственный код, поэтому я просто использовал: 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
1 голос
/ 29 августа 2017

В Rails 4 вы должны иметь возможность использовать skip_callback(:validate, :name_of_validation_method) ... если у вас есть удобный метод проверки. (Отказ от ответственности: я не проверял это.) Если нет, вам нужно взломать список обратных вызовов, чтобы найти тот, который вы хотите пропустить, и использовать его объект filter. *

Пример:

Я работаю над сайтом, использующим Rails 4.1.11 и Spree 2.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.

NB. Мне пришлось использовать instance_variable_get для @attributes, потому что у него нет доступа к нему. Вы также можете проверить c.filter.options в блоке find; в приведенном выше примере это выглядит так:

c.filter.options
#=> {:case_sensitive=>true, :allow_blank=>true, :conditions=>#<Proc:... (lambda)>}
1 голос
/ 19 октября 2013

Errors.delete (ключ) удаляет все ошибки для атрибута, и я хочу удалить только определенный тип ошибок, принадлежащих атрибуту. Этот следующий метод может быть добавлен к любой модели.

Возвращает сообщение, если удалено, в противном случае ноль. Внутренние структуры данных изменены, поэтому все другие методы должны работать, как и ожидалось, после устранения ошибок.

Выпущено по лицензии MIT

Метод удаления ошибки из модели после выполнения проверок.

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"})
1 голос
/ 22 июля 2013

Я знаю, что опоздал на игру, но как насчет:

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

в инициализаторе?

0 голосов
/ 14 марта 2014

Для меня на моей модели ниже кода было достаточно. Я не хочу подтверждать почтовый индекс.

after_validation :remove_nonrequired

def remove_nonrequired
  errors.messages.delete(:zipcode)
end
0 голосов
/ 22 декабря 2011

Вот «решение» 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
...