【自分用】railsチュートリアル 7章

やること
ユーザー登録機能追加
HTMLフォームで登録情報を送信
ユーザーを新規作成して情報をDBに保存
作成されたユーザーのプロフを表示
ユーザーを表示するためのページ作成
ユーザー用のRESTアーキテクチャを実装する第一歩
統合テストに追加

7.1 ユーザーを表示する
ユーザーの名前とプロフ写真を表示するためのページ作成する
まずトピックブランチ作成
＄  git switch -c sign-up

7.1.1 デバッグとRails環境
リスト7.1 レイアウトにデバッグ情報追加
debugメソッドとparams変数を使ってデバッグ用の情報が表示されるように
if Rails.env.development?
このコードで開発環境だけで表示されるようになる

リスト7.2 デバッグ出力を見やすくするcss

7.1.2 Usersリソース
RESTアーキテクチャに従ってユーザー情報をweb上に表示させる
データの作成、表示、更新、削除をリソースとして扱う
HTTP基準には４つの基本操作(POST GET PATCH DELETE)が定義されているので各アクションに割り当てていく

RESTの原則に従う場合、リソースへの参照はリソース名と一意のIDを使うのが普通
ユーザーをリソースとみなす場合 ID=1のユーザーを参照するのは /users/1のURLに対してGETリクエストを発行するのと同じ

リスト7.3 Usersリソースをroutesファイルに追加
/users/1にアクセスすると、ID=1のユーザーの情報が表示されるようになった
他に、Usersリソースで必要になるアクションが利用できるようになる
表7.1 Usersリソースが提供するRESTfulなルート

リスト7.4 ユーザー情報を表示するビュー

ユーザー表示ビューが動作するにはUsersコントローラ内のshowアクションに対応する@user変数を定義する
リスト7.5 Userモデルのfindメソッドを使ってDBからユーザーを取り出す
Usersコントローラにリクエストが送信されると、params[:id]の部分がユーザーidの1に置き換わる

7.1.3 debuggerメソッド
このメソッドを使うとrailsアプリケーションの挙動を確認できる
リスト7.6 debuggerをUsersコントローラに差し込む
リスト7.7 使った後は取り外す

7.1.4 Gravatar画像とサイドバー
Gravatarは無料のサービスで、プロフ写真をアップして指定したアドレスと関連付けできる
ユーザーのアドレスを組み込んだGravatar専用の画像パスを設定しておくと対応する画像が自動的に表示される
リスト7.8 ユーザー表示ビューに名前と画像を表示する

ヘルパーファイルで定義されたメソッドはすべてのビューで利用できる
gravatar_forメソッドをUsersコントローラに関連付けられているヘルパーファイルに置く
リスト7.9 gravatar_forヘルパーメソッド定義
GravatarのURLはユーザーのアドレスをMD5という仕組みでハッシュ化している
RubyではDigestライブラリのhexdigestメソッドを使うとハッシュ化できる
MD5ハッシュでは大文字と小文字を区別するのでRubyのdowncaseメソッドで引数を小文字に変換

リスト7.10 サイドバー作成
asideタグを使う
rowクラスとcol-md-4クラスも追加(bootstrap)

CSSのルールがネストしているのでアセットパイプラインでSassエンジンを使う
リスト7.11 サイドバーのスタイル

リスト7.2 ユーザー登録フォーム
web上からユーザーを登録できるようにする

7.2.1 form_withを使用する
ユーザー情報を入力するフォーム。railsではform_withヘルパーメソッドを使う
これはActive Recoordのオブジェクトを取り込みそのオブジェクトの属性を使ってフォームを構築する

/signupのルーティングはUsersコントローラのnewアクションにすでに紐付けられている
よって次はform_withの引数で必要になるUserオブジェクトを作成する
リスト7.14 @user変数の定義
リスト7.15 登録フォーム
リスト7.16 登録フォームのCSS
ミックスイン機能を導入している
ミックスインを使うとCSSルールをグループ化してパッケージにまとめさまざまな要素で使えるようになる

7.2.2 ユーザー登録フォームのHTML
コードを分けて見ていく
<%= form_with(model: @user) do |f| %>
  .
  .
  .
<% end %>
doはform_withが受け取るブロックを表す。fというブロック変数はformのこと
fオブジェクトはHTMLフォーム要素(テキストフィールド、ラジオボタン、パスワードフィールドなど)に対応するメソッドが呼び出されると、@userの属性を設定するためのHTMLを返す
<%= f.label :name %>
<%= f.text_field :name %>
このコードを実行するとUserモデルのname属性を設定する、ラベル付きテキストフィールド要素を作成するのに必要なHTMLを作成する

リスト7.17 フォームのHTMLソース
<%= f.label :name %>
<%= f.text_field :name %>
このERBは
↓
<label for="user_name">Name</label>
<input type="text" name="user[name]" id="user_name" />

<%= f.label :email %>
<%= f.email_field :email %>
このERBは
↓
<label for="user_email">Email</label>
<input type="email" name="user[email]" id="user_email" />

<%= f.label :password %>
<%= f.password_field :password %>
↓
<label for="user_password">Password</label>
<input type="password" name="user[password]" id="user_password" />

パスワードフィールドは文字がドットに置き換わる
emailフィールドはアドレス入力しやすいキーボードが表示される

ユーザー作成で重要なのはinputごとのname属性
これがあるからrailsはparams変数から初期化ハッシュを構成できる。このハッシュはユーザーが入力した値に基づいてユーザーを作成するときに使われる

次に重要なのはformタグ。railsはformタグを作成するときに@userオブジェクトを使う
＠userは新しいユーザーなので、railsはpostメソッドでフォームを構築すべきと判断する

<form action="/users" accept-charset="UTF-8" method="post">
/usersに対してHTTPのPOSTリクエストを送信する指示を送る

7.3 ユーザー登録失敗
無効なデータ送信を受け付けるユーザー登録フォームを作成しエラーの一覧を表示する

7.3.1 正しいフォーム
/usersへのpostリクエストはcreateアクションに送られる
createアクションでフォーム送信を受け取り、User.newを使って新しいユーザーオブジェクトを作成し、ユーザーを保存（もしくは保存失敗）し、再送信できるようにユーザー登録ページを表示する方法で機能を実装する

ユーザー登録フォームのコードを見直す
<form action="/users" accept-charset="UTF-8" method="post">
このHTMLはpostリクエストを/usersというURLに送信する
リスト7.18 createアクション
renderメソッドはコントローラのアクション内でも動作する
status: :unprocessable_entityはHTTPステータスコード422 Unprocessable Entityに対応するもので、Turboを用いて通常のHTMLをレンダリングする場合に必要
このフォームは--skip-bundleを指定してrails newを実行したアプリでは動作するが、Turboをインストールすると動かなくなる
現時点のアプリでstatus: :unprocessable_entityを書いておいても問題はない

実際に無効なユーザー登録データを送信してみる
デバッグ情報のパラメーターハッシュのuser部分を見てみる
"user" => { "name" => "Foo Bar",
            "email" => "foo@invalid",
            "password" => "[FILTERED]",
            "password_confirmation" => "[FILTERED]"
          }
このハッシュはUsersコントローラにparamsとして渡される。paramsハッシュには各リクエストの情報が含まれる
ユーザー登録情報の送信の場合、paramsには入れ子になったハッシュが含まれる
デバッグ情報ではフォーム送信の結果が送信された値に対応する属性とともにuserハッシュに保存されている
このハッシュのキーがinputタグにあったname属性の値になる

<input id="user_email" name="user[email]" type="email" />
user[email]という値はuserハッシュの:emailキーの値と一致する

ハッシュのキーはシンボルとしてUsersコントローラに渡している
このハッシュはUser.newの引数で必要となるデータと完全に一致する
マスアサインメントと呼ばれるコードは
@user = User.new(params[:user])
↓とほぼ同じ
@user = User.new(name: "Foo Bar", email: "foo@invalid",
                 password: "foo", password_confirmation: "bar")

セキュリティ性を高めるためにStrong Parametersというテクニックを使う

7.3.2 Strong Parameters
マスアサインメントは値のハッシュを使ってRubyの変数を初期化するもの
@user = User.new(params[:user])    # 実装は終わっていないことに注意
この実装が終わっていない理由はparamsハッシュ全体を初期化する行為はセキュリティ上危険だから
例 Userモデルに、webサイトの管理者であるかどうかを示すadmin属性があるとする
admin='1'という値をparams[:user]の一部に紛れ込ませればtrueにできる
paramsハッシュがまるごとUser.newに渡されてしまうとどのユーザーでもWebサイトの管理者権限を奪い取ることができてしまう

対策としてStrong Parametersというテクニックをコントローラ層で使う
strong parametersでは必須パラメータと許可済みパラメータを指定できる
paramsハッシュを丸ごと渡すとエラーが発生するのでrailsはデフォルトでマスアサインメントの脆弱性から守られるようになった

この場合paramsハッシュでは:user属性を必須とし、名前、メールアドレス、パスワード、パスワード確認の属性をそれぞれ許可し、それ以外は許可しないようにする
params.require(:user).permit(:name, :email, :password, :password_confirmation)
このコードの戻り値は許可された属性のみが含まれたparamsのハッシュ

これらのパラメータはuser_paramsのような補助メソッドの形で使う
このメソッドは適切に初期化したハッシュを返し、params[:user]の代わりになるように使う
@user = User.new(user_params)

user_paramsメソッドはUsersコントローラの内部のみで実行され、外部ユーザーに公開する必要はないので、rubyのprivateキーワードを使って外部から使えないようにする
リスト7.19
privateキーワード以降のコードを強調するためインシデントを深くしている

この時点で登録フォームが動くようになる
ただし、送信内容に不備があっても開発者用のデバッグ領域にしかエラーの内容が表示されない
また、有効なユーザー情報を送信しても実際には新しいユーザーが作成されない

7.3.3 エラーメッセージ
エラーメッセージを追加する
railsはこのようなメッセージをUserモデルの検証時に自動的に生成する
例えばメールアドレスが無効、パスワードが短すぎる状態で保存しようとする
$ rails console
>> 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)"]

このエラーメッセージをブラウザで表示するにはユーザーのnewページでエラーメッセージのパーシャルを出力する
form-controlというクラスを追加するとBootstrapが働く
リスト7.20 エラーメッセージが表示されるように
'shared/error_messages'というパーシャルをrender（描画）している
複数のビューで使われるパーシャルは専用のディレクトリ「shared」に置かれる
$ mkdir app/views/shared
$ touch app/views/shared/_error_messages.html.erb

リスト7.21 パーシャル中身
countメソッドはエラーの個数を返す
any?メソッドはempty?メソッドの逆
オブジェクトがある場合はtrue、ない場合はfalse
pluralizeテキストヘルパーも使える。第一引数に整数を渡すと第二引数の英単語を複数形にする

リスト7.22 エラーメッセージのCSS
railsは無効な内容の送信で元のページに戻されるとCSSクラス「field_with_errors」を持ったdivタグでエラー箇所を自動的に囲んでくれる
@extend関数を使ってBootstrapのhas-errorというCSSクラスを適用する

これでエラーメッセージが見やすくなる
現状、presence:trueバリデーションも、has_secure_passwordバリデーションも空のパスワードnilを検出するので同じエラーメッセージが２つ表示されてしまう
後ほど追加するallow_nil: trueオプションで解決できる

7.3.4 失敗時のテスト
無効な送信をしたときのテストを書いていく
新規ユーザー登録用の統合テストを生成する
コントローラのリソース名は複数形という慣習を踏まえ、統合テストのファイル名はusers_signupとする
$ rails generate integration_test users_signup
このテストではユーザー情報が無効な場合はユーザー登録ボタンを押してもユーザーが作成されないことを確認する
これを確認するためにユーザーの数をカウントする
関連ページのHTML要素をassert_selectでテストする。これにより今後うっかり要素を変更したときにも気付ける

まずgetメソッドでユーザー登録ページにアクセス
get signup_path

フォーム送信をテストするにはPOSTリクエストをusers_pathに送信する必要がある
assert_no_difference 'User.count' do
    post users_path, params: { user: { name: "",
                                       email: "user@invaild",
                                       password:              "foo",
                                       password_confirmation: "bar" } }
    end

createアクションのUser.newで期待されているデータをparams[:user]ハッシュにまとめている
assert_no_defferenceメソッドのブロック内でpostを使い、メソッドの引数に'User.count'を与えている。これはassert_no_differenceのブロックを実行する前後で引数の値（User.count）が変わらないことをテストしている
→つまり、①ユーザー数をメモ②無効なデータ送信③ユーザー数が変わらないか検証
また、正しいレスポンスコードが返され、正しいテンプレートがレンダリングされることを検証するために、アサーション追加
リスト7.23 無効なユーザーに対するテスト

7.4 ユーザー登録成功
次はフォームが有効な場合に新規ユーザーを実際にDBに保存できるようにしてユーザー登録フォームを完成させる

まずユーザーを保存できるようにする。保存成功するとユーザー情報は自動的にDBに登録される
次にブラウザの表示をリダイレクトして登録されたユーザーのプロフィールを表示する。ついでにメッセージも

7.4.1 登録フォームの完成
現状、有効な情報を送信するとエラーが発生する
原因はrailsはデフォルトのアクションに対応するビューを表示しようとした際にcreateアクションに対応するビューのテンプレートがないこと

createアクションに対応するテンプレートを作成することもできるが、ユーザー登録に成功した場合は別ページにリダイレクトする方が一般的
ここでは新しく作成されたユーザーのプロフィールページにリダイレクトする。場合によってはルートURLにリダイレクトさせる

リスト7.26 保存とリダイレクトを行うuserのcreateアクション

7.4.2 flash
登録完了時に表示されるページにメッセージを表示し、２度目以降にはそのページにメッセージを表示しないようにする
flash変数を使う
リスト7.27 フラッシュメッセージ追加

flash変数に代入したメッセージはリダイレクトした直後のページで表示できるようになる
今回はflash内に存在するキーがあるか調べ、キーがあればその値(メッセージ)を全て表示するようレイアウトを修正
falsh変数の内容をwebサイト全体にわたって表示できるようにすると次のようになる
<% flash.each do |message_type, message| %>
  <div class="alert alert-<%= message-type %>"><%= message %></div>
<% end %>

alert-<%= message_type %>
このコードは適用するCSSクラスをメッセージの種類によって変更するようにする
:successキーのメッセージが表示される場合適用されるCSSクラスはalert-successになる
:successキーはシンボルだったがテンプレート内に反映するとERBが自動的に"success"という文字列に変換している
Bootstrapはflashのクラス用に４つのスタイルを持っている（success、info、warning、danger）

リスト7.29 flash変数の内容をレイアウトに追加

7.4.3 実際のユーザー登録
実際にユーザー登録を試してみる
DBをリセット
$ rails db:migrate:reset

7.4.4 成功時のテスト
有効な送信に対するテストを書く。今後バグが埋め込まれたら検知できるようになる
このテストの目的はDBの中身が正しいか検証すること。つまり、有効な情報を送信してユーザーが作成されたことを確認する

assert_differenceメソッドを使う。このメソッドは第一引数に文字列（'User.count'）を取り、assert_differenceブロック内の処理の前後でUser.countの値を比較する。第二引数はオプション ここには比較した結果の差異(今回は1)を渡す

リスト7.31 有効なユーザー登録に対するテスト
users_pathにPOSTリクエストを送信した後にfollow_redirect!メソッドを使っている。このメソッドはPOSTリクエストを送信した結果を見て指定されたリダイレクト先に移動するメソッド
ユーザー登録が成功した後にどのテンプレートが表示されているか検証している
このテストを成功させるにはUserのルーティングとUserのshowアクション、show.html.erbビューがそれぞれ動いている必要がある。したがって1行で様々な機能をテストしている

7.5 プロ品質のデプロイ
アプリをデプロイして本番環境で動かせるようにする
実際にデータを操作できるようにするデプロイは今回が初めて
ユーザー登録をセキュアにするために本番用のアプリに重要な機能を追加する
その後デフォルトのWebサーバーを実際の世界で使われているWebサーバーに置き換えて、本番DBに設定を追加する
ここまでの変更をmainブランチにマージ
$ git add -A
$ git commit -m "Finish user signup"
$ git switch main
$ git merge sign-up

7.5.1 本番環境でのTLS
アプリのセキュリティ上の欠陥を修正するためにTLS(Transport Layer Security)。TLSはローカルのサーバーからネットワークにデータを送信する前に大事な情報を暗号化する技術
railsでは本番環境用の設定ファイルであるproduction.rbのコードをたった１行変更するとあらゆるブラウザでTLSを強制してhttpsによる安全な通信を確立できる
リスト7.34 TLSを使うようにする

この段階でリモートサーバーにTLSを設定する
本番サイトでTLSを使えるようにするには自分のドメインで利用するTLS/SSL証明書を購入して設定する必要がある
手間がかかるが、ここではそうした作業は不要
Renderドメイン内で実行されるアプリケーションについてはRenderのTLS証明書が利用できるため

※カスタムドメインのTLSをCloudflareで扱う場合は、railsアプリ側でTLS/SSLを強制的に有効にする設定を行ってはいけない
代わりにproduction.rbファイルのconfig.force_sslをコメントアウトしたままにするか、明示的にfalseにする
リスト7.35 明示的にfalse

7.5.2 本番環境用のWebサーバー
本番環境に適したWebサーバーの設定も必要。本番環境ではPumaというHTTPサーバーを使う。Pumaは大量のリクエストを受信できる能力を持ったサーバー
puma gemはデフォルトでgemfileに追加されている

リスト7.36 本番環境のWebサーバー設定ファイル

設定したconfig/puma.rbファイルを使ってRenderがPumaのプロセスを実行できるよう、RenderのStart Commandを設定する
Renderダッシュボードの「Settings」でデフォルトのStart Commandをbundle exec puma -C config/puma.rbに置き換える

7.5.3 本番データベースを設定する
本番環境のDBでPostgreSQLが使われるようにする

リスト7.37 DB設定ファイルconfig/database.ymlのproductionセクションを更新する

7.5.4 本番環境へのデプロイ
変更をコミットし、デプロイする
$ rails test
$ git add -A
$ git commit -m "Use SSL and the Puma web server in production"
$ git push

ユーザー登録フォームが動いたら成功。アドレスバーに鍵アイコンが表示されているはず。これはTLSが動いていることを示す

production環境のDBをリセットすることもできる
リセット後は元に戻す
まず、bin/render-build.shファイルを変更する
-bundle exec rails db:migrate
+bundle exec rails db:migrate:reset

図7.29 次にRenderダッシュボードの「Environment」から環境変数DISABLE_DATABASE_ENVIRONMENT_CHECKを追加。値は１
これにより本番環境のデータを意図的にリセットすることを許容する

変更後にこれらの変更をコミットしデプロイすると本番環境のデータがまっさらになる
リセットしたら忘れずに元に戻す

7.6 最後に
今後は認証システムを導入してログインとログアウトをできるようにする
アカウントを更新したり削除できるようにする

7.6.1 本章のまとめ
debugメソッドで役立つデバッグ情報を表示できる
Sassのmixin機能を使うとCSSのルールをまとめたり他の場所で再利用できるように
railsには３つの環境がある・development開発環境、testテスト環境、production本番環境
標準的なRESTful URLを通してユーザー情報をリソースとして扱えるように
Gravatarを使うとユーザーのプロフィール画像を表示できるようになる
form_withヘルパーはActiveRecordのオブジェクトに対応したフォームを生成する
ユーザー登録に失敗した場合はnewビューをレンダリングするようにした。その際、ActiveRecordが自動的に検知したエラーメッセージを表示できるように
flash変数を使うと一時的なメッセージを表示できる
ユーザー登録に成功すると「DB上にユーザー追加」「プロフページにリダイレクト」「ウェルカムメッセージの表示」の順で処理が進む
統合テストを使うと送信フォームの振る舞いを検証したりバグの発生を検知できる
セキュアな通信と高いパフォーマンスを確保するために、本番環境ではTLSを導入してPumaを設定した