LoginSignup
6
4

More than 3 years have passed since last update.

【Rails】メールアドレスの一意性の検証【Rails Tutorial 6章まとめ】

Posted at

メールアドレスはユーザーごとに一意であり、重複してはならない。
既存のユーザーと同じメールアドレスを持つユーザーは無効となるように、テストを書く。

test/models/user_test.rb
  test "email addresses should be unique" do
    duplicate_user = @user.dup
    @user.save
    assert_not duplicate_user.valid?
  end

dupメソッドは、@userインスタンスをコピーする。
@userが保存された後は、duplicate_userは無効でなければならない。

emailのバリデーションにuniqunessオプションを追加して、重複できないようにする。

app/models/user.rb
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: true

メールアドレスの大文字小文字

ところで、メールアドレスは大文字小文字を区別しないらしい。
つまり、foo@bar.comFOO@BAR.COMFoO@BAr.coMは同じである。

そこで、重複するユーザーのメールアドレスは、既存ユーザーのメールアドレスを大文字にして入れる。

test/models/user_test.rb
  test "email addresses should be unique" do
    duplicate_user = @user.dup
    duplicate_user.email = @user.email.upcase
    @user.save
    assert_not duplicate_user.valid?
  end

メールアドレスの文字列を大文字にするために、upcaseメソッドを使っている。

現在のバリデーションでは、メールアドレスの大文字小文字を区別しているので、テストは失敗する。
@user.emailとduplicate_user.email(= @user.email.upcase)は別物と認識されるので、duplicate_userが有効となるからである。

メールアドレスの大文字小文字を区別しないようにするためには、uniquenessオプションにcase_sensitive: falseを設定する。

app/models/user.rb
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }

テストをして、成功することを確認する。

データベースレベルでの一意性の検証とインデックス

ユーザー登録ボタンを2回クリックしたりすると、データベースに同じユーザーが二つ作成されてしまうことがあるらしい。
たまに「注文ボタンは一回だけ押してください」とか、「ボタンをクリックした後、ページの読み込みが遅くてもそのままお待ちください」とか書いてあることがあるが、それのことである。

これを解決するために、Userモデルのemailカラムにインデックスを付け、その一意性を設定する。
インデックスは本来、データベースの検索を高速化するためのものである。

Userモデルを変更するため、マイグレーションファイルを作成し、インデックスを追加する。

$ rails generate migration add_index_to_users_email
db/migrate/[timestamp]_add_index_to_users_email.rb
class AddIndexToUsersEmail < ActiveRecord::Migration[5.0]
  def change
    add_index :users, :email, unique: true
  end
end

uniqueオプションをtrueとすることで、インデックスの一意性が保たれる。
ところで、バリデーションでは"uniqueness"と名詞だったのに、インデックスでは"unique"と形容詞なのはなぜ?

マイグレーションを実行する。

rails db:migrate

ここで、テスト用のユーザーデータを入れておくfixtureファイルに、同じメールアドレスが設定されたユーザーが存在しているために、テストが失敗する。

test/fixtures/users.yml
one:
  name: MyString
  email: MyString

two:
  name: MyString
  email: MyString

このfixtureファイルは、Userモデルを生成した際にできたものである。
これを空にしておき、テストが成功することを確認する。

test/fixtures/users.yml
# 空にする (既存のコードは削除する)

メモ:なぜかここでエラーが出た。

ActiveRecord::PendingMigrationError (

Migrations are pending. To resolve this issue, run:

    bin/rake db:migrate RAILS_ENV=development

):

インデックス用のマイグレーションファイルを削除して、db:migrateを実行。
マイグレーションファイルを作り直してdb:migrateを再び実行すると直った。

もう一つの問題

さらにもう一つ問題があるらしい。
「いくつかのデータベースのアダプタが、常に大文字小文字を区別するインデックス を使っているとは限らない問題への対処です。例えば、Foo@ExAMPle.Comfoo@example.comが別々の文字列だと解釈してしまうデータベースがあります」
とのこと。

データベースのアダプタとやらがイマイチよく分からないが、とにかくこれを解決するため、データベースにメールアドレスを保存する際には小文字に変換して保存するようにする。
before_saveメソッドを使う。

app/models/user.rb
class User < ApplicationRecord
  before_save { self.email = self.email.downcase }
  validates :name,  presence: true, length: { maximum: 50 }
  .
  .
  .
end

メールアドレスを小文字化して代入し直す。
selfは保存されるUserインスタンスを指す。

self.email = self.email.downcase

は、右側のselfを省略して

self.email = email.downcase

とも書ける。

!を使って次のように書くと、email属性を直接変更できるようである。

email.downcase!

小文字化に対するテスト

メールアドレスが保存される際に、小文字化されているかをテストする。

test/models/user_test.rb
 test "email addresses should be saved as lower-case" do
    mixed_case_email = "Foo@ExAMPle.CoM"
    @user.email = mixed_case_email
    @user.save
    assert_equal mixed_case_email.downcase, @user.reload.email
  end

注:最後のassert_equalで、なぜかreloadメソッドを使用している。
@userは保存された後なので、reloadをしても特に意味はないはずである。
実際にコンソールで確認したが、@user.emailと@user.reload.emailは同じ値を返し、reloadを除いてもテストに問題はなかった。

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4