Sprawdzanie unikalności wielu kolumn
Czy istnieje sposób rails-way, aby potwierdzić, że rzeczywisty rekord jest unikalny, a nie tylko kolumna? Na przykład, model / tabela przyjaźni nie powinna mieć wielu identycznych rekordów, takich jak:
user_id: 10 | friend_id: 20
user_id: 10 | friend_id: 20
4 answers
Możesz wykonać validates_uniqueness_of
wywołanie w następujący sposób.
validates_uniqueness_of :user_id, :scope => :friend_id
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-03 17:38:35
Możesz użyć validates
aby zweryfikować uniqueness
w jednej kolumnie:
validates :user_id, uniqueness: {scope: :friend_id}
Składnia walidacji dla wielu kolumn jest podobna, ale zamiast tego powinieneś podać tablicę pól:
validates :attr, uniqueness: {scope: [:attr1, ... , :attrn]}
jednak , metody walidacji pokazane powyżej mają warunek rasy i nie mogą zapewnić spójności. Rozważmy następujący przykład:
-
Rekordy tabeli bazy danych powinny być unikalne przez N pola;
-
Wielokrotne ( dwa lub więcej ) jednoczesne żądania, obsługiwane przez oddzielne procesy każdy ( serwery aplikacji, serwery robocze w tle lub cokolwiek, czego używasz ), dostęp do bazy danych, aby wstawić ten sam rekord w tabeli;
-
Każdy proces równolegle waliduje, jeśli istnieje rekord z tymi samymi polami n;
Walidacja dla każdego żądania jest przekazywana pomyślnie, a każdy proces tworzy rekord w tabeli z tym samym data.
Aby uniknąć tego typu zachowań, należy dodać unikalne ograniczenie do tabeli db. Można go ustawić za pomocą add_index
helper dla jednego (lub wielu) pól poprzez uruchomienie następującej migracji:
class AddUniqueConstraints < ActiveRecord::Migration
def change
add_index :table_name, [:field1, ... , :fieldn], unique: true
end
end
Zastrzeżenie: nawet po ustawieniu unikalnego ograniczenia, dwa lub więcej jednoczesnych żądań spróbuje zapisać te same dane do db, ale zamiast tworzyć zduplikowane rekordy, spowoduje to ActiveRecord::RecordNotUnique
wyjątek, który należy uchwyt osobno:
begin
# writing to database
rescue ActiveRecord::RecordNotUnique => e
# handling the case when record already exists
end
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-20 16:36:29
Prawdopodobnie potrzebujesz rzeczywistych ograniczeń db, ponieważ validates cierpi z powodu warunków wyścigowych.
validates_uniqueness_of :user_id, :scope => :friend_id
Kiedy utrzymujesz instancję użytkownika, Rails potwierdzi twój model, uruchamiając zapytanie SELECT, aby sprawdzić, czy jakiekolwiek rekordy użytkownika już istnieją z podanym identyfikatorem user_id. Zakładając, że rekord okaże się prawidłowy, Rails uruchomi instrukcję INSERT, aby utrzymać użytkownika. Działa to świetnie, jeśli używasz pojedynczej instancji pojedynczego procesu / serwera www wątku.
W przypadku dwóch procesy / wątki próbują utworzyć użytkownika o tym samym identyfikatorze user_id mniej więcej w tym samym czasie, może pojawić się następująca sytuacja.
Z unikalnymi indeksami db na miejscu, powyższa sytuacja rozegra się w następujący sposób.
ODPOWIEDŹ zaczerpnięta z tego wpisu na blogu- http://robots.thoughtbot.com/the-perils-of-uniqueness-validations
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-17 18:14:17
Można to zrobić za pomocą ograniczenia bazy danych na dwóch kolumnach:
add_index :friendships, [:user_id, :friend_id], unique: true
Możesz użyć walidatora rails, ale ogólnie polecam użycie ograniczenia bazy danych.
Więcej czytań: https://robots.thoughtbot.com/validation-database-constraint-or-both
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-01 22:01:50