Подтвердить что ты не робот

Удаление или переопределение проверки ActiveRecord, добавленного суперклассом или микшированием

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

Валидация, о которой идет речь,

validates_uniqueness_of :email, :case_sensitive => false

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

validates_uniqueness_of :email, :scope => :account_id

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

4b9b3361

Ответ 1

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

  • найдите ошибку в атрибуте :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

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

Ответ 2

Я бы разблокировал 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

Ответ 3

У меня недавно была эта проблема, и после того, как 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

Ответ 4

Мне нужно было удалить свойство продукта Spree :value, и кажется, что более простое решение с Klass.class_eval и clear_validators! of 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

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

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

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

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

Выпущено под Лицензия 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"})

Ответ 7

В 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 в одной таблице для исторических целей.

Начиная с обновления, теперь камень 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)>}

Ответ 8

Здесь 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

Ответ 9

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

after_validation :remove_nonrequired

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