6

Jaki jest poprawny sposób na uratowanie wyjątku i po prostu kontynuować przetwarzanie? Mam aplikację, która zawiera foldery i elementy, ze związkiem habtm poprzez tabelę dołączeń o nazwie folders_items. Ta tabela ma wyjątkowe ograniczenie, dzięki czemu nie ma duplikatów kombinacji elementów/folderów. Jeśli użytkownik spróbuje dodać element do tego samego folderu kilka razy, oczywiście nie chcę dodawać kolejnych wierszy; ale nie chcę też przerwać przetwarzania.Rails 3 zignorować wyjątek wyjątek wyjątek Postgres

Postgres automatycznie zgłasza wyjątek, gdy UNIQUE jest naruszona, więc starałem się ignorować go w sterowniku, co następuje:

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

def do_nothing 

end 

Działa to dobrze na pojedynczych wstawek. Kontroler wykonuje render z kodem statusu równym 200. Mam jednak inną metodę, która wstawia masę w pętlę. W tej metodzie kontroler opuszcza pętlę, gdy napotyka pierwszy zduplikowany wiersz, co nie jest tym, czego chcę. Na początku myślałem, że pętla musi być zawijana w transakcji, która jest wycofywana, ale tak nie jest - wszystkie wiersze przed wstawieniem duplikatu. Chcę po prostu zignorować wyjątek ograniczenia i przejść do następnego elementu. Jak zapobiec temu wyjątkowi PG :: Error?

Odpowiedz

11

Ogólnie rzecz biorąc, obsługa wyjątków powinna znajdować się w najbliższym punkcie błędu, który można wykonać z wyjątkiem. W twoim przypadku, że chcesz, aby Twój rescue wewnątrz pętli, na przykład:

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 

A parę punktów w okolicy:

  1. Ograniczenie naruszenie wewnątrz bazy danych daje błąd ActiveRecord::RecordNotUnique zamiast podstawowy PG::Error. AFAIK, dostaniesz PG::Error, jeśli rozmawiasz bezpośrednio z bazą danych, zamiast przechodzić przez ActiveRecord.
  2. Zastąp symbol INDEX_NAME_GOES_HERE prawdziwą nazwą unikatowego indeksu.
  3. Należy zignorować tylko określone naruszenie ograniczeń, którego się spodziewasz, stąd bit next if(...), po którym następuje bezargumentowy raise (tzn. Ponownie podnieś wyjątek, jeśli nie jest to oczekiwane).
+0

Dzięki, mu. Wygląda na to, że zadziałało. Próbowałem osadzania "next" w metodzie do_nothing, o której mowa, rescue_with, ale to dało mi błąd. W każdym razie na pewno jest to błąd PG :: i nie mówię bezpośrednio do bazy danych. Jest to po prostu zwykła stara tabela dołączania habtmu bez niestandardowej wstawki SQL. Być może ActiveRecord :: RecordNotUnique jest wywoływany tylko na tabelach modeli? –

+0

Za wcześnie rozmawiałem. Przetestowałem to ponownie i na pewno wciąż kończę pętlę pierwszego napotkanego duplikatu. Interesujące jest to, że jeśli wstawię blok begin/rescue w metodzie kontrolera i wyjmę rescue_with ze sterownika, to wyjątek nie zostanie w ogóle złapany. Tak więc, z jakiegoś powodu, najwyraźniej nie jest to na poziomie metody kontrolera. –

+0

Nie chcesz przechwytywać go na poziomie kontrolera, który jest zbyt daleko od miejsca, w którym można coś zrobić z wyjątkiem. Jak wygląda twój kod? –

1

Po umieszczeniu walidatora Rails w swoim modelu, możesz kontrolować przepływ bez rzucania wyjątku.

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

Następnie można użyć

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

Powróci true, jeśli stowarzyszenie powstało, false jeśli wystąpił błąd. Nie rzuci wyjątku. Zastosowanie FolderItem.create! spowoduje zgłoszenie wyjątku, jeśli powiązanie nie zostanie utworzone.

Powodem, dla którego pojawiają się błędy PG, jest fakt, że same Railsy uważają, że model jest poprawny przy składowaniu, ponieważ klasa modelu nie ma ograniczenia unikalności w Railsach. Oczywiście, masz wyjątkowe ograniczenie w DB, które zaskakuje Railsy i powoduje jego wysadzenie w ostatniej chwili.

Jeśli wydajność jest krytyczna, być może zignorujesz tę radę. Posiadanie ograniczenia unikalności w modelu Rails powoduje, że przed każdym SELECT wykonuje on walidację unikalności na poziomie Rails, potencjalnie podwajając liczbę zapytań, które wykonuje twoja pętla.Samo złapanie błędów na poziomie bazy danych, tak jak robisz, może być rozsądną wymianą elegancji dla wydajności.

(edycja) TL; DR: Zawsze mieć unikalne ograniczenie w DB. Również posiadanie ograniczenia modelu pozwoli na walidację ActiveRecord/ActiveModel zanim DB zgłosi błąd.

+3

Sprawdzanie poprawności unikalności szyn powinno ** zawsze ** być wspierane przez ograniczenie unikalności wewnątrz bazy danych. Wszystkie walidacje ActiveRecord podlegają warunkom wyścigu, więc logika musi znajdować się w bazie danych i musisz radzić sobie z wyjątkami, jeśli nie chcesz zepsutych danych. –

+0

Masz całkowitą rację, nie chciałem sugerować, że jest inaczej. Mówiąc o walidacji modelu, zdecydowanie mam na myśli to jako dodatek do wyjątkowego ograniczenia w bazie danych. Potrzebujesz integralności w bazie danych dla integralności i ograniczenia w walidacji Railsów dla ActiveRecord/ActiveModel. –