Удаление или переопределение проверки ActiveRecord, добавленной суперклассом или микшином
-
22-09-2019 - |
Вопрос
Я использую разрешение для аутентификации в моем приложении Rails.В Clearance::User
mixin добавляет пару проверок к моему User
модель, но есть одна из них, которую я хотел бы удалить или переопределить.Каков наилучший способ сделать это?
Проверка, о которой идет речь, заключается в
validates_uniqueness_of :email, :case_sensitive => false
что само по себе неплохо, но мне нужно было бы добавить :scope => :account_id
.Проблема в том, что если я добавлю это в свой User
Модель
validates_uniqueness_of :email, :scope => :account_id
Я получаю и то, и другое проверки, и тот, который добавляет Разрешение, более ограничительный, чем мой, поэтому мой не имеет никакого эффекта.Мне нужно убедиться, что работает только мой.Как мне это сделать?
Решение 2
В итоге я "решил" проблему следующим взломом:
- найдите ошибку на
: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
Если кто-нибудь знает способ получше, я был бы очень благодарен.
Другие советы
Я бы разветвил драгоценный камень и добавил простую проверку, которую затем можно переопределить.В моем примере используется Проблема.
Беспокойство:
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::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)>}
Вот "решение" 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