Oto różnica między unikalnym indeksem a validates_uniqueness_of
Jest to poprawka umożliwiająca ActiveRecord identyfikowanie błędów generowanych przez bazę danych w przypadku naruszeń unikatowych ograniczeń. Na przykład, wykonuje następującą pracę bez deklarowania validates_uniqueness_of:
create_table "users" do |t|
t.string "email", null: false
end
add_index "users", ["email"], unique: true
class User < ActiveRecord::Base
end
User.create!(email: '[email protected]')
u = User.create(email: '[email protected]')
u.errors[:email]
=> "has already been taken"
Korzyści to szybkość, łatwość użycia i kompletność --
Prędkość
Dzięki takiemu podejściu nie musisz wyszukiwać bazy danych, aby sprawdzić unikalność podczas zapisywania (co czasami może być dość powolne w przypadku pominięcia indeksu -- https://rails.lighthouseapp.com/projects/8994/tickets/2503-validate.. . ). Jeśli naprawdę zależy Ci na walidacji unikalności, i tak będziesz musiał użyć ograniczeń bazy danych, aby baza danych sprawdzała poprawność unikalności bez względu na wszystko, a to podejście usuwa dodatkowe zapytanie. Dwukrotne sprawdzenie indeksu nie stanowi problemu dla bazy danych (jest buforowana za drugim razem), ale zapisanie w obie strony bazy danych z aplikacji to wielka wygrana.
Łatwość użytkowania
Biorąc pod uwagę, że i tak musisz mieć ograniczenia bazy danych dla prawdziwej unikalności, to podejście pozwoli, aby wszystko stało się automatycznie po ustanowieniu ograniczeń bazy danych. Możesz nadal używać validates_uniqueness_of, jeśli chcesz.
Kompletność
validates_uniqueness_of zawsze był trochę hackiem — nie radzi sobie poprawnie z warunkami wyścigu i powoduje wyjątki, które muszą być obsługiwane przy użyciu nieco nadmiarowej logiki obsługi błędów. (Zobacz sekcję „Współbieżność i integralność” w http://api.rubyonrails .org/classes/ActiveRecord/Validations/ClassMe... )
validates_uniqueness_of nie wystarczy, aby zapewnić niepowtarzalność wartości. Powodem tego jest to, że w produkcji wiele procesów roboczych może powodować wyścigi:
-
Dwa jednoczesne żądania próbują utworzyć użytkownika o tej samej nazwie (i chcemy, aby nazwy użytkowników były unikalne)
-
Żądania są akceptowane na serwerze przez dwa procesy robocze, które będą teraz przetwarzać je równolegle
-
Oba żądania skanują tabelę użytkowników i widzą, że nazwa jest dostępna
-
Oba żądania przechodzą weryfikację i tworzą użytkownika o pozornie dostępnej nazwie
Aby uzyskać lepsze zrozumienie, sprawdź to
Jeśli utworzysz unikalny indeks dla kolumny, oznacza to, że masz gwarancję, że tabela nie będzie miała więcej niż jednego wiersza o tej samej wartości dla tej kolumny. Używanie tylko validates_uniqueness_of walidacji w swoim modelu nie wystarczy, aby wymusić unikalność, ponieważ mogą istnieć współbieżni użytkownicy próbujący utworzyć te same dane.
Wyobraź sobie, że dwóch użytkowników próbuje zarejestrować konto z tym samym adresem e-mail, do którego dodałeś validates_uniqueness_of :email w swoim modelu użytkownika. Jeśli w tym samym czasie nacisną przycisk „Zarejestruj się”, Railsy poszukają tego e-maila w tabeli użytkowników i odpowiedzą, że wszystko jest w porządku i że można zapisać rekord w tabeli. Railsy następnie zapiszą te dwa rekordy w tabeli użytkownika z tym samym e-mailem i teraz masz naprawdę gówniany problem do rozwiązania.
Aby tego uniknąć, musisz również utworzyć unikalne ograniczenie na poziomie bazy danych:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :email
...
end
add_index :users, :email, unique: true
end
end
Tak więc tworząc unikalny indeks index_users_on_email otrzymujesz dwie bardzo miłe korzyści. Integralność danych i dobra wydajność, ponieważ unikalne indeksy są zwykle bardzo szybkie.
Jeśli umieścisz unique:true w tabeli postów dla identyfikatora użytkownika, nie pozwoli to na wprowadzenie zduplikowanych rekordów o tym samym identyfikatorze użytkownika.