Railsチュートリアル 第7章 ユーザー登録 - ユーザー登録成功

# ユーザー登録に成功した画面のモックアップ

ユーザー登録に失敗した場合の挙動は、ここまでの学習で実装が完了しました。

[ユーザー登録に成功した画面のモックアップ](https://railstutorial.jp/chapters/sign_up?version=5.1#fig-signup_success_mockup) - ユーザー登録に成功したら、当該リンク先のような画面を出力するように実装していきます。

# 登録フォームの完成

## 現状、有効な情報で登録操作を行うと何が起こるか

現状では、ユーザー登録フォームに有効な情報で登録操作を行っても、元の画面に戻されてしまいます。

<img width="1178" alt="スクリーンショット 2019-10-22 18.29.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323778/ed55b7e3-0f1a-0911-5b9d-8358867c6cb4.png">

このときの`rails server`のログ情報は以下のようになります。

```
Started POST "/users" for 172.17.0.1 at 2019-10-22 09:28:55 +0000
...略
No template found for UsersController#create, rendering head :no_content
Completed 204 No Content in 1605ms (ActiveRecord: 50.4ms)
```

「No template found for UsersController#create」というのがポイントですね。実際に発生しているのは以下の事態です。

1. Railsはデフォルトのアクションに対応するビューを表示しようとする
2. `create`アクションに対応するビューのテンプレートは現状存在しない
3. 元の画面に戻されてしまう

### 登録内容はRDBにきちんと反映される

なお、現状においても、有効な情報で登録操作を行えば、登録内容はRDBにきちんと反映されます。

```
Processing by UsersController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"YZ6q9xae3E23E8J5R3JNqhrVZ5r+jL9UV+QIZy7LiF/sdbfCNkYFsMUr+8tqltfCwiHus2I/viw+wT92e7nluQ==", "user"=>{"name"=>"Foobar Foobar", "email"=>"foobar@example.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Create my account"}
   (0.1ms)  begin transaction
  User Exists (6.7ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ?  [["email", "foobar@example.com"], ["LIMIT", 1]]
  SQL (25.7ms)  INSERT INTO "users" ("name", "email", "created_at", "updated_at", "password_digest") VALUES (?, ?, ?, ?, ?)  [["name", "Foobar Foobar"], ["email", "foobar@example.com"], ["created_at", "2019-10-22 09:28:55.485707"], ["updated_at", "2019-10-22 09:28:55.485707"], ["password_digest", "$2a$10$qgI/adIo1AfEBIYOxU636uh/k2kEWw9IM2F/E5kdqMAdkTgeNtRV."]]
   (18.0ms)  commit transaction
```

```
# rails console
Running via Spring preloader in process 505
Loading development environment (Rails 5.1.6)
>> User.count
   (3.0ms)  SELECT COUNT(*) FROM "users"
=> 2
>> User.find(2)
  User Load (4.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
=> #<User id: 2, name: "Foobar Foobar", email: "foobar@example.com", created_at: "2019-10-22 09:28:55", updated_at: "2019-10-22 09:28:55", password_digest: "$2a$10$qgI/adIo1AfEBIYOxU636uh/k2kEWw9IM2F/E5kdqMA...">
```

## 別のページへのリダイレクトを実装する

Railsの一般的な慣習では、「ユーザー登録に成功した場合、ページを描画せずに別のページにリダイレクトする」ことになっています。今回は、「ユーザー登録に成功した場合、新しく作成されたユーザーのプロフィールページにリダイレクトする」という動作にしましょう。

変更するファイルは`app/controllers/users_controller.rb`です。

```diff:app/controllers/users_controller.rb
  class UsersController < ApplicationController

    ...略

    def create
      @user = User.new(user_params)
      if @user.save
-       # TODO: 保存の成功をここに実装する
+       redirect_to @user
      else
        render 'new'
      end
    end

    ...略
  end
```

### `redirect_to @user`というコードについて

```ruby:
redirect_to @user
```

上記のコードと下記のコードは等価の挙動となります。

```ruby:
redirect_to user_url(@user)
```

上述2つのコードを等価とみなす動作も、Railsの機能の一つです。

# 演習 - 登録フォームの完成

## 1. 有効な情報を送信し、ユーザーが実際に作成されたことを、Railsコンソールを使って確認してみましょう。

有効な情報を送信した際、`rails server`のログは以下のようになります。

```
Started POST "/users" for 172.17.0.1 at 2019-10-22 09:46:59 +0000
Cannot render console from 172.17.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by UsersController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"YZ6q9xae3E23E8J5R3JNqhrVZ5r+jL9UV+QIZy7LiF/sdbfCNkYFsMUr+8tqltfCwiHus2I/viw+wT92e7nluQ==", "user"=>{"name"=>"Foo Bar Baz", "email"=>"foobar.foobar@example.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Create my account"}
   (0.2ms)  begin transaction
  User Exists (5.8ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ?  [["email", "foobar.foobar@example.com"], ["LIMIT", 1]]
  SQL (12.4ms)  INSERT INTO "users" ("name", "email", "created_at", "updated_at", "password_digest") VALUES (?, ?, ?, ?, ?)  [["name", "Foo Bar Baz"], ["email", "foobar.foobar@example.com"], ["created_at", "2019-10-22 09:46:59.624267"], ["updated_at", "2019-10-22 09:46:59.624267"], ["password_digest", "$2a$10$aVxeV/ey4bi5F9/DnuXRd.M0/41r/X3Sj7rl1MdKnpubgKUni9tlu"]]
   (18.4ms)  commit transaction
Redirected to http://localhost:8080/users/3
Completed 302 Found in 237ms (ActiveRecord: 48.6ms)
```

RDBに対する`INSERT`文が発行された後、確かにリダイレクト処理が行われていますね。`POST`リクエストの処理結果は、最終的に「302 found」となっています。

```
Started GET "/users/3" for 172.17.0.1 at 2019-10-22 09:46:59 +0000
Cannot render console from 172.17.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by UsersController#show as HTML
  Parameters: {"id"=>"3"}
  User Load (3.4ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 3], ["LIMIT", 1]]
  Rendering users/show.html.erb within layouts/application
  Rendered users/show.html.erb within layouts/application (3.0ms)
  Rendered layouts/_rails_default.erb (276.7ms)
  Rendered layouts/_shim.html.erb (0.4ms)
  Rendered layouts/_header.html.erb (0.8ms)
  Rendered layouts/_footer.html.erb (3.6ms)
Completed 200 OK in 509ms (Views: 444.4ms | ActiveRecord: 3.4ms)
```

最終的には、以下のような画面が表示されます。

<img width="1178" alt="スクリーンショット 2019-10-22 18.47.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323778/4639fe56-fa6d-dcc3-9044-4004e99160f6.png">

先ほど作成したユーザー情報の`email`属性の値が`"foobar.foobar@example.com"`であることを踏まえて、`rails console`で、実際にRDBにユーザー情報が保存されていることを確認してみましょう。


```
# rails console

>> User.find_by(email: "foobar.foobar@example.com")
  User Load (11.1ms)  SELECT  "users".* FROM "users" WHERE "users"."email" = ? LIMIT ?  [["email", "foobar.foobar@example.com"], ["LIMIT", 1]]
=> #<User id: 3, name: "Foo Bar Baz", email: "foobar.foobar@example.com", created_at: "2019-10-22 09:46:59", updated_at: "2019-10-22 09:46:59", password_digest: "$2a$10$aVxeV/ey4bi5F9/DnuXRd.M0/41r/X3Sj7rl1MdKnpu...">
```

確かに、実際にRDBにユーザー情報が保存されています。

## 2. [リスト 7.28](https://railstutorial.jp/chapters/sign_up?version=5.1#code-user_create_action)を更新し、`redirect_to user_url(@user)`と`redirect_to @user`が同じ結果になることを確認してみましょう。

`app/controllers/users_controller.rb`を変更します。

```diff:app/controllers/users_controller.rb
  class UsersController < ApplicationController

    ...略

    def create
      @user = User.new(user_params)
      if @user.save
-       redirect_to @user
+       redirect_to user_url(@user)
      else
        render 'new'
      end
    end

    ...略
  end
```

以上の変更を行った上で、有効な情報を送信すると、`rails server`のログは以下のようになります。

```
Started POST "/users" for 172.17.0.1 at 2019-10-22 10:47:07 +0000
Cannot render console from 172.17.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by UsersController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"YZ6q9xae3E23E8J5R3JNqhrVZ5r+jL9UV+QIZy7LiF/sdbfCNkYFsMUr+8tqltfCwiHus2I/viw+wT92e7nluQ==", "user"=>{"name"=>"Foo Bar Foobar", "email"=>"foobar.baz@example.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Create my account"}
   (0.1ms)  begin transaction
  User Exists (6.0ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ?  [["email", "foobar.baz@example.com"], ["LIMIT", 1]]
  SQL (20.5ms)  INSERT INTO "users" ("name", "email", "created_at", "updated_at", "password_digest") VALUES (?, ?, ?, ?, ?)  [["name", "Foo Bar Foobar"], ["email", "foobar.baz@example.com"], ["created_at", "2019-10-22 10:47:07.789313"], ["updated_at", "2019-10-22 10:47:07.789313"], ["password_digest", "$2a$10$frfU4tFYiTifJHRfHLQ7nO.3c1ju83yJsQbklZU7pGJa2LNdXSENG"]]
   (16.6ms)  commit transaction
Redirected to http://localhost:8080/users/4
Completed 302 Found in 258ms (ActiveRecord: 55.5ms)


Started GET "/users/4" for 172.17.0.1 at 2019-10-22 10:47:07 +0000
Cannot render console from 172.17.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by UsersController#show as HTML
  Parameters: {"id"=>"4"}
  User Load (5.6ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 4], ["LIMIT", 1]]
  Rendering users/show.html.erb within layouts/application
  Rendered users/show.html.erb within layouts/application (0.9ms)
  Rendered layouts/_rails_default.erb (262.1ms)
  Rendered layouts/_shim.html.erb (0.3ms)
  Rendered layouts/_header.html.erb (0.7ms)
  Rendered layouts/_footer.html.erb (0.5ms)
Completed 200 OK in 516ms (Views: 472.0ms | ActiveRecord: 5.6ms)
```

# flash

## flashとその実装

世界中に多数存在するWebアプリケーションの多くには、「新規ユーザー登録が完了した後に表示されるページにメッセージを表示し、2度目以降のログインではそのページにメッセージを表示しない」という機能が実装されています。

Railsでそのような情報を表示するには、flashという特殊な変数を使用します。その用法はRubyのハッシュと類似しています。ここでは、Railsの一般的な慣習に従い、`:success`というキーには成功時のメッセージを代入するように実装します。

flashを実装する箇所は、`app/controllers/users_controller.rb`内です。

```diff:app/controllers/users_controller.rb
  class UsersController < ApplicationController

    ...略

    def create
      @user = User.new(user_params)
      if @user.save
+       flash[:success] = "Welcome to the Sample App!"
        redirect_to @user
      else
        render 'new'
      end
    end

    ...略
  end
```

## flash内に存在するキーがあるかを調べ、もしあればキーに対応する値（メッセージ）を全て表示する

今回はこの項見出しに示した機能を実装していきます。そういえば、第4章中のサンプルプログラムで、「ブロックを使い、特定のハッシュのkeyとvalueの組を順次表示していく」というものがありました。

```irb:
>> flash = { success: "It worked!", danger: "It failed." }
>> flash.each do |key, value|
?>   puts "#{key}"
>>   puts "#{value}"
>> end
success
It worked!
danger
It failed.
=> {:success=>"It worked!", :danger=>"It failed."}
```

ここで`flash`という変数名を使ったのは、今回の実装に向けての伏線だったのですね。

というわけで、flash変数の内容をWebサイト全体にわたって表示できるようにするための（仮）コードは以下のようになります。

```erb
<% flash.each do |message_type, message| %>
  <div class="alert alert-<%= message_type %>"><%= message %></div>
<% end %>
```

（仮）とあるのは、このコードは「HTMLとERbが雑に混ざっており、リファクタリングの余地がある」ためです。Railsチュートリアル本文には、「これをキレイに整形する作業は演習として残しておきましょう」とあります。

## `alert-<%= message_type %>`なるクラスの意味

上述「flash変数の内容をWebサイト全体にわたって表示できるようにするための（仮）コード」の中には、以下のようなコードがあります。

```
alert-<%= message_type %>
```

これは、「埋め込みRubyによって、適用するCSSのクラスをメッセージの種類により変更する」というコードです。例えば、`:success`キーのメッセージが表示される場合であれば、適用されるCSSクラスは以下のようになります。

```
alert-success
```

「`:success`はシンボルであるが、テンプレートに反映させる際には、埋め込みRubyが自動的に`"success"`という文字列に変換している」という点に注意が必要です。

このような実装により、以下の挙動を実現することができます。

* キーの内容により、異なったCSSクラスを適用させる
    * 結果として、メッセージの種類によってスタイルを動的に変更させることができる
    * 例えば、「ログインに失敗した旨を示すメッセージを`flash[:danger]`で表示し、その際に自動的に`alert-danger`というクラスを適用させる」など

### Bootstrap CSSにおける、`alert-`から始まる4つのクラス

BootstrapのCSSでは、`alert-`から始まるクラスが以下4つ定義されています。

* `alert-success`
* `alert-info`
* `alert-warning`
* `alert-danger`

## 全体としてどういう挙動になるか

```ruby:
flash[:success] = "Welcome to the Sample App!"
```

```erb:
<% flash.each do |message_type, message| %>
  <div class="alert alert-<%= message_type %>"><%= message %></div>
<% end %>
```

上述のコードを踏まえると、当該部分に対して最終的に出力されるHTMLは以下のようになります。

```html:
<div class="alert alert-success">Welcome to the Sample App!</div>
```

## `flash`変数の内容をWebサイトのレイアウトに追加する 

```diff:app/views/layouts/application.html.erb
  <!DOCTYPE html>
  <html>
    ...略
    <body>
      <%= render 'layouts/header' %>
      <div class="container">
+       <% flash.each do |message_type, message| %>
+         <div class="alert alert-<%= message_type %>"><%= message %></div>
+       <% end %>
        <%= yield %>
        <%= render 'layouts/footer' %>
        <%= debug(params) if Rails.env.development? %>
      </div>
    </body>
  </html>
```

# 演習 - flash

## 1. コンソールに移り、文字列内の式展開 ([4.2.2](https://railstutorial.jp/chapters/rails_flavored_ruby?version=5.1#sec-strings)) でシンボルを呼び出してみましょう。例えば`"#{:success}"`といったコードを実行すると、どんな値が返ってきますか? 確認してみてください。

```
# rails console --sandbox

>> "#{:success}"
=> "success"
>> "#{:danger}"
=> "danger"
```

## 2. 先ほどの演習で試した結果を参考に、[リスト 7.30](https://railstutorial.jp/chapters/sign_up?version=5.1#code-flash_iteration)のflashはどのような結果になるか考えてみてください。

前提として、以下のようなコードを考えます。

```erb:
<% flash = { success: "It worked!", danger: "It failed." } %>
<% flash.each do |message_type, message| %>
  <div class="alert alert-<%= message_type %>"><%= message %></div>
<% end %>
```

最終的に出力されるHTMLは以下のようになるはずです。

```html:
<div class="alert alert-success">It worked!</div>
<div class="alert alert-danger">It failed.</div>
```

# 実際のユーザー登録

## データベースの内容をリセットする

RailsのActive Recordのマイグレーション機能により、データベースの内容をリセットすることが可能です。データベースの内容をリセットするためのコマンドは、`rails db:migrate:reset`です。実行すると、以下のようなログと共に、データベースの内容がリセットされます。

```
# rails db:migrate:reset
Dropped database 'db/development.sqlite3'
Dropped database 'db/test.sqlite3'
Created database 'db/development.sqlite3'
Created database 'db/test.sqlite3'
== 20190928080951 CreateUsers: migrating ======================================
-- create_table(:users)
   -> 0.0119s
== 20190928080951 CreateUsers: migrated (0.0122s) =============================

== 20191010034159 AddIndexToUsersEmail: migrating =============================
-- add_index(:users, :email, {:unique=>true})
   -> 0.0133s
== 20191010034159 AddIndexToUsersEmail: migrated (0.0136s) ====================

== 20191013040411 AddPasswordDigestToUsers: migrating =========================
-- add_column(:users, :password_digest, :string)
   -> 0.0134s
== 20191013040411 AddPasswordDigestToUsers: migrated (0.0137s) ================
```

ログを見るに、以下のような動作が行われているようですね。

* 既存テーブルの削除
* 新規テーブルの作成
    * 既存のマイグレーションに基づき、列情報も自動生成される
* インデックスの定義
* パスワードダイジェスト列の生成

Railsチュートリアルにおける今回の事例は、「Usersコントローラの`user.save`コマンドが実行された回数が学習者ごとに異なる可能性があり、RDBの内容の違いを吸収するためにデータベースの内容をリセットする」という趣旨のものです。

## 有効なユーザー情報を登録する

早速、ユーザー登録フォームに有効なユーザー情報を入力し、最初のユーザーを作成してみましょう。Railsチュートリアル本文にならい、以下の内容でユーザーを作成してみます。

* Name: Rails Tutorial
* Email: example@railstutorial.org
* PasswordおよびConfirmationは任意

<img width="1178" alt="スクリーンショット 2019-10-23 7.47.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323778/480f4011-e52a-c7da-7bc6-5a64a740a9cd.png">

上記はユーザー情報作成完了時の画面です。ユーザー登録の成功を示すフラッシュメッセージもきちんと表示されていますね。なお、Bootstrap CSSにおいて、`success`クラスの背景色・文字色は、上記スクリーンショットのように爽やかな緑色です。

<img width="1178" alt="スクリーンショット 2019-10-23 7.49.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323778/4a546367-4715-c361-5df2-895a09f3a0de.png">

ユーザー情報ページを再読み込みすると、今後はフラッシュメッセージは表示されなくなります。上記スクリーンショットは、再読み込み時のものです。

# 演習 - 実際のユーザー登録

## 1. Railsコンソールを使って、新しいユーザーが本当に作成されたのかもう一度チェックしてみましょう。結果は、[リスト 7.32](https://railstutorial.jp/chapters/sign_up?version=5.1#code-user_created)のようになるはずです。

```
# rails console

>> User.count
   (3.4ms)  SELECT COUNT(*) FROM "users"
=> 1

>> User.find_by(email: "example@railstutorial.org")
  User Load (3.9ms)  SELECT  "users".* FROM "users" WHERE "users"."email" = ? LIMIT ?  [["email", "example@railstutorial.org"], ["LIMIT", 1]]
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2019-10-22 22:46:59", updated_at: "2019-10-22 22:46:59", password_digest: "$2a$10$j21OfGX82PY0/BqDcapJmeeo/xaVKgSQ9pEZD8hAp4B...">
```

RDBにも新しく作成されたユーザーの情報が正しく反映されているようです。

## 2. 自分のメールアドレスでユーザー登録を試してみましょう。既にGravatarに登録している場合、適切な画像が表示されているか確認してみてください。

まず、ユーザー登録フォームの[Create my account]ボタンをクリックしたところからの`rails server`のログを示します。

```
Started POST "/signup" for 172.17.0.1 at 2019-10-22 22:55:21 +0000
Cannot render console from 172.17.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by UsersController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"iAAnpc1ZKq9VuKfKkDNV0dVc4M8reSqR57fJU+5zPp8o8FsU4a2WCdXm++5dmoqwagJOnOJWHnu3mt+IIQCS1Q==", "user"=>{"name"=>"Hoge Hoge", "email"=>"[Gravatar登録済みの自分のメールアドレス]", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Create my account"}
   (0.1ms)  begin transaction
  User Exists (7.4ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ?  [["email", "[Gravatar登録済みの自分のメールアドレス]"], ["LIMIT", 1]]
  SQL (20.8ms)  INSERT INTO "users" ("name", "email", "created_at", "updated_at", "password_digest") VALUES (?, ?, ?, ?, ?)  [["name", "Hoge Hoge"], ["email", "[Gravatar登録済みの自分のメールアドレス]"], ["created_at", "2019-10-22 22:55:21.433059"], ["updated_at", "2019-10-22 22:55:21.433059"], ["password_digest", "$2a$10$GnOotTswgiIehevLmmlDV.XvhMgBXN67XAZp2KvTxip9WtDVsyo0."]]
   (23.3ms)  commit transaction
Redirected to http://localhost:8080/users/2
Completed 302 Found in 143ms (ActiveRecord: 51.7ms)
```

最終的にはどうなるでしょうか。

<img width="1178" alt="スクリーンショット 2019-10-23 7.55.39.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323778/995f73c9-0d49-4e23-84e2-a5e1668bfba2.png">

上記スクリーンショットは、ユーザー登録が正常終了した直後のものです。Gravatarに登録したプロフィール画像がきちんと表示されています。

# 成功時のテスト

今度は「ユーザー登録フォームにおいて、有効なユーザー情報が送信された場合」に対するテストを書いていきます。今回の主題は「データベースの内容が正しいかを検証することにより、有効なユーザー情報に対して正しくユーザーが作成されたことを確認する」というものです。

## `assert_difference`メソッド

ユーザー登録失敗時のテストに登場した`assert_no_difference`と対になるメソッドです。メソッドの使い方は`assert_difference`と同様です。

* 第1引数に文字列をとる
    * 今回の事例では、`'User.count'`を第1引数とする
* 第2引数には、比較した結果の差異をとる
    * 今回は1とする
* ブロックをとり、ブロックの実行前と実行後で第1引数の文字列が指す要素の値を比較する

```
assert_difference 'User.count', 1 do
  post users_path, ...
end
```

## 成功時のテストのコード

```diff:test/integration/users_signup_test.rb
  class UsersSignupTest < ActionDispatch::IntegrationTest

    ...略
+
+   test "valid signup information" do
+     get signup_path
+     assert_difference 'User.count', 1 do
+       post users_path, params: { user: { name: "Example User",
+                                         email: "user@example.com",
+                                         password: "password",
+                                         password_confirmation: "password"} }
+     end
+     follow_redirect!
+     assert_template 'users/show'
+   end
  end
```

この時点でテストは通ります。

```
# rails test integrate
Running via Spring preloader in process 137
Started with run options --seed 37813

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

Finished in 2.48632s
21 tests, 49 assertions, 0 failures, 0 errors, 0 skips
```

### `follow_redirect!`について

```ruby:
follow_redirect!
assert_template 'users/show'
```

`assert_difference`ブロックの後に、`follow_redirect!`というコードがあります。

`follow_redirect!`は、「リダイレクトを伴うテストにおいて、リダイレクト先の処理を明示的に実行する」というメソッドです。今回は、「`POST`リクエストの実行結果がリダイレクトになる[^POST後のリダイレクト]ので、`users/show`テンプレートが表示されている」目的で`follow_redirect!`メソッドを用いています。

`!`がつくだけに、`follow_redirect`というメソッドも存在すると考えられます。Google検索等してみたのですが、なぜ`!`がつくかについての情報は見つけられませんでした。ただ、「[`follow_redirect`というメソッド](https://apidock.com/rails/ActionController/TestProcess/follow_redirect)は、Rails 1.1.6から2.1.0まで存在したものの、以降廃止されて現在に至っている」のだそうです。

[^POST後のリダイレクト]: HTTPのステータスコードは「302 Found」が返ってきます。HTTPの仕様としては「303 See Other」が正しい応答なのだそうですが、後方互換性等の観点から、このような場合には302を返すのがデファクトスタンダードになっているそうです。

## `users/show`テンプレートが正しく表示されることの確認

```
assert_template 'users/show'
```

`users/show`テンプレートが正しく表示されるには、以下の条件が全て満足される事が必要になります。

* Userのルーティングが正しく実装されている
* Userの`show`アクションが正しく動いている
* `show.html.erb`ビューが正しく実装されている

## テストの全体像まとめ

今回実装したテストは、以下の事柄について確認しています。

* 正しいユーザー登録データに対し、`post`メソッドが正しく動作する
* ユーザー登録の内容がRDBに反映される
    * ユーザー登録後、登録前に比べてRDBのレコード数が1つ増えているかどうか
* `POST`メソッド完了時のリダイレクトが正しく実装されている
* `users/show`テンプレートが正しく表示される

「ユーザー登録成功」というフローで必要とされる機能がカバーされていますね。こういうときに統合テストは便利なのです。

# 演習 - 成功時のテスト

## 1. [7.4.2](https://railstutorial.jp/chapters/sign_up?version=5.1#sec-the_flash)で実装したflashに対するテストを書いてみてください。どのくらい細かくテストするかはお任せします。[リスト 7.34](https://railstutorial.jp/chapters/sign_up?version=5.1#code-flash_test)に最小限のテンプレートを用意しておいたので、参考にしてください (FILL_INの部分を適切なコードに置き換えると完成します)。

> ちなみに、テキストに対するテストは壊れやすいです。文量の少ない`flash`のキーであっても、それは同じです。筆者の場合、`flash`が空でないかをテストするだけの場合が多いです。

Railsチュートリアル本文にならい、`flash`が空でないかをテストするだけの実装とします。

```diff:test/integration/users_signup_test.rb
  require 'test_helper'

  class UsersSignupTest < ActionDispatch::IntegrationTest

    ...略

    test "valid signup information" do
      get signup_path
      assert_difference 'User.count', 1 do
        post users_path, params: { user: { name: "Example User",
                                          email: "user@example.com",
                                          password: "password",
                                          password_confirmation: "password"} }
      end
      follow_redirect!
      assert_template 'users/show'
+     assert_not flash.empty?
    end
  end

```

現時点の状態から`app/controllers/users_controller.rb`に手を加えなければ、テストは通ります。

```
# rails test integrate
Running via Spring preloader in process 189
Started with run options --seed 27734

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

Finished in 2.08653s
21 tests, 50 assertions, 0 failures, 0 errors, 0 skips
```

一方、`app/controllers/users_controller.rb`で`flash[:success]`以下の行をコメントアウトすると、テストが通らなくなります。

```diff:app/controllers/users_controller.rb
  class UsersController < ApplicationController

    ...略

    def create
      @user = User.new(user_params)
      if @user.save
-       flash[:success] = "Welcome to the Sample App!"
+       #flash[:success] = "Welcome to the Sample App!"
        redirect_to @user
      else
        render 'new'
      end
    end

    ...略
  end
```

```
# rails test integrate
Running via Spring preloader in process 202
Started with run options --seed 46053

 FAIL["test_valid_signup_information", UsersSignupTest, 1.8803565000016533]
 test_valid_signup_information#UsersSignupTest (1.88s)
        Expected true to be nil or false
        test/integration/users_signup_test.rb:33:in `block in <class:UsersSignupTest>'

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

Finished in 2.38272s
21 tests, 50 assertions, 1 failures, 0 errors, 0 skips
```

## 2.1. 本文中でも指摘しましたが、flash用のHTML ([リスト 7.31](https://railstutorial.jp/chapters/sign_up?version=5.1#code-layout_flash)) は読みにくいです。より読みやすくした[リスト 7.35](https://railstutorial.jp/chapters/sign_up?version=5.1#code-layout_flash_content_tag)のコードに変更してみましょう。

> なお、このコードでは、Railsのcontent_tagというヘルパーを使っています。

```diff:app/views/layouts/application.html.erb
  <!DOCTYPE html>
  <html>
    ...略
    <body>
      ...略
      <div class="container">
        <% flash.each do |message_type, message| %>
-         <div class="alert alert-<%= message_type %>"><%= message %></div>
+         <% content_tag(:div, message, class: "alert alert-#{message_type}") %>
        <% end %>
        ...略
      </div>
    </body>
  </html>
```

「読みにくい」というか、「RubyのコードとHTMLのコードが混在するのはよろしくない」という事情もあります。

Railsの`content_tag`ヘルパーにより、RubyのコードのみでHTMLを構成できるようになりました。

## 2.2. 変更が終わったらテストスイートを実行し、正常に動作することを確認してください。

```
# rails test integrate
Running via Spring preloader in process 215
Started with run options --seed 26929

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

Finished in 2.10806s
21 tests, 50 assertions, 0 failures, 0 errors, 0 skips
```

問題なくテストが通っていますね。

## 3. [リスト 7.28](https://railstutorial.jp/chapters/sign_up?version=5.1#code-user_create_action)のリダイレクトの行をコメントアウトすると、テストが失敗することを確認してみましょう。

```diff:app/controllers/users_controller.rb
  class UsersController < ApplicationController

    ...略

    def create
      @user = User.new(user_params)
      if @user.save
        flash[:success] = "Welcome to the Sample App!"
-       redirect_to @user
+       #redirect_to @user
      else
        render 'new'
      end
    end

    ...略
  end
```

このコードに対するテストの結果は以下のようになります。

```
# rails test integrate
Running via Spring preloader in process 228
Started with run options --seed 63745

ERROR["test_valid_signup_information", UsersSignupTest, 3.581053300000349]
 test_valid_signup_information#UsersSignupTest (3.58s)
RuntimeError:         RuntimeError: not a redirect! 204 No Content
            test/integration/users_signup_test.rb:31:in `block in <class:UsersSignupTest>'

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

Finished in 3.66224s
21 tests, 48 assertions, 0 failures, 1 errors, 0 skips
```

テストが通らなくなりました。「RuntimeError: not a redirect! 204 No Content」というエラーメッセージがポイントですね。

## 4. [リスト 7.28](https://railstutorial.jp/chapters/sign_up?version=5.1#code-user_create_action)で、`@user.save`の部分を`false`に置き換えたとしましょう (バグを埋め込んでしまったと仮定してください)。このとき、`assert_difference`のテストではどのようにしてこのバグを検知するでしょうか? テストコードを追って考えてみてください。

```ruby:
assert_difference 'User.count', 1 do
  #...略
end
```

`@user.save`が実行されない場合、RDBにレコードは追加されません。結果、`User.count`の値は変わらなくなります。「`@user.save`の実行後は、実行前と比較して`User.count`の値が1増えていなければならない」という条件に反するので、この時点でテストが失敗となります。

実際にやってみるとどうなるでしょうか。

```diff:app/controllers/users_controller.rb
  class UsersController < ApplicationController

    ...略

    def create
      @user = User.new(user_params)
-     if @user.save
+     if false
        flash[:success] = "Welcome to the Sample App!"
        redirect_to @user
      else
        render 'new'
      end
    end

    ...略
  end
```

テスト結果はこのようになります。

```
# rails test integrate
Running via Spring preloader in process 241
Started with run options --seed 8272

 FAIL["test_invalid_signup_information", UsersSignupTest, 1.5347172000001592]
 test_invalid_signup_information#UsersSignupTest (1.54s)
        Expected at least 1 element matching "div#error_explanation", found 0..
        Expected 0 to be >= 1.
        test/integration/users_signup_test.rb:15:in `block in <class:UsersSignupTest>'

 FAIL["test_valid_signup_information", UsersSignupTest, 1.5782804999998916]
 test_valid_signup_information#UsersSignupTest (1.58s)
        "User.count" didn't change by 1.
        Expected: 1
          Actual: 0
        test/integration/users_signup_test.rb:25:in `block in <class:UsersSignupTest>'

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

Finished in 2.10267s
21 tests, 44 assertions, 2 failures, 0 errors, 0 skips
```

あっ、「`Users.errors`に何も代入されないため、`new`がレンダリングされたときに`app/views/shared/_error_messages.html.erb`の内容がレンダリングされない。結果、IDが`error_explanation`となる`div`要素が存在しないため、その点でテストが失敗する」というのもありました。