La suppression ou de forcer une validation ActiveRecord ajoutée par une superclasse ou mixin
-
22-09-2019 - |
Question
J'utilise Clearance pour l'authentification dans mon application Rails. Le mixin Clearance::User
ajoute quelques à mon modèle de validation de User
, mais il est l'un de ceux-ci que je voudrais supprimer ou remplacer. Quelle est la meilleure façon de le faire?
La validation en question est
validates_uniqueness_of :email, :case_sensitive => false
ce qui en soi est pas mal, mais il me faudrait ajouter :scope => :account_id
. Le problème est que si j'ajoute à mon modèle User
validates_uniqueness_of :email, :scope => :account_id
Je reçois les validation et une Clearance est plus restrictive ajoute que le mien, le mien n'a aucun effet. Je dois vous assurer que seulement le mien court. Comment puis-je faire?
La solution 2
J'ai fini par « résoudre » le problème avec le hack suivant:
- Rechercher une erreur sur l'attribut
:email
de type:taken
- vérifier si l'e-mail est unique pour ce compte (qui est la validation que je voulais faire)
- supprimer l'erreur si l'e-mail est unique pour ce compte.
Sons raisonnable jusqu'à ce que vous lisez le code et découvrez comment supprimer une erreur. ActiveRecord::Errors
n'a pas des méthodes pour supprimer les erreurs une fois ajoutées, donc je dois attraper internals et de le faire de moi-même. Super Duper méga laid.
Voici le code:
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 quelqu'un connaît une meilleure façon, je serais très reconnaissant.
Autres conseils
Je FOURCHE le GEM et ajouter une simple vérification, qui peut ensuite être surchargée. Mon exemple utilise une préoccupation.
Inquiétude:
module Slugify
extend ActiveSupport::Concern
included do
validates :slug, uniqueness: true, unless: :skip_uniqueness?
end
protected
def skip_uniqueness?
false
end
end
Modèle:
class Category < ActiveRecord::Base
include Slugify
belongs_to :section
validates :slug, uniqueness: { scope: :section_id }
protected
def skip_uniqueness?
true
end
end
J'ai eu récemment ce problème et après Google ne m'a pas donné les réponses rapides assez j'ai trouvé une solution encore plus net encore non idéale à ce problème. Maintenant, cela ne fonctionnera pas nécessairement dans votre cas car il semble que votre utilisation de super-classes pré-existantes, mais pour moi, il était mon propre code si je viens d'utiliser un. Si un chèque param de type dans la classe super
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
Dans le cas des classes multpile sous
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
Je avais besoin de retirer la validation de :value
de propriété du produit de la Spree et il semble qu'il y ait une solution avec Klass.class_eval
et simplier 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
Et la remplacer ici
Spree::ProductProperty.class_eval do
clear_validators!
validates :property, presence: true
end
Je sais que je suis en retard dans le jeu, mais que diriez-vous:
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
dans un initialiseur?
Errors.delete (clé) supprime toutes les erreurs pour un attribut et je veux seulement supprimer un type d'erreur appartenant à un attribut. Cette méthode suivante peut être ajouté à tout modèle.
Retourne le message si supprimé, sinon nul. structures de données internes sont modifiées de sorte que tous les autres méthodes devraient fonctionner comme prévu après le retrait d'erreur.
Méthode pour supprimer l'erreur de modèle après validation ont été réalisées.
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
Utilisation:
user.remove_error!(:email, :taken)
Méthode pour vérifier la validité des attributs et à l'exception des messages spécifiés.
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
Utilisation:
user.valid_except?({email: :blank})
user.valid_except?({email: "can't be blank"})
Dans Rails 4, vous devriez pouvoir utiliser skip_callback(:validate, :name_of_validation_method)
... si vous Vous une méthode de validation commodément nommé. . (Avertissement:. Je n'ai pas testé que) Sinon, vous aurez besoin de pirater la liste des callbacks pour trouver celui que vous voulez sauter et utiliser son objet filter
Exemple:
Je travaille sur un site en utilisant Rails 4.1.11 et Spree 2.4.11.beta, après avoir mis à jour Spree de 2.1.4. Notre code stocke plusieurs copies de Spree::Variant
s dans une table, à des fins historiques.
Depuis la mise à niveau, le joyau maintenant validates_uniqueness_of :sku, allow_blank: true, conditions: -> { where(deleted_at: nil) }
, qui rompt notre code. Comme vous le remarquerez, cependant, il n'utilise pas une méthode appelée à le faire. Voilà ce que je l'ai fait dans un bloc 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)
Il semble supprimer le rappel de la chaîne de Variant
entièrement.
NB. J'ai dû utiliser instance_variable_get
pour @attributes
, car il ne dispose pas d'un accesseur à elle. Vous pouvez vérifier c.filter.options
dans le bloc find
ainsi; dans l'exemple ci-dessus, cela ressemble à:
c.filter.options
#=> {:case_sensitive=>true, :allow_blank=>true, :conditions=>#<Proc:... (lambda)>}
Voici un Rails 3 « solution » qui a fonctionné pour moi (encore une fois si quelqu'un a une meilleure façon s'il vous plaît offrir!)
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
Pour moi, sur mon modèle ci-dessous le code était suffisant. Je ne veux pas valider code postal.
after_validation :remove_nonrequired
def remove_nonrequired
errors.messages.delete(:zipcode)
end