Удаление или переопределение проверки 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 более ограничительна, чем моя, поэтому моя не имеет никакого эффекта. Мне нужно убедиться, что только моя работает. Как это сделать?
Ответы
Ответ 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