6

Qual è il modo corretto per salvare un'eccezione e continuare semplicemente l'elaborazione? Ho un'app che ha cartelle e articoli, con una relazione habtm attraverso una tabella di join chiamata folders_items. Quella tabella ha un vincolo univoco che garantisce che non ci siano combinazioni di voci/cartelle duplicate. Se l'utente tenta di aggiungere un elemento alla stessa cartella più volte, ovviamente non voglio aggiungere le righe aggiuntive; ma non voglio interrompere l'elaborazione, neanche.Rails 3 ignora l'eccezione di limitazione di Postgres

Postgres lancia automaticamente un'eccezione quando il vincolo univoco è violato, così ho cercato di ignorarlo nel controller come segue:

rescue PG::Error, :with => :do_nothing 

def do_nothing 

end 

Questo funziona bene sui singoli inserimenti. Il controller esegue il rendering con un codice di stato di 200. Tuttavia, ho un altro metodo che fa gli inserimenti di massa in un ciclo. In quel metodo, il controller esce dal ciclo quando incontra la prima riga duplicata, che non è quello che voglio. In un primo momento, ho pensato che il ciclo dovesse essere incapsulato in una transazione che si stava riavvolgendo, ma non lo è - tutte le righe precedenti al duplicato vengono inserite. Voglio semplicemente ignorare l'eccezione del vincolo e passare all'elemento successivo. Come posso evitare che l'eccezione PG :: Error interrompa questo?

risposta

11

In generale, la gestione delle eccezioni dovrebbe essere nel punto più vicino all'errore che è possibile fare qualcosa di ragionevole con l'eccezione. Nel tuo caso, che ci si vuole il tuo rescue all'interno del ciclo, ad esempio:

stuff.each do |h| 
    begin 
    Model.create(h) 
    rescue ActiveRecord::RecordNotUnique => e 
    next if(e.message =~ /unique.*constraint.*INDEX_NAME_GOES_HERE/) 
    raise 
    end 
end 

Un paio di punti di interesse:

  1. Una violazione di vincolo all'interno del database vi darà un errore piuttosto che ActiveRecord::RecordNotUnique il sottostante PG::Error. AFAIK, riceverai un PG::Error se stai parlando direttamente al database anziché passare attraverso ActiveRecord.
  2. Sostituire INDEX_NAME_GOES_HERE con il nome reale dell'indice univoco.
  3. Si desidera ignorare solo la violazione specifica del vincolo che si sta aspettando, quindi il bit next if(...) seguito dallo senza argomento raise (ad esempio rilanciare l'eccezione se non è quello che si prevede di vedere).
+0

Grazie, mu. Sembra che abbia funzionato. Ho provato a incorporare "next" nel metodo do_nothing chiamato rescue_with, ma questo mi ha dato un errore. In ogni caso, è sicuramente un errore PG :: e non sto parlando direttamente al db. È solo una semplice tabella di join di habtm senza inserto SQL personalizzato. Forse ActiveRecord :: RecordNotUnique viene chiamato solo sulle tabelle dei modelli? –

+0

Ho parlato troppo presto. Testato di nuovo e definitivamente sta ancora terminando il ciclo sul primo duplicato che incontra. La cosa interessante è che se inserisco il blocco begin/rescue nel metodo controller e rimuoviamo il rescue_with dal controller, allora l'eccezione non viene catturata affatto. Quindi, per qualche ragione, apparentemente questo non è catturato a livello del metodo del controller. –

+0

Non vuoi prenderlo a livello di controller, è troppo lontano da dove puoi fare qualcosa sull'eccezione. Com'è il tuo codice? –

1

Se si inserisce un validatore di Rails sul modello, è possibile controllare il flusso senza generare un'eccezione.

class FolderItems 
    belongs_to :item 
    belongs_to :folder 
    validates_uniqueness_of :item, scope: [:folder], on: :create 
end 

quindi è possibile utilizzare

FolderItem.create(folder: folder, item: item) 

Si restituirà true se l'associazione è stato creato, false se c'è stato un errore. Non genererà un'eccezione. L'utilizzo di FolderItem.create! genererebbe un'eccezione se l'associazione non viene creata.

Il motivo per cui si vedono errori PG è perché Rails stesso pensa che il modello sia valido in fase di salvataggio, poiché la classe del modello non ha un vincolo di unicità in Rails. Ovviamente, hai un vincolo univoco nel DB, che sorprende Rails e lo fa esplodere all'ultimo minuto.

Se la prestazione è critica, allora forse ignorare questo consiglio. Avere un vincolo di univocità su un modello Rails fa sì che esegua un SELECT prima di ogni INSERT affinché possa eseguire la convalida dell'unicità a livello di Rails, potenzialmente raddoppiando il numero di query che il loop sta eseguendo.Basta prendere gli errori a livello di database come si fa potrebbe essere un ragionevole scambio di eleganza per le prestazioni.

(modifica) TL; DR: ha sempre il vincolo univoco nel DB. Anche il vincolo del modello consentirà la convalida di ActiveRecord/ActiveModel prima che il DB lanci un errore.

+3

Una convalida di unicità di Rails dovrebbe ** sempre ** essere supportata da un vincolo di unicità all'interno del database. Tutte le convalide di ActiveRecord sono soggette a condizioni di gara, quindi la logica deve essere presente nel database e devi fare i conti con le eccezioni se non vuoi dati rotti. –

+0

Hai assolutamente ragione, non intendevo implicare il contrario. Quando parlo di validazione del modello, lo intendo decisamente come in aggiunta al vincolo univoco nel database. È necessario il vincolo nel database per l'integrità e il vincolo nella convalida Rails per ActiveRecord/ActiveModel. –