railsチュートリアルまとめ7 ユーザー登録

# 個人的リマインド用
参考
[Ruby on Rails チュートリアル プロダクト開発の０→１を学ぼう](https://railstutorial.jp/)

## ユーザー登録
### ユーザーを表示する
最終目標はユーザーのプロフ写真と基本ユーザーデータ、マイクロポストの一覧を表示すること。

### デバッグとRails環境
動的なページを追加する準備として、Webサイトのレイアウトにデバッグ情報を追加する。ビルトインのdebugメソッドとparams変数を使って、各プロフィールページにデバッグ用の情報が表示されるようになる。
```erb
app/views/layouts/application.html.erb

<!DOCTYPE html>
<html>
  .
  .
  .
  <body>
    <%= render 'layouts/header' %>
    <div class="container">
      <%= yield %>
      <%= render 'layouts/footer' %>
      <%= debug(params) if Rails.env.development? %> ←ここ！
    </div>
  </body>
</html>
```
本番環境にデプロイしたアプリケーションではデバッグ情報を表示したくないので、if Rails.env.development?を書く。

Railsには環境が3つあり、**テスト環境(test)と開発環境(development)、そして本番環境(production)が**デフォルトで装備されている。

デバッグ情報
```
#<ActionController::Parameters {"controller"=>"static_pages", "action"=>"home"}
permitted: false>
```
これはparamsのリテラル表示であり、基本的にはハッシュである。ここではそのページのコントローラとアクションを表示している。

### Usersリソース
ユーザープロフィールページを作成するには、その前にデータベースにユーザーが登録されていなければならない。登録ページがない状態でどう登録するのか。今回はrails consoleで登録することで解決している。

次はユーザー情報をWebアプリケーションに表示することが目標である。ここではRESTアーキテクチャの慣習に従い、データの作成、表示、更新、削除を**リソース(Resources)として扱う**。
RESTの原則に従う場合、**リソースへの参照はリソース名と一意のIDを使う**ことが普通。今回はユーザーリソースのid=1を参照したいので、/users/1というURLに対して、GETリクエストを発行するということ。RailsのREST機能が有効になっていればGETリクエストは自動的に**showアクション**になる。
そして/users/1のURLを有効にするためには以下のコードを追加する。
```ruby
config/routes.rb

Rails.application.routes.draw do
  root "static_pages#home"
  get  "/help",    to: "static_pages#help"
  get  "/about",   to: "static_pages#about"
  get  "/contact", to: "static_pages#contact"
  get  "/signup",  to: "users#new"
  resources :users # ←ここ！
end
```
上記のコードを追記すれば、/users/1だけではなく、ユーザーのURLを生成するための多数の名前付きルーティングと共に、RESTfulなUsersリソースで必要となる、全てのアクションが利用できるようになる。
|HTTPリクエストメソッド|URL|アクション|名前付きルーティング|用途|
|---|---|---|---|---|
|GET|/users|index|users_path|全てのユーザーを一覧するページ|
|GET|/users/1|show|user_path(user)|特定のユーザーを表示するページ|
|GET|/users/new|new|new_user_path|ユーザーを新規作成するページ(ユーザー登録)|
|POST|/users|create|users_path|ユーザーを作成するアクション|
|GET|/users/1/edit|edit|edit_user_path(user)|id=1のユーザーを編集するページ|
|PATCH|/users/1|update|user_path(user)|ユーザーを更新するアクション|
|DELETE|/users/1|destroy|user_path(user)|ユーザーを削除するアクション|

ただしルーティング先のページはまだない。そのため最小限のページを作成する。
今回がジェネレータを使ってないのでファイルは手動で作成。
```erb
app/views/users/show.html.erb

<%= @user.name %>, <%= @user.email %>
```
ユーザー表示ビューが正しく動作するには、Usersコントローラないのshowアクションに対応する@user変数を定義する必要がある。今回はUserモデルのfindメソッドを使ってデータベースからユーザーを取り出す。
```ruby
app/controllers/users_controller.rb

class UsersController < ApplicationController

  def show
    @user = User.find(params[:id])
  end

  def new
  end
end
```
Usresコントローラにリクエストが正常に送られると、params[:id]の部分はユーザーidの1に置き換わる。

この時点で画面を表示し、デバッグ情報を見てみると
```
{"controller"=>"users", "action"=>"show", "id"=>"1"}
```
### debuggerメソッド
debug情報をより直接的に取り出すのがdebuggerメソッド。
```ruby
app/controllers/users_controller.rb

class UsersController < ApplicationController

  def show
    @user = User.find(params[:id])
    debugger # ←ここ！
  end

  def new
  end
end
```
これを記述したら、ブラウザから/users/1にアクセスし、Railsサーバーを立ち上げているターミナルを見てみる。いろいろ見れるので詳しくは本文の3文の1らへんをチェック。
### Gravatar画像とサイドバー
今回はGravatarを使ってアバター画像をプロフィールに導入する。これは、プロフィール写真をアップロードして、指定したメールアドレスと関連づけることができるサービスである。
```erb
app/views/users/show.html.erb

<% provide(:title, @user.name) %>
<h1>
  <%= gravatar_for @user %>
  <%= @user.name %>
</h1>
```
**ヘルパーファイルで定義されているメソッドは、デフォルトで自動的に全てのビューで利用できる**。ここでは、利便性を考え、garavatar_forをUsersコントローラに関連づけられているヘルパーファイルに置くことにする。
GravatarのURLはユーザーのメールアドレスをMD5という仕組みで**ハッシュ化**している。Rubyでは、Digestライブラリのhexdigestメソッドを使うとMD5のハッシュ化を実現できる。
```
>> email = "MHARTL@example.COM"
>> Digest::MD5::hexdigest(email.downcase)
=> "1fda4469bcbec3badf5418269ffc5968"
```
メールアドレスは大文字と小文字を区別しないが、MD5は区別するのでdowncaseメソッドを使って、hexdigestの引数を小文字に変換している。
```ruby
app/helpers/users_helper.rb

module UsersHelper

  # 引数で与えられたユーザーのGravatar画像を返す
  def gravatar_for(user)
    gravatar_id  = Digest::MD5::hexdigest(user.email.downcase)
    gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}"
    image_tag(gravatar_url, alt: user.name, class: "gravatar")
  end
end
```
サイドバーに関してはCSSをごちゃごちゃして、どうにかする。
### form_withを使用する
次はユーザー登録フォームを作る。Railsではform_withヘルパーメソッドを使う。
ユーザー登録ページの/signupのルーティングは、Usersコントローラのnewアクションに紐づけられているので、次のステップはfom_withの引数で必要となるUserオブジェクトを作成すること。必要となる@user変数の定義は、次のようになる。
```ruby
app/controllers/users_controller.rb

class UsersController < ApplicationController

  def show
    @user = User.find(params[:id])
  end

  def new
    @user = User.new
  end
end
```
htmlはこんな感じ
```erb
app/views/users/new.html.erb

<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_with(model: @user) do |f| %>
      <%= f.label :name %>
      <%= f.text_field :name %>

      <%= f.label :email %>
      <%= f.email_field :email %>

      <%= f.label :password %>
      <%= f.password_field :password %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation %>

      <%= f.submit "Create my account", class: "btn btn-primary" %>
    <% end %>
  </div>
</div>
```
### ユーザー登録フォームのHTML
HTMLの構造を細かく見ていく。
```
<%= form_with(model: @user) do |f| %>
  .
  .
  .
<% end %>
```
fはformのこと。このfオブジェクトは、HTMLフォーム要素に対応するメソッドが呼び出されると、@userの属性を設定するために特別に設計されたHTMLを返す。
```
<%= f.label :name %>
<%= f.text_field :name %>
```
このようにすると、Userモデルのname属性を設定する、ラベル付きテキストフィールド要素を作成するのに必要なHTMLを作成する。生成されたものはこんな感じ
```
<label for="user_name">Name</label>
<input type="text" name="user[name]" id="user_name" />
```
ユーザー作成で重要なのがinputごとにある**特殊なname属性**。
これらのname属性値のおかげで、Railsはparams変数から初期化ハッシュを構成できる。

次に重要なのは**formタグ自身**。Railsはformタグを作成する時に@userオブジェクトを使う。すべてのRubyオブジェクトは自分のクラスを知っているので、Railsは@userのクラスがUserであることを認識する。また、@userは新しいユーザーなので、Railsはpostメソッドでフォームを構築すべきだと判断する。
```
<form action="/users" accept-charset="UTF-8" method="post">
```
/usersに対してHTTPのPOSTリクエストを送るという意味。
### ユーザーの登録失敗
無効なデータ送信を受け付けるユーザー登録フォームを作成し、ユーザー登録フォームを更新してエラー一覧を表示する。
### 正しいフォーム
/usersへのPOSTリクエストはcreateアクションに送られる。ここではcreateアクションでフォーム送信を受け取り、User.newを使って新しいユーザーオブジェクトを作成し、ユーザーを保存(または失敗)し、再送信できるようにユーザー登録ページを表示するという方法で機能を実装する。

まずはユーザー登録の失敗に対応できるcreateアクションを作成する。
```ruby
app/controllers/users_controller.rb

class UsersController < ApplicationController

  def show
    @user = User.find(params[:id])
  end

  def new
    @user = User.new
  end

  def create
    @user = User.new(params[:user])    # 実装は終わっていないことに注意!
    if @user.save
      # 保存の成功をここで扱う。
    else
      render 'new', status: :unprocessable_entity
    end
  end
end
```
status: :unprocessable_entityについて。これはHTTPステータスコード433 Unprocessable Entityに対応するもので、Turbo(後々登場)を用いて通常のHTMLをレンダリングする場合に必要。

この時点で無効なデータを送信すると、エラー画面に飛ばされる。

ここでデバッグ情報の内パラメーターハッシュのuserの部分を詳しく見てみる。
```
"user" => { "name" => "Foo Bar",
            "email" => "foo@invalid",
            "password" => "[FILTERED]",
            "password_confirmation" => "[FILTERED]"
          }
```
このハッシュはUsersコントローラにparamsとして渡される。このparamsハッシュには各リクエストの情報が含まれている。このハッシュのキーがinputタグにあったname属性の値になる。
例:user[name(←ここの部分)]

ちなみに、デバッグ情報ではハッシュのキーが文字列になっているが、**Railsではシンボルになっている点に注意**。
```
@user = User.new(params[:user])
```
上のコードは
```
@user = User.new(name: "Foo Bar", email: "foo@invalid",
                 password: "foo", password_confirmation: "bar")
```
上のコードと同じ意味。
昔はこのコード(@user = User.new(params[:user]))でも動いたが、悪意のあるユーザーにアプリケーションのデータベースが書き換えられないために、**Strong Parameters**を使うようになった。
### Strong Parameters
paramsハッシュ全体を初期化するという行為はセキュリティ上**極めて危険**。これはユーザーが送信したデータを丸ごとUser.newに渡していることになる。例えば管理者権限みたいなものがあったとき、それを簡単に書き換えられたら恐ろしい。そこで**strong Parametersをコントローラ層で使う**ことで問題は解決できる。
```
params.require(:user).permit(:name, :email, :password, :password_confirmation)
```
この場合、paramsハッシュでは:user属性を必須とし、名前、メールアドレス、パスワード、パスワードの確認の属性をそれぞれ許可し、それ以外は認めないようにする。

これらのパラメータはuser_paramsのような補助メソッドの形で使うのが定番。このメソッドはUsersコントローラ内部のみで実行され、Web経由で外部ユーザーに公開する必要はないため、privateキーワードを使い**外部から使えない**ようにする。
```ruby
app/controllers/users_controller.rb

class UsersController < ApplicationController
  .
  .
  .
  def create
    @user = User.new(user_params) # ←ここと
    if @user.save
      # 保存の成功をここで扱う。
    else
      render 'new', status: :unprocessable_entity
    end
  end

  private # ←ここ！

    def user_params
      params.require(:user).permit(:name, :email, :password,
                                   :password_confirmation)
    end
end
```
### エラーメッセージ
RailsはエラーメッセージをUserモデルの検証時に自動生成してくれる。
```
>> user = User.new(name: "Foo Bar", email: "foo@invalid",
?>                 password: "dude", password_confirmation: "dude")
>> user.save
=> false
>> user.errors.full_messages
=> ["Email is invalid", "Password is too short (minimum is 6 characters)"]
```
errors.full_messagesオブジェクトは、エラーメッセージの配列を持っている。
```erb
app/views/users/new.html.erb
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_with(model: @user) do |f| %>
      <%= render 'shared/error_messages' %> # ←ここ

      <%= f.label :name %>
      <%= f.text_field :name, class: 'form-control' %>

      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

      <%= f.label :password %>
      <%= f.password_field :password, class: 'form-control' %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation, class: 'form-control' %>

      <%= f.submit "Create my account", class: "btn btn-primary" %>
    <% end %>
  </div>
</div>
```
Railsの慣習として、複数のビューで使われるパーシャルは専用のディレクトリ「shared」　に置かれる。
```
mkdir app/views/shared
touch app/views/shared/_error_messages.html.erb
```
```erb
app/views/shared/_error_messages.html.erb

<% if @user.errors.any? %>
  <div id="error_explanation">
    <div class="alert alert-danger">
      The form contains <%= pluralize(@user.errors.count, "error") %>.
    </div>
    <ul>
    <% @user.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
<% end %>
```
countメソッドは数を数え、any?はempty?の逆。これらはRubyの配列に対してそのまま使える。
またpluralizeは第一引数に数字、第二引数に文字列で、数字に合わせた形で文を作る。
### 失敗時のテスト
最初に新規ユーザー登録用の統合テストを作成する。
```
rails g integration_test users_signup
```
このテストでは、ユーザー情報が無効の場合はユーザー登録ボタンを押してもユーザーが作成されないことを確認する。ここではUser.countを使う。
まずはgetメソッドを使ってユーザー登録ページにアクセス。
```
get signup_path
```
フォーム送信をテストするには、POSTリクエストをusers_pathに送信する必要がある。
```ruby
assert_no_difference 'User.count' do
  post users_path, params: { user: { name:  "",
                                     email: "user@invalid",
                                     password:              "foo",
                                     password_confirmation: "bar" } }
end
```
createアクションのUser.newで期待されているデータを、params[:user]というハッシュまとめている。
assert_no_differenceは違いがないという意味。なのでフォーム送信後もユーザーカウントが変わらないことをテストしている。

また、正しいレスポンスコードが返され、正しいテンプレートがレンダリングされることを検証するために、以下のアサーションを追加。
```
assert_response :unprocessable_entity
assert_template 'users/new'
```
完成形はこちら
```ruby
test/integration/users_signup_test.rb

require "test_helper"

class UsersSignupTest < ActionDispatch::IntegrationTest

  test "invalid signup information" do
    get signup_path
    assert_no_difference 'User.count' do
      post users_path, params: { user: { name:  "",
                                         email: "user@invalid",
                                         password:              "foo",
                                         password_confirmation: "bar" } }
    end
    assert_response :unprocessable_entity
    assert_template 'users/new'
  end
end
```
### ユーザー登録成功
次はフォームが有効な場合に新規ユーザーを実際にデータベースに保存できるようにし、ユーザー登録フォームを完成させる。
保存に成功するとデータベースに登録され、登録されたユーザーのプロフにリダイレクトする。
現状有効な情報で送信してもエラーになる。理由としてはcreateアクションに対応するビューがないこと。まずはリダイレクトから。
```ruby
app/controllers/users_controller.rb

class UsersController < ApplicationController
  .
  .
  .
  def create
    @user = User.new(user_params)
    if @user.save
      redirect_to @user # ←ここ！
    else
      render 'new', status: :unprocessable_entity
    end
  end

  private

    def user_params
      params.require(:user).permit(:name, :email, :password,
                                   :password_confirmation)
    end
end
```
### flash
登録時にウェルカムメッセージを表示したい。そんなときにflashを使う。
```ruby
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', status: :unprocessable_entity
    end
  end

  private

    def user_params
      params.require(:user).permit(:name, :email, :password,
                                   :password_confirmation)
    end
end
```
flash変数に代入したメッセージは、リダイレクトした直後のページで表示できるようになる。今回はflash内に存在するキーがあるかを調べ、キーがあればその値を全て表示するよう修正する。
```erb
<% flash.each do |message_type, message| %>
  <div class="alert alert-<%= message_type %>"><%= message %></div>
<% end %>
```
successならclassはalert-successになり、cssが適用される。
```erb
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>
```
### 成功時のテスト
今回の目的はデータベースの中身が正しいかどうか検証すること。すなわち、有効な情報を送信して、ユーザーが作成されたことを確認する。
```
assert_difference 'User.count', 1 do
  post users_path, ...
end
```
```ruby
test/integration/users_signup_test.rb

require "test_helper"

class UsersSignupTest < ActionDispatch::IntegrationTest
  .
  .
  .
  test "valid signup information" do
    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
```
follow_redirect!に注目。このメソッドは、POSTリクエストを送信した結果を見て、指定されたリダイレクト先に移動するメソッド。
ユーザー登録が成功した後には、UserのルーティングとUserのshowアクション、そしてshow.html.erbがそれぞれ正しく動いている必要がある。