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

StackOverflow https://stackoverflow.com/questions/2309757

Вопрос

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

Проверка, о которой идет речь, заключается в

validates_uniqueness_of :email, :case_sensitive => false

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

validates_uniqueness_of :email, :scope => :account_id

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

Это было полезно?

Решение 2

В итоге я "решил" проблему следующим взломом:

  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

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

Другие советы

Я бы разветвил драгоценный камень и добавил простую проверку, которую затем можно переопределить.В моем примере используется Проблема.

Беспокойство:

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

Недавно у меня возникла эта проблема, и после того, как Google недостаточно быстро дал мне ответы, я нашел более аккуратное, но все еще неидеальное решение этой проблемы.Теперь это не обязательно сработает в вашем случае, поскольку кажется, что вы используете уже существующие суперклассы, но для меня это был мой собственный код, поэтому я просто использовал параметр :if с проверкой типа в суперклассе.

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

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

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

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 (ключ) удаляет все ошибки для атрибута, и я хочу удалить только определенный тип ошибки, относящийся к атрибуту.Этот следующий метод может быть добавлен к любой модели.

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

Выпущенный в рамках 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"})

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

Пример:

Я работаю над сайтом, использующим Rails 4.1.11 и Spree 2.4.11.beta, обновив Spree с версии 2.1.4.Наш код хранит несколько копий Spree::Variants в одной таблице, для исторических целей.

После обновления драгоценный камень теперь 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)>}

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

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

after_validation :remove_nonrequired

def remove_nonrequired
  errors.messages.delete(:zipcode)
end
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top