# coding: utf-8

Предостережение уникальности

Есть одна небольшая проблема, предостережение, на которое я ссылался выше:

Использование validates :uniqueness не гарантирует уникальности.

D’oh! Но что может может пойти не так? А вот что:


  1. Алиса регистрируется на сайте, с адресом [email protected].
  2. Alice случайно кликает, “Submit” дважду, отправляя два запроса в быстрой последовательности.
  3. Затем происходит следующее: запрос 1 создает пользователя в памяти, который проходит проверку, запрос 2 делает то же самое, запрос 1'й пользователя сохраняется, запрос 2 в пользователя сохраняется.
  4. Результат: две пользовательские записи с одинаковыми адресами электронной почты, несмотря на валидацию уникальности.

Если вышеописанная последовательность кажется неправдоподобной, поверьте мне, это не так: это происходит на любом Rails сайте со значительным трафиком.21 К счастью, решение просто в реализации, нам просто необходимо обеспечить уникальность также на уровне базы данных. Наш метод заключается в создании в базе данных индекса столбца электронной почты и последующем требовании уникальности индекса.

Индекс электронной почты представляет собой обновление требований к нашей модели данных, что (как обсуждалось в Разделе 6.1.1) делается в Rails посредством миграций. Мы видели в Разделе 6.1.1 что генерация модели User автоматически создает новую миграцию (Листинг 6.2); в данном случае мы добавляем структуру к существующей модели, таким образом, мы должны создать миграцию непосредственно, используя migration генератор:

$ rails generate migration add_email_uniqueness_index

В отличие от миграции для пользователей, миграция уникальности электронной почты не предопределена, таким образом, мы должны заполнить ее содержание кодом из Листинга 6.22. 22

Листинг 6.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).

# coding: utf-8