La rimozione o sovrascrivendo una convalida ActiveRecord aggiunto da una superclasse o mixin
-
22-09-2019 - |
Domanda
Sto usando Liquidazione per l'autenticazione nella mia applicazione Rails. Il mixin Clearance::User
aggiunge un paio di convalide al mio modello User
, ma non c'è uno di questi che vorrei rimuovere o sostituire. Qual è il modo migliore di fare questo?
La convalida in questione è
validates_uniqueness_of :email, :case_sensitive => false
che di per sé non è male, ma avrei bisogno di aggiungere :scope => :account_id
. Il problema è che se aggiungo questo per il mio modello User
validates_uniqueness_of :email, :scope => :account_id
ho sia validazioni, e quello Clearance aggiunge è più restrittiva del mio, quindi il mio non ha alcun effetto. Ho bisogno di fare in modo che solo mia corre. Come posso fare questo?
Soluzione 2
Ho finito per "risolvere" il problema con il seguente trucco:
- sguardo per un errore l'attributo di tipo
:email
:taken
- controllare se l'email è unico per questo account (che è la convalida che volevo fare)
- rimuovere l'errore se l'email è unico per questo account.
Suoni ragionevole fino a leggere il codice e scoprire come rimuovo un errore. ActiveRecord::Errors
non ha metodi per rimuovere gli errori, una volta aggiunti, quindi devo afferrare IT di interni e di farlo anche io. Super duper mega brutto.
Questo è il codice:
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
Se qualcuno sa di un modo migliore, sarei molto grato.
Altri suggerimenti
Mi piacerebbe fork del GEM e aggiungi un semplice controllo, che possono poi essere ignorata. Il mio esempio utilizza una preoccupazione.
La preoccupazione:
module Slugify
extend ActiveSupport::Concern
included do
validates :slug, uniqueness: true, unless: :skip_uniqueness?
end
protected
def skip_uniqueness?
false
end
end
Modello:
class Category < ActiveRecord::Base
include Slugify
belongs_to :section
validates :slug, uniqueness: { scope: :section_id }
protected
def skip_uniqueness?
true
end
end
Recentemente ho avuto questo problema e dopo che Google non mi ha dato le risposte abbastanza veloci ho trovato una soluzione più ordinato ma ancora non-ideale a questo problema. Ora, questo non necessariamente funziona nel vostro caso come sembra la vostra utilizzando le classi di super preesistente, ma per me è stato il mio codice così ho usato un:. Se param con un controllo di tipo nella 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
Nel caso di sottoclassi 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
Avevo bisogno di rimuovere il prodotto Spree convalida proprietà :value
e sembra c'è una soluzione più semplice con Klass.class_eval
e clear_validators!
di AciveRecord::Base
module Spree
class ProductProperty < Spree::Base
#spree logic
validates :property, presence: true
validates :value, length: { maximum: 255 }
#spree logic
end
end
E ignorare qui
Spree::ProductProperty.class_eval do
clear_validators!
validates :property, presence: true
end
Lo so, sono in ritardo al gioco, ma come circa:
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
in un inizializzatore?
Errors.delete (chiave) rimuove tutti gli errori per un attributo e ho solo voglia di rimuovere un tipo specifico di errore appartenente ad un attributo. Questo metodo seguente può essere aggiunto a qualsiasi modello.
Restituisce un messaggio se rimosso, altrimenti nullo. strutture di dati interni vengono modificati in modo che tutti gli altri metodi dovrebbero funzionare come previsto dopo la rimozione di errore.
Metodo eliminare l'errore dal modello convalide sono stati eseguiti.
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
Utilizzo:
user.remove_error!(:email, :taken)
metodo di verifica della validità tranne gli attributi e messaggi specificati.
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
Utilizzo:
user.valid_except?({email: :blank})
user.valid_except?({email: "can't be blank"})
In Rails 4, si dovrebbe essere in grado di utilizzare skip_callback(:validate, :name_of_validation_method)
... se sono un metodo di validazione comodamente nome. . (Disclaimer:. Non ho provato che) In caso contrario, sarà necessario incidere nella lista di callback per trovare quello che si desidera saltare, e utilizzare il suo oggetto filter
Esempio:
Sto lavorando su un sito utilizzando Rails 4.1.11 e Spree 2.4.11.beta, dopo aver aggiornato Spree da 2.1.4. Il nostro codice memorizza copie multiple di Spree::Variant
s in una tabella, per scopi storici.
Dato che l'aggiornamento, la gemma ora validates_uniqueness_of :sku, allow_blank: true, conditions: -> { where(deleted_at: nil) }
, che rompe il nostro codice. Come si noterà, però, non fa uso di un metodo chiamato a farlo. Questo è quello che ho fatto in un blocco 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)
Questo sembra rimuovere la richiamata da catena di Variant
del tutto.
NB. Ho dovuto usare instance_variable_get
per @attributes
, perché non dispone di una funzione di accesso ad esso. È possibile controllare c.filter.options
nel blocco find
pure; Nell'esempio di cui sopra, questo appare come:
c.filter.options
#=> {:case_sensitive=>true, :allow_blank=>true, :conditions=>#<Proc:... (lambda)>}
Ecco una "soluzione" Rails 3 che ha funzionato per me (sempre se qualcuno ha un modo migliore si prega di offrirlo!)
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
Per quanto mi riguarda il mio modello sottostante Codice è stato sufficiente. Non voglio codice postale per convalidare.
after_validation :remove_nonrequired
def remove_nonrequired
errors.messages.delete(:zipcode)
end