Zweryfikuj unikalność wielu kolumn

193

Czy istnieje sposób na sprawdzenie, czy 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
re5et
źródło
7
wybacz mi, jeśli jestem gęsty, ale jak to by pomogło w tej sytuacji?
re5et
2
spróbuj użyć „validates_uniqueness_of” w swoim modelu. jeśli to nie zadziała, spróbuj utworzyć indeks, na którym możesz utworzyć migrację cech, która zawiera instrukcję taką jak add_index: table, [: column_a,: column_b],: unique => true)
Harry Joy
1
@HarryJoy, zapytał Is there a rails-way way. A ty oferujesz mu drogę bez szyn, ale standard. The Active Record way claims that intelligence belongs in your models, not in the database.
Zielony
2
Niestety validates :field_name, unique: truejest podatny na warunki wyścigowe, więc nawet w przypadku torów, preferowane jest rzeczywiste ograniczenie. @HarryJoy Głosuję za odpowiedzią opisującą sposób ograniczenia.
Pooyan Khosravi
1
lepsza odpowiedź niż wszystkie wymienione poniżej to ten jeden stackoverflow.com/a/34425284/1612469, ponieważ przynosi kolejną warstwę, aby upewnić się, że wszystko będzie działało poprawnie
Aleks

Odpowiedzi:

319

Możesz zawęzić validates_uniqueness_ofrozmowę w następujący sposób.

validates_uniqueness_of :user_id, :scope => :friend_id
Dylan Markow
źródło
83
Chciałem tylko dodać, że możesz przekazać wiele parametrów zakresu na wypadek, gdybyś musiał zweryfikować unikalność na więcej niż 2 polach. To znaczy: scope => [: friend_id,: group_id]
Dave Rapin
27
Dziwne, że nie możesz powiedzieć validates_uniqueness_of [:user_id, :friend_id]. Może to trzeba załatać?
Alexey,
12
Alexey, validates_uniqueness_of [: user_id,: friend_id] dokona tylko sprawdzenia poprawności dla każdego z wymienionych pól - i jest to udokumentowane i oczekiwane zachowanie
Nikita Hismatov
71
W Rails 4 jest to: validates: user_id, unikalność: {scope:: friend_id}
Marina Martin
3
Prawdopodobnie chcesz dodać niestandardowy komunikat o błędzie, taki jak: komunikat => „ma już tego znajomego”.
laffuste
137

Możesz użyć validatesdo sprawdzenia poprawności uniquenessw jednej kolumnie:

validates :user_id, uniqueness: {scope: :friend_id}

Składnia sprawdzania poprawności w wielu kolumnach jest podobna, ale zamiast tego należy podać tablicę pól:

validates :attr, uniqueness: {scope: [:attr1, ... , :attrn]}

Jednak przedstawione powyżej metody walidacji mają warunek wyścigu i nie mogą zapewnić spójności. Rozważ następujący przykład:

  1. rekordy tabeli bazy danych powinny być unikalne dla n pól;

  2. wiele ( dwóch lub więcej ) równoczesnych żądań, obsługiwanych przez osobne procesy ( serwery aplikacji, serwery procesów roboczych w tle lub cokolwiek używasz ), dostęp do bazy danych w celu wstawienia tego samego rekordu do tabeli;

  3. każdy proces równolegle sprawdza, czy istnieje rekord z tymi samymi n polami;

  4. sprawdzanie poprawności dla każdego żądania zostało pomyślnie zakończone, a każdy proces tworzy rekord w tabeli z tymi samymi danymi.

Aby uniknąć tego rodzaju zachowania, należy dodać unikalne ograniczenie do tabeli db. Możesz ustawić go za pomocą add_indexpomocnika dla jednego (lub wielu) pól, uruchamiając następującą migrację:

class AddUniqueConstraints < ActiveRecord::Migration
  def change
   add_index :table_name, [:field1, ... , :fieldn], unique: true
  end
end

Uwaga : nawet po ustawieniu unikalnego ograniczenia dwa lub więcej współbieżnych żądań spróbuje zapisać te same dane w db, ale zamiast tworzyć duplikaty rekordów, pojawi się ActiveRecord::RecordNotUniquewyjątek, który należy obsługiwać osobno:

begin
# writing to database
rescue ActiveRecord::RecordNotUnique => e
# handling the case when record already exists
end 
potashin
źródło