2010-02-22 1 views
19

Sto utilizzando Clearance per l'autenticazione nella mia applicazione Rails. Il mixin Clearance::User aggiunge un paio di convalide al mio modello User, ma c'è una di queste che vorrei rimuovere o sovrascrivere. Qual è il modo migliore per fare questo?Rimozione o sovrascrittura di una convalida di ActiveRecord aggiunta da una superclasse o mixin

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 al mio User modello

validates_uniqueness_of :email, :scope => :account_id 

ottengo entrambe convalide, e quello Clearance aggiunge è più restrittivo del mio, quindi il mio non ha alcun effetto. Devo assicurarmi che solo le mie funzionino. Come faccio a fare questo?

risposta

6

ho finito per "risolvere" il problema con il seguente trucco:

  1. look per un errore l'attributo :email di tipo :taken
  2. di controllo se l'e-mail è unico per questo account (che è il convalida che volevo fare)
  3. rimuovere l'errore se l'e-mail è univoca per questo account.

Suoni ragionevoli finché non si legge il codice e si scopre come rimuovo un errore. ActiveRecord::Errors non ha metodi per rimuovere gli errori una volta aggiunti, quindi devo afferrare i suoi interni e farlo da solo. 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.

+1

A partire da Rails 3, è possibile utilizzare errors.delete (: field) per rimuovere un errore dalla raccolta. –

4

Recentemente ho avuto questo problema e dopo google non mi ha dato le risposte abbastanza velocemente ho trovato una soluzione più ordinata e tuttavia non ancora ideale per questo problema. Ora questo non funzionerà necessariamente nel tuo caso poiché sembra che tu stia usando le super classi preesistenti, ma per me è stato il mio codice personale, quindi ho usato solo un: if param con un check di tipo nella super classe.

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 classi di 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 
+1

Purtroppo per me non posso cambiare la superclasse, è una gemma. – Theo

+0

È come mettere il nastro sopra la luce del motore di controllo e dire che funziona. – courtsimas

-1

Ecco una 3 "soluzione" Rails 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 
1

So che sono in ritardo al gioco, ma che ne dite:

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?

1

Errors.delete (chiave) rimuove tutti gli errori per un attributo e desidero solo rimuovere un tipo specifico di errore appartenente a un attributo. Questo metodo seguente può essere aggiunto a qualsiasi modello.

Restituisce il messaggio se rimosso, altrimenti nulla.Le strutture dei dati interne vengono modificate, quindi tutti gli altri metodi dovrebbero funzionare come previsto dopo la rimozione degli errori.

rilasciato sotto Metodo MIT License

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 

Usage:

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 

Usage:

user.valid_except?({email: :blank}) 
user.valid_except?({email: "can't be blank"}) 
-1

Per me sul mio modello sottostante Codice è stato sufficiente. Non voglio che lo zipcode sia convalidato.

after_validation :remove_nonrequired 

def remove_nonrequired 
    errors.messages.delete(:zipcode) 
end 
+3

Stai scherzando, vero? –

+0

Per me è stato rimosso l'errore, ma non lo stato "non valido" sul modello. – PJSCopeland

4

Avevo bisogno di rimuovere proprietà del prodotto Spree :value convalida e sembra che ci sia 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 sovrascrivere qui

Spree::ProductProperty.class_eval do  
    clear_validators! 
    validates :property, presence: true 
end 
+0

non rimuove anche il validatore sulla classe base e quindi tutte le altre sottoclassi? –

+0

in base a https://github.com/rails/rails/blob/e3ceb28e66ca6e869f8f9778dc42672f48001a90/activemodel/lib/active_model/validations.rb#L233 si reimposta solo localmente Set di validazioni/callback. Presumo che il set di callback sia impostato sulla singola classe non su tutta la catena. Non ho avuto problemi con le convalide delle classi derivate. –

+0

Penso che dovrebbe andare così: 'classe Sottoclasse

8

mi piacerebbe lanciare il GEM e aggiungere un controllo semplice, che può essere sovrascritto. Il mio esempio usa una preoccupazione.

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 
0

In Rails 4, si dovrebbe essere in grado di utilizzare skip_callback(:validate, :name_of_validation_method) ... se avete un metodo di validazione comodamente nome. (Dichiarazione di non responsabilità: non l'ho ancora provato.) In caso contrario, dovrai inserire l'elenco dei callback per trovare quello che desideri saltare e utilizzare l'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 più copie di Spree::Variant s in una tabella, per scopi storici.

Dal momento che l'aggiornamento, la gemma ora validates_uniqueness_of :sku, allow_blank: true, conditions: -> { where(deleted_at: nil) }, che rompe il nostro codice. Come noterai, però, non usa un metodo chiamato per 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 s' del tutto.

NB. Ho dovuto usare instance_variable_get per @attributes, perché non ha una funzione di accesso ad esso.È possibile controllare anche c.filter.options nel blocco find; nell'esempio sopra, questo appare:

c.filter.options 
#=> {:case_sensitive=>true, :allow_blank=>true, :conditions=>#<Proc:... (lambda)>}