Предостережение уникальности
Есть одна небольшая проблема, предостережение, на которое я ссылался выше:
Использование validates :uniqueness
не гарантирует уникальности.
D’oh! Но что может может пойти не так? А вот что:
- Алиса регистрируется на сайте, с адресом
[email protected]
. - Alice случайно кликает, “Submit” дважду, отправляя два запроса в быстрой последовательности.
- Затем происходит следующее: запрос 1 создает пользователя в памяти, который проходит проверку, запрос 2 делает то же самое, запрос 1'й пользователя сохраняется, запрос 2 в пользователя сохраняется.
- Результат: две пользовательские записи с одинаковыми адресами электронной почты, несмотря на валидацию уникальности.
Если вышеописанная последовательность кажется неправдоподобной, поверьте мне, это не так: это происходит на любом Rails сайте со значительным трафиком.21 К счастью, решение просто в реализации, нам просто необходимо обеспечить уникальность также на уровне базы данных. Наш метод заключается в создании в базе данных индекса столбца электронной почты и последующем требовании уникальности индекса.
Индекс электронной почты представляет собой обновление требований к нашей модели данных, что (как обсуждалось в Разделе 6.1.1) делается в Rails посредством миграций. Мы видели в Разделе 6.1.1 что генерация модели User автоматически создает новую миграцию (Листинг 6.2); в данном случае мы добавляем структуру к существующей модели, таким образом, мы должны создать миграцию непосредственно, используя migration
генератор:
$ rails generate migration add_email_uniqueness_index
В отличие от миграции для пользователей, миграция уникальности электронной почты не предопределена, таким образом, мы должны заполнить ее содержание кодом из Листинга 6.22. 22
db/migrate/<timestamp>_add_email_uniqueness_index.rb
class AddEmailUniquenessIndex < ActiveRecord::Migration
def self.up
add_index :users, :email, :unique => true
end
def self.down
remove_index :users, :email
end
end
Это использует Rails метод add_index
чтобы добавить индекс на столбце email
таблицы users
. Индекс сам по себе не обеспечивает уникальность, но это делает опция :unique => true
.
Заключительный шаг должен переместить базу данных:
$ rake db:migrate
Теперь вышеописанный сценарий с Алисой будет хорошо работать: база данных сохранит запись пользователя, основанную на первом запросе, и отвергнет второе сохранение за нарушение уникальности. (Ошибка появится в логе Rails, но в этом нет ничего плохого. Можно фактически поймать ActiveRecord::StatementInvalid
исключение that gets raised—см. Insoshi для примера—но в этом учебном руководстве мы не будем заморачиваться этим шагом.) Добавление этого индекса на атрибут электронной почты преследует вторую цель, кратко рассмотренную в Разделе 6.1.4: он решает проблему find_by_email
(Блок 6.2).