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
Author: potashin, 2011-02-02

4 answers

Możesz wykonać validates_uniqueness_of wywołanie w następujący sposób.

validates_uniqueness_of :user_id, :scope => :friend_id
 310
Author: Dylan Markow,
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:

  1. Rekordy tabeli bazy danych powinny być unikalne przez N pola;

  2. 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;

  3. Każdy proces równolegle waliduje, jeśli istnieje rekord z tymi samymi polami n;

  4. 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 
 120
Author: potashin,
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. Stan wyścigu z walidacjami

Z unikalnymi indeksami db na miejscu, powyższa sytuacja rozegra się w następujący sposób. Unikalne indeksy w db

ODPOWIEDŹ zaczerpnięta z tego wpisu na blogu- http://robots.thoughtbot.com/the-perils-of-uniqueness-validations

 33
Author: Deepak Azad,
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

 1
Author: Tate Thurston,
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