ruby on rails - Eliminar o anular una validación de ActiveRecord agregada por una superclase o mixin
ruby-on-rails validation (9)
Terminé "resolviendo" el problema con el siguiente truco:
- busque un error en el atributo de
:email
:taken
tipo - 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 lea el código y descubra cómo elimino un error. ActiveRecord::Errors
no tiene métodos para eliminar errores una vez agregados, así que tengo que agarrarme de sus elementos internos y hacerlo 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.
Estoy usando Clearance para autenticación en mi aplicación Rails. The Clearance::User
mixin agrega un par de validaciones a mi modelo de User
, pero hay una que me gustaría eliminar o anular. Cual 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 agregar :scope => :account_id
. El problema es que si agrego esto a mi modelo de User
validates_uniqueness_of :email, :scope => :account_id
Recibo ambas validaciones, y la que Clearance añade es más restrictiva que la mía, por lo que la mía no tiene ningún efecto. Necesito asegurarme de que solo funciona la mía. ¿Cómo hago esto?
Hace poco tuve este problema y, después de que Google no me diera las respuestas lo suficientemente rápido, encontré una solución más ordenada pero aún no ideal para este problema. Ahora, esto no funcionará necesariamente en su caso, ya que parece que usa superclases preexistentes, pero para mí era mi propio código, así que acabo de usar un: si param con una verificación de tipo 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 sub clases de multiples
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
Aquí hay una "solución" de Rails 3 que funcionó para mí (de nuevo, si alguien tiene una mejor manera, por favor, ofrézcala).
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
Sé que llego tarde al juego, pero ¿qué tal?
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 de un atributo y solo quiero eliminar un tipo específico de error que pertenece a un atributo. Este siguiente método se puede agregar a cualquier modelo.
Devuelve el mensaje si se eliminó, de lo contrario no. Las estructuras de datos internas se modifican para que todos los demás métodos funcionen según lo esperado después de la eliminación del error.
Publicado bajo la licencia MIT
Método para eliminar el error del modelo después de que se hayan ejecutado las validaciones.
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 verificar la validez, excepto los 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"})
Para mí, en mi modelo a continuación, el código era suficiente. No quiero que se valide el código postal.
after_validation :remove_nonrequired
def remove_nonrequired
errors.messages.delete(:zipcode)
end
Necesitaba eliminar la propiedad del producto Spree :value
validación de :value
y parece que hay una solución más Klass.class_eval
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
Bifurcaría el GEM y agregaría un cheque simple, que luego podría anularse. Mi ejemplo usa una preocupación.
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
En Rails 4, debería poder usar skip_callback(:validate, :name_of_validation_method)
... si tiene un método de validación convenientemente nombrado. (Descargo de responsabilidad: no lo he probado). De lo contrario, deberá ingresar a la lista de devoluciones de llamada para encontrar la que desea omitir y usar su objeto de filter
.
Ejemplo:
Estoy trabajando en un sitio usando Rails 4.1.11 y Spree 2.4.11.beta, habiendo actualizado Spree desde 2.1.4. Nuestro código almacena copias múltiples de Spree::Variant
s en una sola tabla, 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 notará, sin embargo, no usa un método con nombre para hacerlo. Esto es lo que hice en un bloque de 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 por completo la devolución de llamada de la cadena de Variant
.
NÓTESE BIEN. He tenido que usar instance_variable_get
para @attributes
, porque no tiene un @attributes
de @attributes
. También puede verificar c.filter.options
en el bloque de find
; en el ejemplo anterior, esto se ve así:
c.filter.options
#=> {:case_sensitive=>true, :allow_blank=>true, :conditions=>#<Proc:... (lambda)>}