Railsチュートリアル 第6章 ユーザーのモデルを作成する - ユーザーを検証する

# 現状のUserモデルの問題点

## `name`属性と`email`属性に、あらゆる文字列を取ることができる

以下は、`name`属性の値が`nil`であるレコードがRDB上に存在することを示す例です。現状のUserモデルの実装では、このようなデータも有効とされてしまいます。

```
>> User.find(4)
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 4], ["LIMIT", 1]]
=> #<User id: 4, name: nil, email: "foo@bar.com", created_at: "2019-10-03 11:06:14", updated_at: "2019-10-03 11:06:14">
```

さらに、`email`属性の値にも`nil`を取ることができてしまいます。

```
>> User.find(4).update_attributes(email: nil)
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 4], ["LIMIT", 1]]
   (0.1ms)  SAVEPOINT active_record_1
  SQL (0.2ms)  UPDATE "users" SET "email" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["email", nil], ["updated_at", "2019-10-03 11:17:27.810162"], ["id", 4]]
   (0.3ms)  RELEASE SAVEPOINT active_record_1
=> true
>> User.find(4)
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 4], ["LIMIT", 1]]
=> #<User id: 4, name: nil, email: nil, created_at: "2019-10-03 11:06:14", updated_at: "2019-10-03 11:17:27">
```

## あるべき姿

ユーザーアカウントを内容とするRDBのレコードの場合、例えば以下のような制約は実装されていてほしいものです。

* `name`属性は空であってはならない
* `email`属性はメールアドレスのフォーマットに従う書式である
* `email`属性は一意の値である必要がある
    * ログイン時のユーザー名として、メールアドレスを用いる予定であるため

# 検証/バリデーションとは

Railsの文脈において、検証ないしバリデーションという言葉は、「属性値に対して何らかの制約を課すためのActive Recordの機能」を指します。よく使われる制約の一例としては、以下のようなものが挙げられます。

* 存在性の制約
* 長さの制約
* フォーマットの制約
* 一意性の制約

## バリデーション実装を題材とした、テスト駆動開発の基本の習得

バリデーション実装というのは、テスト駆動開発と相性がよい対象といえます。その理由は以下の通りです。

* 「値に対してルールを適用する」という動作は、テストの成功・失敗を定義しやすいこと
    * 状態に依存しないため、特に困難なくテストを実装できる
* バリデーションが失敗した場合、結果としてやや複雑な動作が要求されること
    * どのような理由でバリデーションが通らなかったかをユーザーに提示する動作
    * RDBに当該変更を反映しない動作
* やや複雑な動作であるため、テストなしでは正常動作の立証が困難であること

というわけで、Railsチュートリアルにおいては、バリデーション実装を題材としてテスト駆動開発を行っています。

具体的には、以下のような手法で開発を進めていくということです。

1. 有効なモデルであれば通るようなテストを書く
2. 有効なモデルのオブジェクトを作成する
    * この時点ではテストは通るはず
3. 作成したオブジェクトの属性のうち、1つを有効でない属性に意図的に変更する
4. バリデーションで失敗するかどうかをテストする
    * この時点でテストが失敗する

### テストコードの実装

モデルに対するテストのモックは、`rails generate model`コマンドの実行結果として生成されます。例えば、現在題材としているUserモデルに対しては、以下のモックが既に存在します。

```ruby:test/models/user_test.rb
require 'test_helper'

class UserTest < ActiveSupport::TestCase
  # test "the truth" do
  #   assert true
  # end
end
```

見ての通り、現時点では単なるモックです。この時点で重要なのは、「Railsにおけるモデルに対するテストクラスは、`ActiveSupport::TestCase`というクラスを継承している」という点でしょうか。コントローラに対するテストが`ActionDispatch::IntegrationTest`というクラスを継承していたのとは違いますね。

ここから実際のテストを書いていきましょう。まずは、有効なUserかどうかをテストするコードです。

```ruby:est/models/user_test.rb
require 'test_helper'

class UserTest < ActiveSupport::TestCase

  def setup
    @user = User.new(name: "Example User", email: "user@example.com")
  end

  test "should be valid" do
    assert @user.valid?
  end
end
```

テストメソッドにおいて`setup`というメソッドが使われています。Railsのテストフレームワークにおいて、「各テストが走る直前に実行される処理」を記述するためのメソッドです。`@user`はインスタンス変数ですが、setupメソッド内で宣言しておけば、すべてのテスト内で当該インスタンス変数が使えるようになります。

というわけで、今書いたテストコードによって、`valid?`メソッドを使ってUserオブジェクトの有効性をテストできる、というわけです。

### 初めてのテストコード実行

モデルオブジェクトに対するテストを実行するには、開発環境で`rails test:models`というコマンドを実行します。

```
# rails test:models
Started with run options --seed 19540

  1/1: [===================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.08566s
1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
```

#### 余談 - 誤って`rails test:model`というコマンドを実行してしまうとどうなるか

誤って`rails test:model`というコマンドを実行してしまうと、以下のようにスタックトレースを含む長いエラーメッセージが表示されます。

```
# rails test:model
rails aborted!
Don't know how to build task 'test:model' (See the list of available tasks with `rails --tasks`)
Did you mean?  test:models
/usr/local/bundle/gems/railties-5.1.6/lib/rails/commands/rake/rake_command.rb:21:in `block in perform'
/usr/local/bundle/gems/railties-5.1.6/lib/rails/commands/rake/rake_command.rb:18:in `perform'
/usr/local/bundle/gems/railties-5.1.6/lib/rails/command.rb:46:in `invoke'
/usr/local/bundle/gems/railties-5.1.6/lib/rails/commands.rb:16:in `<top (required)>'
/var/www/sample_app/bin/rails:9:in `require'
/var/www/sample_app/bin/rails:9:in `<top (required)>'
/usr/local/bundle/gems/spring-2.0.2/lib/spring/client/rails.rb:28:in `load'
/usr/local/bundle/gems/spring-2.0.2/lib/spring/client/rails.rb:28:in `call'
/usr/local/bundle/gems/spring-2.0.2/lib/spring/client/command.rb:7:in `call'
/usr/local/bundle/gems/spring-2.0.2/lib/spring/client.rb:30:in `run'
/usr/local/bundle/gems/spring-2.0.2/bin/spring:49:in `<top (required)>'
/usr/local/bundle/gems/spring-2.0.2/lib/spring/binstub.rb:31:in `load'
/usr/local/bundle/gems/spring-2.0.2/lib/spring/binstub.rb:31:in `<top (required)>'
/var/www/sample_app/bin/spring:15:in `<top (required)>'
bin/rails:3:in `load'
bin/rails:3:in `<main>'
(See full trace by running task with --trace)
```

## 演習 - バリデーション実装を題材とした、テスト駆動開発の基本の習得

### 1. コンソールから、新しく生成したuserオブジェクトが有効 (valid) であることを確認してみましょう。

```
>> User.new.valid?
=> true
```

### 2. [6.1.3](https://railstutorial.jp/chapters/modeling_users?version=5.1#sec-creating_user_objects)で生成したuserオブジェクトも有効であるかどうか、確認してみましょう。 

```
>> user.valid?
=> true
```

# 存在性を検証する

存在性の検証とは、「渡された属性が存在することを検証する」ことを言います。この節では、以下の存在性検証を実装します。

* ユーザー情報がRDBに保存される前に、nameフィールドが存在することを検証する
* ユーザー情報がRDBに保存される前に、emailフィールドが存在することを検証する

## `name`属性の存在性に関するテスト

### `name`属性の存在性に関するテストの追加

`test/models/user_test.rb`に、まずは`name`属性の存在性に関するテストを追加します。

```diff:`test/models/user_test.rb`
  require 'test_helper'

  class UserTest < ActiveSupport::TestCase

    def setup
      @user = User.new(name: "Example User", email: "user@example.com")
    end

    test "should be valid" do
      assert @user.valid?
    end
+
+   test "name should be present" do
+     @user.name = "    "
+     assert_not @user.valid?
+   end
  end
```

この時点では、テストが失敗するようになります。

```
# rails test:models
Started with run options --seed 50708

 FAIL["test_name_should_be_present", UserTest, 0.11231679999036714]
 test_name_should_be_present#UserTest (0.11s)
        Expected true to be nil or false
        test/models/user_test.rb:15:in `block in <class:UserTest>'

  2/2: [===================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.13430s
2 tests, 2 assertions, 1 failures, 0 errors, 0 skips
```

### `name`属性の存在を検査するには

モデルオブジェクトのクラス定義において、第1引数を`:name`とした`validates`メソッドを、`{ presence: true }`というオプションハッシュを第2引数として与えて用いることにより実現できます。

```diff:app/models/user.rb
  class User < ApplicationRecord
+   validates :name, presence: true
  end
```

以下の3つのコードは、いずれも同じ意味です。

```ruby:
class User < ApplicationRecord
  validates :name, presence: true
end
```

```ruby:
class User < ApplicationRecord
  validates(:name, presence: true)
end
```

```ruby:
class User < ApplicationRecord
  validates(:name, { presence: true })
end
```

重要なのは以下の事柄です。

* Rubyにおいては、特別な場合を除き、メソッド呼び出しの`()`は省略できる
* 最後の引数がハッシュである場合、当該ハッシュに対する`{}`は省略できる

### Userモデルに検証を追加した結果を確認してみる

コンソールを起動して、Userモデルに検証を追加した結果を見てみます。

```
>> user = User.new(name: "", email: "mhartl@example.com")
=> #<User id: nil, name: "", email: "mhartl@example.com", created_at: nil, updated_at: nil>
>> user.valid?
=> false
```

`valid?`メソッドの戻り値が`false`になっていますね。

どの検証が失敗したかを確認するには、検証が失敗した際に生成される`errors`メソッドを使うと便利です。

```
>> user.errors.full_messages
=> ["Name can't be blank"]
>> user.name.blank?
=> true
```

エラーメッセージにblankとあるので、`blank?`メソッドで確認してみました。確かに`blank?`の結果が`true`になっていますね。

### validでないモデルオブジェクトは、RDBに保存できない

`valid?`が`false`であるようなモデルオブジェクトは、RDBに保存することができません。

```
>> user.save
   (1.6ms)  SAVEPOINT active_record_1
   (0.2ms)  ROLLBACK TO SAVEPOINT active_record_1
=> false
```

`ROLLBACK`されていますね。

### 今度こそテストは成功

ここまで確認したところで、改めて`rails test:models`を実行してみます。

```
# rails test:models
Started with run options --seed 17899

  2/2: [===================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.11375s
2 tests, 2 assertions, 0 failures, 0 errors, 0 skips
```

今度こそテストは成功しました。

## `email`属性の存在性に関するテスト

```diff:test/models/user_test.rb
  require 'test_helper'

  class UserTest < ActiveSupport::TestCase

    def setup
      @user = User.new(name: "Example User", email: "user@example.com")
    end

    test "should be valid" do
      assert @user.valid?
    end

    test "name should be present" do
      @user.name = "    "
      assert_not @user.valid?
    end

+   test "email should be present" do
+     @user.email = "    "
+     assert_not @user.valid?
+   end
  end
```

ここまでの変更に加え、`email`属性の有効性に対するテストを追加するためには、まず`test/models/user_test.rb`に対して上記の変更を行います。

この時点では、以下の通りテストは通りません。

```
# rails test:models
Started with run options --seed 4227

 FAIL["test_email_should_be_present", UserTest, 0.10753690000274219]
 test_email_should_be_present#UserTest (0.11s)
        Expected true to be nil or false
        test/models/user_test.rb:20:in `block in <class:UserTest>'

  3/3: [===================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.12862s
3 tests, 3 assertions, 1 failures, 0 errors, 0 skips
```

現時点でテストが通るようにするためには、`app/models/user.rb`に以下の変更を加えます。

```diff:app/models/user.rb
  class User < ApplicationRecord
    validates :name, presence: true
+   validates :email, presence: true
  end
```

改めてテストを実行します。

```
# rails test:models
Started with run options --seed 54749

  3/3: [===================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.10906s
3 tests, 3 assertions, 0 failures, 0 errors, 0 skips
```

今度こそテストが通りました。

## 改めて全体のテストを実行

全てのテストを実行してみます。

```
# rails test       
Running via Spring preloader in process 2372
Started with run options --seed 40322

  11/11: [=================================] 100% Time: 00:00:03, Time: 00:00:03

Finished in 3.63199s
11 tests, 22 assertions, 0 failures, 0 errors, 0 skips
```

通りました。

# 演習 - 存在性の検証

モデルオブジェクト（`app/models`）に変更を加えた場合、Railsコンソールを再起動しなければ、モデルオブジェクトに対する変更が反映されません。

## 1.1. 新しいユーザー`u`を作成し、作成した時点では有効ではない (invalid) ことを確認してください。

```
>> u = User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>
>> u.valid?
=> false
```

## 1.2. なぜ有効ではないのでしょうか? エラーメッセージを確認してみましょう。

```
>> u.errors.full_messages
=> ["Name can't be blank", "Email can't be blank"]
```

こういうときに使うのは、`errors.full_messages`メソッドですね。結果、以下2つの文字列から成る配列を返してきました。

* Name can't be blank
* Email can't be blank

## 2.1. `u.errors.messages`を実行すると、ハッシュ形式でエラーが取得できることを確認してください。

```
>> u.errors.messages
=> {:name=>["can't be blank"], :email=>["can't be blank"]}
```

## 2.2. emailに関するエラー情報だけを取得したい場合、どうやって取得すれば良いでしょうか? 

`errors.messages`はハッシュを返すのでしたね。ハッシュの`find`メソッドを使います。keyが`:email`である要素を`find`するのです。

```
>> u.errors.messages.find {|k,v| k == :email}
=> [:email, ["can't be blank"]]
```

私事ですが、ハッシュの`find`メソッドを、および、ハッシュに関するブロックの構文を覚えていませんでした。

# 長さを検証する

今度はユーザー名・メールアドレスの長さに制限を与えていきます。

ユーザー名の長さに制限を与える理由として、Railsチュートリアルでは、「ユーザーの名前はサンプルWebサイトに表示される」旨を挙げていました。一方、メールアドレスの長さに制限を与える理由としては、「ほとんどのRDBMSでは文字列の上限を255文字としている」旨を挙げていました。

というわけで、文字列の長さの制限は以下とします。

* Name属性…最長50文字
* Email属性…最長255文字

## 長さに関するテストを追加する

というわけで、以下のコードを`test/models/user_test.rb`に追加していきます。

```diff:test/models/user_test.rb
  require 'test_helper'

  class UserTest < ActiveSupport::TestCase

  ...略
+
+   test "name should not be too long" do
+     @user.name = "a" * 51
+     assert_not @user.valid?
+   end
+
+   test "email should be not to long" do
+     @user.email = "a" * 244 + "@example.com"
+     assert_not @user.valid?
+   end
  end
```

## 長い文字列を作る方法

Rubyでは、文字列のかけ算によって長い文字列を容易に生成することができます。

```
>> "a" * 51
=> "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
>> ("a" * 51).length
=> 51
```

上記は、51文字の文字列を生成した例です。今回の学習では、Name属性のバリデーションに用いています。

```
>> "a" * 244 + "@example.com"
=> "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@example.com"
>> ("a" * 244 + "@example.com").length
=> 256
```

上記は、メールアドレスとして正しい書式になるような256文字の文字列を生成した例です。今回の学習では、Email属性のバリデーションに用いています。

## テストは失敗する

```
# rails test:models
Started with run options --seed 56934

 FAIL["test_name_should_not_be_too_long", UserTest, 0.11051520000910386]
 test_name_should_not_be_too_long#UserTest (0.11s)
        Expected true to be nil or false
        test/models/user_test.rb:25:in `block in <class:UserTest>'

 FAIL["test_email_should_be_not_to_long", UserTest, 0.11830520001240075]
 test_email_should_be_not_to_long#UserTest (0.12s)
        Expected true to be nil or false
        test/models/user_test.rb:30:in `block in <class:UserTest>'

  5/5: [===================================] 100% Time: 00:00:00, Time: 00:00:00
```

現状で`rails test:models`は失敗します。各FAILメッセージの冒頭には、今追加したテストの名前をスネークケースにしたものが来ています。新たに追加したテストが、確かに正しく動作していますね。

## 長さを強制するためのコードをモデルに追加する

Railsのモデルクラスの`validates`メソッドにおいて、最終引数のオプションハッシュで、`:length`というキーを持つ属性が利用できます。`:length`属性は、引数としてハッシュを取ります。`:length`属性の引数たるハッシュに`:maximum`というキーを持つ属性を与えると、`:maximum`属性の値により、モデルオブジェクトの対応する属性の最大長を決めることができます。

早速、Userモデルのコードを書き換えていきましょう。

```diff:app/models/user.rb
  class User < ApplicationRecord
-   validates :name,  presence: true
+   validates :name,  presence: true, length: { maximum: 50 }
-   validates :email, presence: true
+   validates :email, presence: true, length: { maximum: 255 }
  end
```

上記が「Name属性の最大長50文字・Email属性の最大長255文字という制約をUserクラスに追加する」というコードの例です。

## テストが成功した！

Userモデルのコードを書き換えたところで、再び`rails test:models`を実行してみましょう。

```
# rails test:models
Started with run options --seed 55077

  5/5: [===================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.11015s
5 tests, 5 assertions, 0 failures, 0 errors, 0 skips
```

今度こそテストが成功しました。

# 演習 - 長さの検証

## 1. 長すぎる`name`と`email`属性を持ったuserオブジェクトを生成し、有効でないことを確認してみましょう。

```
>> u = User.new(name: "#{("a".."z").to_a.join * 2}", email: "#{("a".."z").to_a.join * 10}@example.net")
=> #<User id: nil, name: "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx...", email: "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx...", created_at: nil, updated_at: nil>
>> u.name.length 
=> 52
>> u.email.length
=> 272
>> u.valid?
=> false
```

上記は「`name`属性も`email`属性も長すぎる」という例です。`valid?`は確かに`false`を返しています。

```
>> u = User.new(name: "#{("a".."z").to_a.join * 2}", email: "#{("a".."z").to_a.join}@example.net")
=> #<User id: nil, name: "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx...", email: "abcdefghijklmnopqrstuvwxyz@example.net", created_at: nil, updated_at: nil>
>> u.name.length
=> 52
>> u.email.length
=> 38
>> u.valid?
=> false
```

上記、今度は「`email`属性の長さは適切だが、`name`属性が長すぎる」という例です。`valid?`は確かに`false`を返しています。

```
>> u = User.new(name: "#{("a".."z").to_a.join}", email: "#{("a".."z").to_a.join * 10}@example.net")
=> #<User id: nil, name: "abcdefghijklmnopqrstuvwxyz", email: "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx...", created_at: nil, updated_at: nil>
>> u.name.length
=> 26
>> u.email.length
=> 272
>> u.valid?
=> false
```

上記、今度は「`name`属性の長さは適切だが、`email`属性が長すぎる」という例です。`valid?`は確かに`false`を返しています。

```
>> u = User.new(name: "#{("a".."z").to_a.join}", email: "#{("a".."z").to_a.join * 2}@example.net")
=> #<User id: nil, name: "abcdefghijklmnopqrstuvwxyz", email: "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx...", created_at: nil, updated_at: nil>
>> u.name.length
=> 26
>> u.email.length
=> 64
>> u.valid?
=> true
```

上記、最後は「`name`属性も`email`属性も適切な長さである」という例です。`valid?`が`true`になりました。

### 余談…`"#{("a".."z").to_a.join}"`の意味

`"abcdefghijklmnopqrstuvwxyz"`という26文字の文字列を得るためのコードです。実際に行われている処理は以下のとおりです。

1. `"a"`から始まり、`"z"`で終わる範囲を生成する
2. 1.で生成した範囲を配列に変換する
3. 2.で生成した配列を文字列に変換する

```
>> ('a'..'z').to_a.join
=> "abcdefghijklmnopqrstuvwxyz"
>> ('a'..'z').to_a.join.length
=> 26
```

この文字列に`* 10`という演算を行えば、260文字の文字列を生成することができます。見事に255文字を超えてきますね。

```
>> (('a'..'z').to_a.join * 10)       
=> "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"
>> (('a'..'z').to_a.join * 10).length
=> 260
```

## 2. 長さに関するバリデーションが失敗した時、どんなエラーメッセージが生成されるでしょうか? 確認してみてください。 

```
>> u = User.new(name: "#{("a".."z").to_a.join * 2}", email: "#{("a".."z").to_a.join * 10}@example.net")
=> #<User id: nil, name: "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx...", email: "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx...", created_at: nil, updated_at: nil>
>> u.valid?
=> false
>> u.errors.full_messages
=> ["Name is too long (maximum is 50 characters)", "Email is too long (maximum is 255 characters)"]
```

* Name is too long (maximum is 50 characters)
* Email is too long (maximum is 255 characters)

以上のエラーメッセージが確認できます。そういえば、`errors`オブジェクトの値は副作用により与えられるのですね。

# フォーマットを検証する

メールアドレスの形式には、やや複雑なルールが存在します。そのため、単に文字数を制限するだけでは、メールアドレスのバリデーションとしては不十分です。というわけで、「メールアドレスとして最低限の体裁を整えていなければならない」という条件を実装しましょう。

## 文字列の配列を作る`%w[]`書式

```
>> %w[foo bar baz]
=> ["foo", "bar", "baz"]
```

以下の例は、`each`メソッドを使って、`addresses`配列の各要素を繰り返し取り出す例です。

```
>> addresses = %w[USER@foo.COM THE_US-ER@foo.bar.org first.last@foo.jp]
=> ["USER@foo.COM", "THE_US-ER@foo.bar.org", "first.last@foo.jp"]

>> addresses.each do |address|
?>   puts address
>> end
USER@foo.COM
THE_US-ER@foo.bar.org
first.last@foo.jp
=> ["USER@foo.COM", "THE_US-ER@foo.bar.org", "first.last@foo.jp"]
```

## テストを記述する

### 有効なメールアドレスに対するテスト

```diff:test/models/user_test.rb
  class UserTest < ActiveSupport::TestCase
  ...略
+   test "email validation should be accept valid addresses" do
+     valid_addresses = %w[user@example.com USER@foo.COM A_US-ER@foo.bar.org first.last@foo.jp alice+bob@baz.cn]
+     valid_addresses.each do |valid_address|
+       @user.email = valid_address
+       assert @user.valid?, "#{valid_address.inspect} should be valid"
+     end
+   end
  end
```

`assert`の第2引数にエラーメッセージを追加していることがポイントです。このようにすれば、どのメールアドレスでテストが失敗したかを特定できます。

```
assert @user.valid?, "#{valid_address.inspect} should be valid"
```

`inspect`メソッドというのは、第4章で出てきたメソッドですね。今回は、詳細な文字列情報を得るために使っています。

このテストを追加した時点では、テストは問題なく通ります。

```
# rails test:models
Started with run options --seed 22570

  6/6: [===================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.10920s
6 tests, 10 assertions, 0 failures, 0 errors, 0 skips
```

一つ前のテスト追加の時点では`5 tests, 5 assertions`だったのが、今回は`6 tests, 10 assertions`になっています。テスト対象のメールアドレスとして、有効なメールアドレス5つから成る配列を与えたからですね。

### 無効なメールアドレスに対するテスト

```diff:test/models/user_test.rb
  class UserTest < ActiveSupport::TestCase

    def setup
      @user = User.new(name: "Example User", email: "user@example.com")
    end

  ...略
+
+   test "email validation should reject invalid addresses" do
+     invalid_addresses = %w[user@example,com user_at_foo.org user.name@example. foo@bar_baz.com foo@bar+baz.com]
+     invalid_addresses.each do |invalid_address|
+       @user.email = invalid_address
+       assert_not @user.valid?, "#{invalid_address.inspect} should be invalid"
+     end
+   end
  end
```

この時点では、テストが失敗するようになります。

```
# rails test:models
Started with run options --seed 33633

 FAIL["test_email_validation_should_reject_invalid_addresses", UserTest, 0.079405600001337]
 test_email_validation_should_reject_invalid_addresses#UserTest (0.08s)
        "user@example,com" should be invalid
        test/models/user_test.rb:45:in `block (2 levels) in <class:UserTest>'
        test/models/user_test.rb:43:in `each'
        test/models/user_test.rb:43:in `block in <class:UserTest>'

  7/7: [===================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.13012s
7 tests, 11 assertions, 1 failures, 0 errors, 0 skips
```

### 有効なメールアドレスであるかどうかを判別する、実用的な正規表現

Railsチュートリアルでは、「有効なメールアドレスであるかどうかを判別する実用的な正規表現」として、以下のものが挙げられていました。

```
\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z
```

この正規表現の意味については、Railsチュートリアル本文内[表6.1](https://railstutorial.jp/chapters/modeling_users?version=5.1#table-valid_email_regex)にまとめられています。

#### 上述正規表現の問題点

ただし、この正規表現を有効なメールアドレスかどうかの判別に使うことは、日本においては若干の問題があります。以下のような「有効でないにもかかわらず、未だ日本国内で使われている可能性があるメールアドレス」が有効であると判定されてしまうためです。

* `.user@example.com`（メールアドレスの先頭に`.`がある）
* `user.@example.com`（`@`の直前に`.`がある）
* `foo..bar@example.com`（`@`より前で`.`が連続している）

これらの有効でないメールアドレスは、2009年3月以前のNTTドコモやauにおいて、キャリアメールのメールアドレスとして作成することができてしまっていたものです。

<img width="1054" alt="スクリーンショット 2019-10-09 7.42.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323778/79e12d52-a4e3-4ddd-6941-b634bed5ab2b.png">

上記スクリーンショットのとおり、[Rubular](http://www.rubular.com/)で試した場合も上述した有効でないメールアドレスの例が有効であると判定されています。

### メールアドレスの有効性判定をUserモデルに追加する

上述有効でないメールアドレスの問題はさておき、正規表現によるメールアドレスの有効性判定をUserモデルに実装してみましょう。

```diff:app/models/user.rb
  class User < ApplicationRecord
    validates :name,  presence: true, length: { maximum: 50 }
+   VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
-   validates :email, presence: true, length: { maximum: 255 }   
+   validates :email, presence: true, length: { maximum: 255 },
+                     format: { with: VALID_EMAIL_REGEX }
  end
```

これでテストが通るようになります。

```
# rails test:models
Started with run options --seed 38765

  7/7: [===================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.13943s
7 tests, 15 assertions, 0 failures, 0 errors, 0 skips
```

assertionsの数が11から15に増えました。いいですねぇ。

# 演習 - フォーマットの検証

## 1. [リスト 6.18](https://railstutorial.jp/chapters/modeling_users?version=5.1#code-email_format_valid_tests)にある有効なメールアドレスのリストと、[リスト 6.19](https://railstutorial.jp/chapters/modeling_users?version=5.1#code-email_format_validation_tests)にある無効なメールアドレスのリストをRubularのYour test string:に転記してみてください。その後、[リスト 6.21](https://railstutorial.jp/chapters/modeling_users?version=5.1#code-validates_format_of_email)の正規表現をYour regular expression:に転記して、有効なメールアドレスのみがすべてマッチし、無効なメールアドレスはすべてマッチしないことを確認してみましょう。

<img width="1054" alt="スクリーンショット 2019-10-09 8.05.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323778/8c866ec5-b9bb-c61d-07df-cf82f9c5c498.png">

## 2. 先ほど触れたように、[リスト 6.21](https://railstutorial.jp/chapters/modeling_users?version=5.1#code-validates_format_of_email)のメールアドレスチェックする正規表現は、foo@bar..comのようにドットが連続した無効なメールアドレスを許容してしまいます。まずは、このメールアドレスを[リスト 6.19](https://railstutorial.jp/chapters/modeling_users?version=5.1#code-email_format_validation_tests)の無効なメールアドレスリストに追加し、これによってテストが失敗することを確認してください。

<img width="1054" alt="スクリーンショット 2019-10-09 8.06.32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323778/eb73f1ba-f5fc-ab30-7644-fee34c8ec2d9.png">

「foo@bar..comのようにドットが連続した無効なメールアドレスを許容する」というのは、上述スクリーンショットのような挙動をいいます。「`@`以降で`.`が連続している」というパターンですね。

このようなメールアドレスを無効と判定できるような実装を追加したい…その第一段階としてのテストの追加です。

```diff:test/models/user_test.rb
  class UserTest < ActiveSupport::TestCase

    def setup
      @user = User.new(name: "Example User", email: "user@example.com")
    end

  ..略

    test "email validation should reject invalid addresses" do
-     invalid_addresses = %w[user@example,com user_at_foo.org user.name@example.foo@bar_baz.com foo@bar+baz.com]
+     invalid_addresses = %w[user@example,com user_at_foo.org user.name@example.foo@bar_baz.com foo@bar+baz.com foo@bar..com]
      invalid_addresses.each do |invalid_address|
        @user.email = invalid_address
        assert_not @user.valid?, "#{invalid_address.inspect} should be invalid"
      end
    end
  end
```

この時点で、テストを走らせてみます。

```
# rails test:models
Started with run options --seed 27813

 FAIL["test_email_validation_should_reject_invalid_addresses", UserTest, 0.1293631999869831]
 test_email_validation_should_reject_invalid_addresses#UserTest (0.13s)
        "foo@bar..com" should be invalid
        test/models/user_test.rb:45:in `block (2 levels) in <class:UserTest>'
        test/models/user_test.rb:43:in `each'
        test/models/user_test.rb:43:in `block in <class:UserTest>'

  7/7: [===================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.13355s
7 tests, 16 assertions, 1 failures, 0 errors, 0 skips
```

期待通り（？）、テストは失敗しました。`"foo@bar..com" should be invalid`というメッセージ内容を見るに、失敗してほしいところできちんと失敗しているようですね。

## 2.2. 次に、[リスト 6.23](https://railstutorial.jp/chapters/modeling_users?version=5.1#code-better_email_regex)で示した、少し複雑な正規表現を使ってこのテストがパスすることを確認してください。

`app/models/user.rb`を書き換えていきます。

```diff:app/models/user.rb
  class User < ApplicationRecord
    validates :name,  presence: true, length: { maximum: 50 }
-   VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
+   VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
    validates :email, presence: true, length: { maximum: 255 },
                      format: { with: VALID_EMAIL_REGEX }
  end
```

テストします。

```
# rails test:models
Started with run options --seed 39103

  7/7: [===================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.13441s
7 tests, 16 assertions, 0 failures, 0 errors, 0 skips
```

テストが通りました。

## ３. foo@bar..comをRubularのメールアドレスのリストに追加し、[リスト 6.23](https://railstutorial.jp/chapters/modeling_users?version=5.1#code-better_email_regex)の正規表現をRubularで使ってみてください。有効なメールアドレスのみがすべてマッチし、無効なメールアドレスはすべてマッチしないことを確認してみましょう。 

<img width="1054" alt="スクリーンショット 2019-10-10 7.44.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323778/6e822ffd-ad30-6906-ec6f-cf5a558ca6bb.png">

# 一意性を検証する

## メールアドレスの一意性についての重要注意

「メールアドレスの一意性」という観点において、Railsチュートリアルには重要な指摘事項が2点あります。

* 実用上、メールアドレスの大文字小文字は区別されない
    * バリデーションも大文字小文字を区別しない形で実装しなければならない
    * 「文字列型の大文字小文字を区別するかしないか」は、RDBMSの種類によって異なるので、その違いを吸収するメカニズムを実装しなければならない
* Active Recordには、RDBMSレベルでのデータの一意性を保証する仕組みはない
    * ほぼ同時に同内容の`POST`リクエストを受け取った場合など、重複してはならないカラムに重複が発生するおそれがある

「これらを踏まえた上で、相応の対策が必要となる」ということです。…恐れずに行ってみましょう！

## 重複するメールアドレスを拒否する（Rails側で）

### テストを書く

一意性をテストするためには、「実際にレコードをRDBMSに保存する」という操作が必要になります。ここで使うのは、モデルオブジェクトの`save`メソッドです。テストコードに登場するのは初めてですね。

```diff:test/models/user_test.rb
  class UserTest < ActiveSupport::TestCase

    def setup
      @user = User.new(name: "Example User", email: "user@example.com")
    end

  ...略
+
+   test "email addresses should be unique" do
+     duplicate_user = @user.dup
+     @user.save
+     assert_not duplicate_user.valid?
+   end
  end
```

### テストは通らない

この時点でテストは通りません。`assert_not`の時点で、RDBMS上に`duplicate_user`と同一内容のmail属性値を持つレコードが存在するのに、`duplicate_user`の`valid?`が`true`になるためです。

```
Started with run options --seed 45524

 FAIL["test_email_addresses_should_be_unique", UserTest, 0.11071269999956712]
 test_email_addresses_should_be_unique#UserTest (0.11s)
        Expected true to be nil or false
        test/models/user_test.rb:52:in `block in <class:UserTest>'

  8/8: [===================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.21069s
8 tests, 17 assertions, 1 failures, 0 errors, 0 skips
```

### テストを通す

上記テストを通すためには、Userモデルにおける`email`のバリデーションに`uniqueness: true`というオプションを追加すればOKです。

```diff:app/models/user.rb
  class User < ApplicationRecord
    validates :name,  presence: true, length: { maximum: 50 }
    VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
    validates :email, presence: true, length: { maximum: 255 },
-                     format: { with: VALID_EMAIL_REGEX }
+                     format: { with: VALID_EMAIL_REGEX },
+                     uniqueness: true
  end
```

### 実は、「メールアドレスは、実用上大文字小文字を区別しない」ことに対応しなければならなかった

前述「メールアドレスの一意性についての重要注意」にあった事柄の一つですね。Railsチュートリアルのテストコードでは、「メールアドレスを大文字に変換して比較する」という操作を追加しています。

```diff:test/models/user_test.rb
  class UserTest < ActiveSupport::TestCase

    def setup
      @user = User.new(name: "Example User", email: "user@example.com")
    end

  ...略
 
    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
  end
```

「メールアドレスを大文字に変換して比較する」というのが何を指すか、Railsコンソールで順を追って確認してみます。

```
>> user = User.create(name: "Example User", email: "user@example.com")
   (0.1ms)  SAVEPOINT active_record_1
  User Exists (1.2ms)  SELECT  1 AS one FROM "users" WHERE "users"."email" = ? LIMIT ?  [["email", "user@example.com"], ["LIMIT", 1]]
  SQL (14.0ms)  INSERT INTO "users" ("name", "email", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["name", "Example User"], ["email", "user@example.com"], ["created_at", "2019-10-09 23:07:22.293648"], ["updated_at", "2019-10-09 23:07:22.293648"]]
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> #<User id: 1, name: "Example User", email: "user@example.com", created_at: "2019-10-09 23:07:22", updated_at: "2019-10-09 23:07:22">
>> user.email.upcase
=> "USER@EXAMPLE.COM"
>> duplicate_user = user.dup
=> #<User id: nil, name: "Example User", email: "user@example.com", created_at: nil, updated_at: nil>
>> duplicate_user.email = user.email.upcase
=> "USER@EXAMPLE.COM"
>> duplicate_user.valid?
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE "users"."email" = ? LIMIT ?  [["email", "USER@EXAMPLE.COM"], ["LIMIT", 1]]
=> true
```

最後の`duplicate_user.valid?`が`true`になっています。前述`"email addresses should be unique"`テストを通すためには、`duplicate_user.valid?`は`false`にならなければなりません。

モデルオブジェクトにおいて、「一意性検証において大文字小文字を区別しない」という実装をするには、`:uniquness`オプションの`:case_sensitive`オプションの値`false`とすればOKです。

```diff:app/models/user.rb
  class User < ApplicationRecord
    validates :name,  presence: true, length: { maximum: 50 }
    VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
    validates :email, presence: true, length: { maximum: 255 },
                      format: { with: VALID_EMAIL_REGEX }
                      format: { with: VALID_EMAIL_REGEX },
-                     uniqueness: true
+                     uniqueness: { case_sensitive: false }
  end
```

「`:uniqueness`オプションの値にハッシュのみを与えている」という点は注目に値します。このような書き方をした場合、Railsは`:uniquness`オプションを`true`と判断した上で処理を行います。

この時点で、Rails側のテストは通るようになりました。

```
# rails test:models
Started with run options --seed 11495

  8/8: [===================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.21977s
8 tests, 17 assertions, 0 failures, 0 errors, 0 skips

```

```

# rails test       
Running via Spring preloader in process 2521
Started with run options --seed 16633

  16/16: [=================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.45354s
16 tests, 36 assertions, 0 failures, 0 errors, 0 skips
```

## RDBMSレベルでメールアドレスの一意性を保証する

現時点で、Rails側でメールアドレスの重複を排除する仕組みは実装できています。しかし、前述「メールアドレスの一意性についての重要注意」で言及したように、RDBMSレベルでメールアドレスの一意性を保証する仕組みは未だ実装されていません。そのため、現時点では「重複するメールアドレスを持つ複数レコードがRDBMSに登録されてしまう」という事態の発生を排除しきれません。Railsチュートリアルでは、「Submitボタンを素早く2回クリックしてしまった場合」を例として、そうした事態が発生しうることについて言及しています。

というわけで、RDBMS側にもメールアドレスの一意性を保証する仕組みを実装する必要があります。具体的には以下の手順を踏みます。

1. RDBMS上のemailカラムにインデックスを追加する
2. 追加したインデックスについて、一意性制約を追加する

変更を加えるのはRDBMS側ですが、この操作はRails側から行うことが可能です。「マイグレーションの実装」という操作ですね。ただ、今回はマイグレーションの新規実装ではなく、既存のモデルへの構造の追加なので、新規実装時とは異なる手順を踏む必要があります。

### 既存のモデルに構造を追加する

既存のモデルに構造を追加する場合、`migration`ジェネレーターを用いてマイグレーションを直接作成する必要があります。

```
# rails generate migration add_index_to_users_email
Running via Spring preloader in process 2535
      invoke  active_record
      create    db/migrate/[timestamp]_add_index_to_users_email.rb
```

`db/migrate/{[timestamp]_add_index_to_users_email.rb`というファイルが生成されました。これが新たに作成したマイグレーションの実体です。

生成時点のマイグレーションには、特に何も定義されていません。ここにメールアドレスの一意性制約を追加していきます。

```diff:
  class AddIndexToUsersEmail < ActiveRecord::Migration[5.1]
    def change
+     add_index :users, :email, unique: true
    end
  end
```

追加したコードのポイントは以下です。

* `users`テーブルの`email`カラムにインデックスを追加している
* インデックスはRails（ActiveRecord::Migration）の`add_indexメソッド`により追加することができる
    * デフォルトでは一意性制約を追加しない
* オプションとして`unique: true`を指定することにより、一意性制約を追加できる

マイグレーションのコードを変更したら、最後に実際にマイグレートの処理を行います。

```
# rails db:migrate  
== [timestamp] AddIndexToUsersEmail: migrating =============================
-- add_index(:users, :email, {:unique=>true})
   -> 0.0222s
== [timestamp] AddIndexToUsersEmail: migrated (0.0224s) ====================
```

無事マイグレートの処理が完了しました。

## fixture

Railsにおけるfixtureというのは、テストに際して初期データを投入する機構のことです。モデルオブジェクトに対応して存在します。最初の生成は、`rails generate model`により自動で行われます。

```yaml:test/fixtures/users.yml
# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html

one:
  name: MyString
  email: MyString

two:
  name: MyString
  email: MyString

```

上記は自動生成されたままの状態のfixture定義データです。見ての通り、メールアドレスが一意ではありません。そのため、「RDBMSにメールアドレスの一意性制約を追加した時点で、一意性制約違反によりテストが通らなくなった」というのが現状です。

なお、そもそもfixtureの内容自体が有効でない代物なのですが、fixtureの内容はバリデーションを通らないゆえ、ここまで問題にはなっていませんでした。

現時点およびこの先しばらくはfixtureを使うことはありません。なので、ここでは単にfixture定義ファイルの内容を単純に空にしてしまうという解決策をとります。

```diff:test/fixtures/users.yml
- # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
-  one:
-   name: MyString
-   email: MyString
-
- two:
-   name: MyString
-   email: MyString
-
```

fixtureの中身を空にした時点で、モデルに対するテストは通るようになります。

```
# rails test:models
Started with run options --seed 158

  8/8: [===================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.20588s
8 tests, 17 assertions, 0 failures, 0 errors, 0 skips
```

## 大文字小文字を区別するRDBMSへの対処

インデックスにおいて大文字と小文字を区別するかは、使用するRDBMSによって異なります。

今回のアプリケーションでは、メールアドレスのインデックスにおいて大文字と小文字は区別してほしくありません。例えば、eRAseRmOToRpHAntOM@example.comとEraSErMotOrPhaNTom@ExAMPle.comは同一の文字列と解釈してほしいです。そのため、インデックスにおいて大文字と小文字を区別するRDBMSへの対処が必要となります。

今回は、「データベースに保存される直前に、すべての文字列を小文字に変換する」という方法をとります。例えば、"EraSErMotOrPhaNTom@ExAMPle.com"という文字列が渡されたら、保存直前に"erasermotorphantom@example.com"に変換してしまうわけです。

### Active Recordのコールバック(callback)メソッドとは

RailsのActive Recordライブラリでは、オブジェクトのライフサイクル中の特定タイミングに実行されるメソッドを定義することができます。例えば、作成時・保存時・更新時・削除時・検索時・検証時・データベースからの読み込み時などがそのタイミングです。こうした特定タイミングに実行されるメソッドを「コールバック」といいます。

ここで「特定タイミング」と言いました。より厳密には、特定タイミングの「前」「後」というのも指定することができます。例えば、作成前・作成後。保存前・保存後…というような指定も可能です。

### コールバックメソッドの実装

繰り返しますと、今回実装するのは、「データベースに保存される直前に、`email`属性のすべての文字列を小文字に変換する」という処理です。「データベースに保存される直前」に実行されるコールバックは、`before_save`という名前で定義されています。

では実装していきましょう。対象ファイルは`app/models/user.rb`です。

```diff:app/models/user.rb
  class User < ApplicationRecord
+   before_save { self.email = email.downcase }
    validates :name,  presence: true, length: { maximum: 50 }
    VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
    validates :email, presence: true, length: { maximum: 255 },
                      format: { with: VALID_EMAIL_REGEX },
                      uniqueness: { case_sensitive: false }
  end
```

重要なポイントは以下です。

* before_saveメソッドに渡しているのはブロックである
* 「小文字に変換する」という処理は、`String`クラスの`downcase`メソッドで実現している
* このコードにおける`self`は、現在のユーザーを指す
* 代入式右辺の`self`は省略することができる
    * 一方、代入式左辺の`self`は省略することができない

### この実装で実現できること

* RDBMSのemail属性に一意性制約を追加することにより、email属性の値が重複するユーザーの存在をRDBMSレベルで排除できるようになった
* RDBMSのemail属性にインデックスを追加することにより、検索効率が向上する
    * メールアドレスからユーザーを引く際に全表スキャンをしなくて済むようになった

# 演習 - 一意性を検証する

## 1.1. [リスト 6.33](https://railstutorial.jp/chapters/modeling_users?version=5.1#code-email_downcase_test)を参考に、メールアドレスを小文字にするテストをリスト 6.32に追加してみましょう。

> ちなみに追加するテストコードでは、データベースの値に合わせて更新する`reload`メソッドと、値が一致しているかどうか確認する`assert_equal`メソッドを使っています。

```diff:
  class UserTest < ActiveSupport::TestCase

    def setup
      @user = User.new(name: "Example User", email: "user@example.com")
    end

    ...略

    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
+
+   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
  end
```

`"email addresses should be saved as lower-case"`という名前のテストが、設問内容に対応するテストですね。以下の処理を行っています。

1. 大文字小文字が混ざったメールアドレスの例を`mixed_case_email`オブジェクトとして定義する
2. テストで使うユーザーオブジェクトのEmail属性に、`mixed_case_email`を代入する
3. テストで使うユーザーオブジェクトをRDBMS上に保存する
4. `mixed_case_email`の値を全て小文字にしたものと、改めてRDBMSから読み込んだユーザーオブジェクトのemail属性の値が一致していればテスト成功

## 1.2. [リスト 6.33](https://railstutorial.jp/chapters/modeling_users?version=5.1#code-email_downcase_test)のテストがうまく動いているか確認するためにも、`before_save`の行をコメントアウトして`red`になることを確認してみましょう。

```diff:app/models/user.rb
  class User < ApplicationRecord
-   before_save { self.email = email.downcase }
+ #  before_save { self.email = email.downcase }
    validates :name,  presence: true, length: { maximum: 50 }
    VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
    validates :email, presence: true, length: { maximum: 255 },
                      format: { with: VALID_EMAIL_REGEX },
                      uniqueness: { case_sensitive: false }
  end
```

上述のコードは、`before_save`の行をコメントアウトした状態です。これでテストを実行してみましょう。

```
# rails test:models
Started with run options --seed 47663

 FAIL["test_email_addresses_should_be_saved_as_lower-case", UserTest, 0.11753190000308678]
 test_email_addresses_should_be_saved_as_lower-case#UserTest (0.12s)
        Expected: "foo@example.com"
          Actual: "Foo@ExAMPle.com"
        test/models/user_test.rb:60:in `block in <class:UserTest>'

  9/9: [===================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.27076s
9 tests, 18 assertions, 1 failures, 0 errors, 0 skips
```

それらしいエラーメッセージがありますね。以下の部分です。

```
 test_email_addresses_should_be_saved_as_lower-case#UserTest (0.12s)
        Expected: "foo@example.com"
          Actual: "Foo@ExAMPle.com"
```

「`"foo@example.com"`が入ってくるべきところに、`"Foo@ExAMPle.com"`が入ってきている」という趣旨のエラーメッセージです。

## 1.3. `before_save`の行のコメントアウトを解除すると`green`になることを確認してみましょう。

```diff:app/models/user.rb
  class User < ApplicationRecord
- #  before_save { self.email = email.downcase }
+   before_save { self.email = email.downcase }
    validates :name,  presence: true, length: { maximum: 50 }
    VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
    validates :email, presence: true, length: { maximum: 255 },
                      format: { with: VALID_EMAIL_REGEX },
                      uniqueness: { case_sensitive: false }
  end
```

変更内容は上述となります。テストを実行してみましょう。

```
# rails test:models
Started with run options --seed 6755

  9/9: [===================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.26453s
9 tests, 18 assertions, 0 failures, 0 errors, 0 skips
```

テストは無事完了しました。

## 2. テストスイートの実行結果を確認しながら、`before_save`コールバックを`email.downcase!`に書き換えてみましょう。

> ヒント: メソッドの末尾に!を付け足すことにより、email属性を直接変更できるようになります (リスト 6.34)。

```diff:app/models/user.rb
  class User < ApplicationRecord
-   before_save { self.email = email.downcase }
+   before_save { email.downcase! }
    validates :name,  presence: true, length: { maximum: 50 }
    VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
    validates :email, presence: true, length: { maximum: 255 },
                      format: { with: VALID_EMAIL_REGEX },
                      uniqueness: { case_sensitive: false }
  end
```

モデルのテストから先に実行してみましょう。コマンドは`rails test:models`ですね。

```
# rails test:models
Started with run options --seed 33265

  9/9: [===================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.26898s
9 tests, 18 assertions, 0 failures, 0 errors, 0 skips
```

無事完了しました。

続いて、テストスイート全体も実行してみましょう。こちらのコマンドは`rails test`です。

```
# rails test
Running via Spring preloader in process 2614
Started with run options --seed 40098

  17/17: [=================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.36793s
17 tests, 37 assertions, 0 failures, 0 errors, 0 skips
```

無事完了しました。