Extracción o anulando una validación ActiveRecord añadida por una superclase o mixin
-
22-09-2019 - |
Pregunta
Estoy usando Liquidación para la autenticación en mi aplicación Rails. El mixin Clearance::User
agrega un par de validaciones a mi modelo User
, pero hay uno de ellos que me gustaría eliminar o anulación. ¿Cuál es la mejor manera de hacer esto?
La validación en cuestión es
validates_uniqueness_of :email, :case_sensitive => false
que en sí mismo no es malo, pero necesitaría añadir :scope => :account_id
. El problema es que si añado esto a mi modelo User
validates_uniqueness_of :email, :scope => :account_id
I get ambos validaciones, y la Liquidación añade es más restrictiva que la mía, así que la mía no tiene ningún efecto. Necesito para asegurarse de que sólo la mía corre. ¿Cómo puedo hacer esto?
Solución 2
Me terminó "resolver" el problema con este truco:
- mirada de un error en el atributo de tipo
:email
:taken
- Compruebe si el correo electrónico es único para esta cuenta (que es la validación que quería hacer)
- eliminar el error si el correo electrónico es único para esta cuenta.
suena razonable hasta que haya leído el código y descubrir cómo elimino un error. ActiveRecord::Errors
no tiene métodos para eliminar los errores una vez añadido, así que tengo que agarrar de TI de partes internas y yo mismo. Super Duper Mega feo.
Este es el código:
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
Si alguien sabe de una mejor manera, estaría muy agradecido.
Otros consejos
Me había tenedor el GEM y añadir una simple comprobación, que luego pueden ser anulados. Mi ejemplo se utiliza una preocupación.
La preocupación:
module Slugify
extend ActiveSupport::Concern
included do
validates :slug, uniqueness: true, unless: :skip_uniqueness?
end
protected
def skip_uniqueness?
false
end
end
Modelo:
class Category < ActiveRecord::Base
include Slugify
belongs_to :section
validates :slug, uniqueness: { scope: :section_id }
protected
def skip_uniqueness?
true
end
end
recientemente he tenido este problema y después de que Google no me dio las respuestas lo suficientemente rápida he encontrado una solución más limpia y aún así no-ideal a este problema. Ahora bien, esto no necesariamente funcionará en su caso, ya que parece que su uso de pre-existentes súper clases pero para mí fue mi propio código de manera que acabo de utilizar un:. Si parámetro con una verificación de tipos en la superclase
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
En el caso de clases de sub multpile
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
que necesitaba para eliminar el producto Spree validación :value
propiedad y parece que hay una solución más simple con Klass.class_eval
y clear_validators!
de AciveRecord::Base
module Spree
class ProductProperty < Spree::Base
#spree logic
validates :property, presence: true
validates :value, length: { maximum: 255 }
#spree logic
end
end
Y anularlo aquí
Spree::ProductProperty.class_eval do
clear_validators!
validates :property, presence: true
end
Yo sé que estoy tarde para el juego, pero ¿qué hay de:
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
en un inicializador?
Errors.delete (clave) elimina todos los errores para un atributo y sólo desea eliminar un tipo específico de error que pertenece a un atributo. Este método siguiente se puede añadir a cualquier modelo.
Devuelve si el mensaje eliminado, nil de lo contrario. estructuras de datos internos se modifican para todos los demás métodos deben funcionar como se espera después de la eliminación de errores.
Método de eliminar el error de modelo después de validaciones se han ejecutado.
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
Uso:
user.remove_error!(:email, :taken)
Método para comprobar la validez excepto atributos y mensajes especificados.
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
Uso:
user.valid_except?({email: :blank})
user.valid_except?({email: "can't be blank"})
En los carriles 4, que debe ser capaz de utilizar skip_callback(:validate, :name_of_validation_method)
... si Tienes un método de validación convenientemente llamado. . (Negación:. No he probado eso) Si no es así, tendrá que introducirse en la lista de las devoluciones de llamada a encontrar la que desea saltar, y el uso de su objeto filter
Ejemplo:
Estoy trabajando en un sitio usando Rails 4.1.11 y 2.4.11.beta Spree, después de haber actualizado desde Spree 2.1.4. Nuestras tiendas de códigos múltiples copias de Spree::Variant
s en una mesa, con fines históricos.
Desde la actualización, la gema ahora validates_uniqueness_of :sku, allow_blank: true, conditions: -> { where(deleted_at: nil) }
, que rompe nuestro código. Como se dará cuenta, sin embargo, que no utiliza un método denominado de hacerlo. Esto es lo que he hecho en un bloque 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)
Esto parece eliminar la devolución de llamada de la cadena de Variant
por completo.
NB. He tenido que utilizar para instance_variable_get
@attributes
, ya que no tiene un descriptor de acceso a la misma. Puede comprobar c.filter.options
en el bloque find
así; en el ejemplo anterior, esto se parece a:
c.filter.options
#=> {:case_sensitive=>true, :allow_blank=>true, :conditions=>#<Proc:... (lambda)>}
Aquí hay una Rieles "solución" 3 que trabajó para mí (de nuevo, si alguien tiene una mejor manera por favor ofrecerlo!)
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
Para mí en mi siguiente modelo de código era suficiente. No quiero código postal para validar.
after_validation :remove_nonrequired
def remove_nonrequired
errors.messages.delete(:zipcode)
end