おっさん初学者のRailsチュートリアル学習記録(8章)

プログラミング年少組のど素人が、アウトプット用に素人目線の解説を書いていきます。
自分が自分に教えていることを想像しながら進めていきます。
下記と同じような境遇の方であれば参考になるかも

- まっさらなプログラミング初学者目線
- オール独学
- 勉強は得意じゃない
- 社会人のため、1日に勉強できる量は限られる。

電子書籍のwebテキスト(第６版)を購入して使用
Progateの「Web開発パス」完走済
ドットインストールのプレミアム会員卒業
Railsチュートリアル解説動画を見ながら学習中
#8章#
7章でユーザーの新規登録ができるようになりましたので、8章ではログインとログアウトができるようにします。

ブラウザがログインしている状態を保持し、ユーザーによってブラウザが閉じられたら状態を破棄するといった仕組みを作っていきます。このシステムを**認証システム**といいます。

また、ログイン済みでしかできない機能を実装していきます。この仕組みを**認可モデル**といいます。

まあ、、ここら辺の用語なんてどうでもいいんですけどね。。
これからの章ではそういった機能を使ってログインユーザー独自に対して可能になる操作などについて実装指定いく感じ見たいなことがつらつら書いてあります。

最初に戻ると、この8章と９章を通して以下の3点を学習していきます
1: ブラウザを閉じるとログインを破棄する（Sessionで解決）
2: ユーザーのログイン情報を自動で保存する（Cookie）
3: ユーザーがチェックボックスをオンにした場合のみログインを保存する（Remember me)

#8.1 セッション#
いきなりですが、ログインには**セッション（Session）**という機能が欠かせません。
セッションしてなかったら、ログインしたとしてもログイン状態を保持することができないので、ページを切り替えるごとにログイン状態が破棄されてしまい、再度ログインを要求される状況となります。

セッションするためにはログイン状態を保存する必要があります。
保存といいますとデータベースを想像しますが、今回はデータベースは使いません。
ですのでルーティングとビューとコントローラーでログインとログアウトの仕組みを作っていきます。

ログイン情報を保存する場所についてですが**cookies**とかいうものを利用します。
cookiesとはユーザーが見ているブラウザに保存される小さなテキストデータです。
「サイト閲覧の足あと」みたいな感じで保存されているのでしょうかよく分かりませんが。ちなみにこちらはページを遷移したところで破棄されません。
そしてここにユーザーIDなどの情報を保存することにします。
ブラウザに保存されている情報に、ユーザーのデータを紐付けているイメージでしょうか。

まあ、**「サイト閲覧の足あと」**を改良して**「ユーザーのサイト閲覧の足あと」**にする。といったところでしょうか。

この節では**sessionメソッド**を使ってサイトを閉じるまでログイン状態を保持できるものを見ていきます。

**ログインページ**では**new**で新しいセッションを**「出力」**し、そのページでログインすると**create**でセッションを実際に作成してブラウザの一時cookiesに暗号化済みユーザーIDを**「保存」**し、ログアウトすると**destroy**でセッションを**「破棄」**するという流れになるそうです。
このセッションはブラウザを閉じると有効期限が終了します
##8.1.1 Sessionコントローラー##
さて、さっそくログインのためのコントローラーやビューを作っていきましょう。
なっがーい前置きで説明した通り、今回データベースは出てきません。
まずはいつもの「rails generate controller」で「sessionsコントローラー」を作成します

```
$ rails generate controller Sessions new
```
ここのコントローラーで出てくるアクションは、出力の「new」、保存の「create」、破棄の「destroy」ですが、ビューが必要になるのは「new」だけなので上のコマンドでOKです。

コントローラーができたのでまずはルーティングを整理しましょう
「rails generate」でこれが勝手に作られます。

```routes.rb
get 'sessions/new'
```
これは、、、改善の余地がたくさんありますね。。。

```routes.rb
  get '/login', to: 'sessions#login'
  post '/login', to: 'sessions#create'
  delete '/logout', to: 'sessions#destroy'
```
「/login」って同じURLですが、GETリクエストで取得する時は「loginアクション」が、POSTリクエストで送信した時は「createアクション」が動くようになっています。
「delete」リクエストは初めて出てきましたが、読んで時のごとく「消去」なのでしょう。アクションも「destroyアクション」なので、どんな動作かは想像がつきます。

「rails g controller」ではテストもできるんでしたね

```sessions_controller_test.rb
class SessionsControllerTest < ActionDispatch::IntegrationTest
  test "should get new" do
    get sessions_new_url
    assert_response :success
  end
```
これも名前付きルートに変更してやりましょう

```sessions_controller_test.rb
    get login_path
    assert_response :success
```
とまあ、とりあえずルーティングとコントローラーの形が出来上がりました。
##8.1.2 ログインフォーム##
お次はログインのページビューを整えていきますか
ログインページは登録ページとほぼほぼ見た目同じで作っていきます
入力するところは「email」と「password」の2つだけです。
また、エラーメッセージはフラッシュで表示させます。

ほぼほぼ同じ、ということは「フォームを送信する」という機能を兼ねた動的なページを作成するということですが、登録の時と大きく違う部分が<font color="red">**「フォームを送信する先」**</font>です。
登録の時は、Userモデルという「データベース」に情報を送信していました。
が、最初に言った通り今回はデータベースには保存しません。
ですので、今回もフォーム送信ですので**「form_withヘルパー」**使うんですけど中身が違います。
とりあえず前回のやつがこれ

```
<%= form_with(model: @user, local: true) do |f| %>
```
データを送る先がUserモデルであり、データはインスタンス変数`@user`としていました。
今回は送るモデルがないのでそこらへんから違ってきます。だから言ったよね「フォームを送信する先」が違うと

```
form_with(url: login_path, scope: :session, local: true)
```
送る先が「url: login_path」になったんだな〜というのは分かりますが、**「scope: :session」**てなんよ！？モデルがない分送らないといけない情報が増えた結果追加されているみたいなのですが・・

今回は「form_with」で**どこに何を送りますよ〜**っていうことを指定します
「どこに」はなんとなく分かりますよね。「url」です
そして「何を」に該当するのが「scope」にあたります。。
今回は「:session」という値を入れています。ので、「login_pathに:sessionを送りますよ〜」となってるワケですはい。

もう少し突っ込んでみると、モデルが使えないのでこちらは使えません
　「`@session` = Session.new」
先ほどのUserモデルの方のform_withを見ると

```
model: @user # まだ何もデータが入ってないやつ
```
となっていましたよね。それは「@user = User.new」👈こちらが使えていたので成り立っていたワケです。

話を戻して「:session」って具体的に何ですかー？となりますが、それが次に見ていくことになります。

##8.1.3 ユーザーの検証と認証##
予告通りセッションの動作について見ていきましょうか

**セッションを作成する場合にやること**
1.入力が無効な場合の処理を行う
2.ログインが失敗した場合に表示されるエラーメッセージを配置する
3.ログインに成功した場合に使う土台部分を作成する

まずは、コントローラーにルーティングでルートにあらかじめ作成した「create」「destroy」アクションを作っていきましょう。
とりあえず「create」アクションが実行された場合に、newビューが表示されるように設定します

```sessions_controller.rb
  def create
    render 'new'
  end
```
ここで「rails server」を開いてログインページで入力してみるとデバッグ情報にこう出ています

```
session:
  email: 'user@example.com'
  password: 'foobar'
commit: Log in
action: create
controller: sessions
```
これは別の書き方だとこうなります

```
{ session: { password: "foobar", email: "user@example.com" } }
```
あの、ハッシュの中にハッシュがあるパターンです。
つまり

```
アクションでparams[:session]
```
とすることで、「password」と「email」の両方を取得することができ、

```
params[:session][:email]
```
これだと「email」のみ

```
params[:session][:password]
```
これだと「password」のみを取得することができます

そんなこんなでユーザーのログイン部分を実装したものがこちらです

```sessions_controller.rb
  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      # ユーザーログイン後にユーザー情報のページにリダイレクトする
    else
      # エラーメッセージを作成する
      render 'new'
    end
  end

```
って「#コメント」で書いてる部分がちらほらあるけども・・・
まず、変数「user」にデータベースの中から、ログインフォームに入力したemailアドレスに該当するものを探してきた結果を代入してます。
「if user」って何やねん
これは、<font color="red">「フォームで送られてきたユーザーのemailアドレスがデータベース内にあった場合」</font>
ということを表しています。要するに、あれば「true」なければ「false」ということになります。
また、「&&」が続いているので、フォームで送られてきたユーザーのemailアドレスがデータベース内にあった場合に「authenticate」メソッドを実行する。と言った流れになります。

上の行はいいとして次に出てきた「authenticate」です。。
これは6章で出てきてましたよ〜覚えてますか〜〜〜

**authenticateメソッド**
引数の文字列がパスワードと一致するとUserオブジェクトを、間違っているとfalseを返すメソッドであり、「ログインフォームに入力したパスワードをハッシュ化した値」と「password_digestに保存された値」を比較して、同じものであるかどうかを判断してくれます。

パスワードを比較して、違ったら「false」を返す・・・おお確かにそうだった。
なので
**「ユーザーがデータベースにあり、かつ、認証に成功した場合にのみここはtrueとなる」**
という感じでしょうね。

で、elseより下は認証に失敗した場合の動作を表していますので
new画面「ログイン画面」に戻される。ということでしょう
##8.1.4 フラッシュメッセージを表示する##
ユーザー新規登録失敗時のフラッシュ表示に関してはこんなことを書いています
「ユーザー登録に失敗した場合はnewビューを再描画するようにした。その際、Active Recordが自動的に検知したエラーメッセージを表示できるようにした」
（Active Recordとは、MVCで言うところのM、つまりモデルに相当するもの）
何度も言ってきましたが、セッションはデータベースを介さないものでした。
ですので、フラッシュメッセージは時と場合に合わせて自動的に作成されません。自分で指定してください。という話です。
そこでとりあえずで作られたのがこちら

```
flash[:danger] = 'Invalid email/password combination'
（これは正しいものではないが）
```
このコードで認証に失敗した時確かにエラーメッセージが赤字でいい感じに表示されます。（ [:danger] としてますからね「alert danger」）
が、メッセージがページを遷移しても消えないんです。
パッと出てきて、パッと消えるのがフラッシュでしたが、これは一体どういうことでしょうか。
つか、コードに正しいものではないって書いてますね・・・
##8.1.5 フラッシュのテスト##
さて、フラッシュ消えない問題を解決していきましょう
そのために「統合テスト」を書いていきます。
この、「フラッシュが消えない」というバグのような現象を解決するために作る「統合テスト」は今後より本格的な統合テストを作っていく一つの土台となるみたいです・・・

ということで「rails generation」で統合テスト（integrationテスト）を作成しましょう

```
$ rails generate integration_test users_login
```
いつものテストの流れである、失敗するテスト（バグが発生するテスト）を書いて、それが成功するようにコードを修正して、もう一回テストして成功させる。という流れは同じです。
今回は失敗する動作はもう出来上がっています（フラッシュが消えない）

さて、統合テストですが前回同様一連の流れを書いていくようにして進めます

1. ログイン用のパスを開く
2. 新しいセッションのフォームが正しく表示されたことを確認する
3. わざと無効なparamsハッシュを使ってセッション用パスにPOSTする
4. 新しいセッションのフォームが再度表示され、フラッシュメッセージが追加されることを確認する
5. 別のページ（Homeページなど） にいったん移動する
6. 移動先のページでフラッシュメッセージが表示されていないことを確認する

てな感じが一連の流れです。長いっすね。

```test/integration/users_login_test.rb
  test "login with invalid information" do
    # 1. ログイン用のパスを開く
    get login_path
    # 2. 新しいセッションのフォームが正しく表示されたことを確認する
    assert_template 'sessions/new'
    # 3. わざと無効なparamsハッシュを使ってセッション用パスにPOSTする
    post login_path, params: { session: { email: "", password: "" } }
    # 4. 新しいセッションのフォームが再度表示され、フラッシュメッセージが追加されることを確認する
    assert_template 'sessions/new'
    assert_not flash.empty?
    # 5. 別のページ（Homeページなど） にいったん移動する
    get root_path
    # 6. 移動先のページでフラッシュメッセージが表示されていないことを確認する
    assert flash.empty?
  end
```
冷静に見るとそのまんまって感じっすね。

```
    # 6. 移動先のページでフラッシュメッセージが表示されていないことを確認する
    assert flash.empty?
```
今回は最後のこれが引っかかってますね。消えないんですもん。。
ここだけテストするにはこうしましょう

```
$ rails test test/integration/users_login_test.rb # まだ失敗する
```
さてさて、問題の解決ですが、解決方法は至って簡単です。
sessionsコントローラーのflashに「now」を付ければいいだけ。
こうすることで、勝手にフラッシュメッセージが消滅してくれるようになります。

```sessions_controller.rb
flash.now[:danger] = 'Invalid email/password combination'
```
簡単すぎる解決策でしたね。解決したのでテストも成功するはずです。

しかし、なぜ解決できたのでしょうか。
前にflashを使った時は「now」なんて付けなくてもこんなバグは出てきませんでした。
これには**「redirect_to」と「render」の違い**が関係しているみたいです。
「redirect_to」：リクエストを送っている。
「render」：リクエストを送っていない。
flashはリクエスト先まで表示します。
renderで元のページに戻された時点ではリクエストはかかっておらず、次のページ遷移で最初のGETリクエストがかかることになりますのでフラッシュが表示されてしまっていたのです。
「now」をつけることで、renderで表示されたページをリクエスト1回目とカウントするように変更するのでしょう。きっと。
#8.2 ログイン#
無効な値を処理できるようにしたのでお次は有効な値を見ていきましょう。
これでログインできるように組み立てていきます。

ここで、最初に登場したあのよくわからんcookiesを使います。
とりあえずはブラウザを閉じるとログアウトしちゃう形で作って、９章でブラウザ閉じてもログイン状態を保持できるようにグレードアップしていきます。

セッションという機能はもちろん「実装」しなくてはなりません。
実装にあたっては、あれやこれやと面倒な設定が必要になります。
そこで使うメソッドなんかはセッション用ヘルパーモジュール内で管理していくことになります。

ですので、Railsの全クラスの親クラスであるApplicationコントローラーにこのセッション用ヘルパーモジュールを読み込ませればどのコントローラーでも使えるようになるみたいです。とりあえずこれだけチャチャっとやっちゃいます。

```application_controller.rb
class ApplicationController < ActionController::Base
 include SessionsHelper
end
```
##8.2.1 log_inメソッド##
まず、Railsが準備してくれている「sessionメソッド」を使って、単純なログインを行えるようにします。

```
session[:user_id] = user.id
```
これはセッションにユーザーIDを登録しています
session[:name] = user.name
こうするとセッションに名前を登録します
session[:email] = user.email
こうするとセッションにemailを登録します

session[:user_id]を実行すると、ユーザーのブラウザ内の「一時cookies」に「暗号化済の」ユーザーIDが作成されます。
**sessionを渡すと、ブラウザのcookiesとrailsサーバーの中に情報が入り、双方の情報が同じである状態である場合にログイン済となる**
まあ・・・ここはコードの見た目を分かりやすくするために「log_inメソッド」として上のセッション機能を定義しましょうかね。
「log_in」メソッドを定義するSessionsヘルパーはここに勝手にできてるよ
　👉　app > helpersの中

```sessions_helper.rb
  def log_in(user)
    session[:user_id] = user.id
  end
```
ログインのためのヘルパーメソッド（その名もlog_in）が完成しましたので、ログインの動作を決めているsessionsコントローラーのcreateアクションに搭載しましょうか

```sessions_controller.rb
  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      log_in user # これで上のuserはログイン状態になる
      redirect_to user # ユーザーのプロフィールページに飛ぶ
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
  end
```
ってこの2行が追加されただけですね

```
   log_in user => session[:user_id] = user.id
   redirect_to user
```
さっき作ったlog_inメソッドに user を渡してログインさせようとしているのね。ということかな。
**log_in user は log_in(user)のことね**
そして、「redirect_to」でユーザーのプロフィールページに飛ぶ。

まあ。ログインってすごい簡単ですよね。
session[:user_id] = user.id とするだけでOKなのですから。

##8.2.2 現在のユーザー##
今んところ、ログイン状態にすることはできましたが、見た目ではログインしているかどうか分かりません。。そこんところを解決していきましょうか。

まずは、ユーザー名を取り出せるようにします。
ログインしたら大体ユーザー名がどこかに表示されてますしね。
そんな時は**「current_user」メソッド**を定義して、セッションIDに対応するユーザー名をデータベースから取り出せるようにします。

```
<%= current_user.name %>
```
といった感じにしておけば「ログインしている」ユーザーネームが表示されるようになりますし、

```
redirect_to current_user
```
としておけば、「ログインしている」ユーザーのプロフィールページに飛ぶことができます。ようになる予定です。

この「current_user」ですが、現在のユーザーをデータベースから引っ張ってきて「current_user」にブッ込む。といった感じで定義したいものです。
これは「find_by」を使ってidで検索をかけます。

```
User.find_by(id: session[:user_id])
```
これを利用して「current_user」メソッドを定義します

```session_helper.rb
def current_user
 if session[:user_id] # ログインしてるんだったら
  User.find_by(id: session[:user_id])
 end
end
```
もはや「session[:user_id]」だけでログイン状態を確認してます。。。ifなので、session[:user_id]がtrueの場合ってことで、ログイン状態を確認するのでしょう。
セッションにユーザーIDがなくなった時点でこのコードは終了です。「nil」を返します。
（ここでいきなり「if」が入っているのは、ログインユーザー状態の問い合わせを毎回毎回データベースにしなくてよくなるからみたいです）
データベースへの問い合わせが多すぎると、その分速度にも影響が出ます。サイトの運用には大事な問題ですよね。やったら読み込み時間が長いのはストレスでしかありません。

```
if @current_user.nil?
  @current_user = User.find_by(id: session[:user_id])
  return @current_user
else
  return @current_user
end
```
いきなり何やこれ。となりますが、先ほどデータベースへの問い合わせをできるだけ減らすための対策として「if」が登場しました。今回もその話の続きで、さらにその問い合わせの件数を減らす方法を見ていきます。

これを**メモ化**と言います

コードに戻りましよう。
まずは、何でいきなりインスタンス変数が出てきているのかです。
インスタンス変数にして保存しておけば、何回も何回もそれを使い回せる性質を持っています。そっちを使ってればデータベースに問い合わせする必要は無くなりますもんね。
コードではインスタンス変数`@current_user`が存在しない場合(nilの場合)、セッションIDをデータベースから見つけてきて`@current_user`に代入します。
`@current_user`がnilではない場合は、インスタンス変数の結果を「再利用」します。

ちなみにこのコードについては**or演算子「 || 」**を使うことでスッキリさせることができます。

```
@current_user = @current_user ||(or) User.find_by(id: session[:user_id])
```
or演算子・・・左がtrueなら左を実行。左がfalse(nil)なら右を実行。

`@current_user`には`@current_user`もしくは`User.find_by(id: session[:user_id])`のどちらかが代入されますよーという意味でしょうか。。
これをさらに短縮できてこうなります

```
@current_user ||= User.find_by(id: session[:user_id])
```
もはやこれから最初の原型を想像することは難しいですけど・・・

最後にこちらを先ほどの「current_user」定義したものに組み込みますと・・・

```sessions_helper.rb
 def current_user
  if session[:user_id]
   @current_user ||= User.find_by(id: session[:user_id])
  end
 end
```
いや、難しいっすね。マジで。
##8.2.3 レイアウトリンクを変更する##
ではさっそく「current_user」を使ってログインしている時とそうでない時でレイアウトを変えてみましょう。
主に表示を変えるのはヘッダー部分です。
さっそくヘッダー部分のレイアウトの修正に取り掛かりたいところですが、その前にもう一つ便利なヘルパーメソッドを定義します。

ログイン状態かそうでないかを返す「logged_in?」メソッド(ログインしとりますよね？)を定義します

```session_helper.rb
 def logged_in?
  !current_user.nil? # current_userはnilの状態ですか？を否定
 end
```
「current_userが存在している状態か」を確認しています
trueなら存在してますし、falseなら存在していないことになります。

```
<% if logged_in? %>
  # ログインユーザー用のリンク
<% else %>
  # ログインしていないユーザー用のリンク
<% end %>
```
ログイン中のページならではのリンクがたくさん追加されるので、ヘッダーページが大改修工事になります

```_header.html.erb
   <% if logged_in? %> # ログイン中ならこっちを表示
     <li><%= link_to "Users", '#' %></li>
     <li class="dropdown">
       <a href="#" class="dropdown-toggle" data-toggle="dropdown">
         Account <b class="caret"></b>
       </a>
       <ul class="dropdown-menu">
         <li><%= link_to "Profile", current_user %></li>
         <li><%= link_to "Settings", '#' %></li>
         <li class="divider"></li>
         <li>
           <%= link_to "Log out", logout_path, method: :delete %>
         </li>
       </ul>
     </li>
    <% else %> # ログインしていなければこっちを表示
     <li><%= link_to "Log in", login_path %></li>
   <% end %>
```
ログイン中の新しいリンクとして「Profile」「Settings」「Log out」などが追加されています。
リンク先はまだ作ってないので、全部とりあえずの「'#'」が入っています。
気になるのは・・・リンクのパスですね〜

```
<%= link_to "Profile", current_user %>
```
え・・・リンクのパスでcurrent_userメソッド出てきとりますばい・・と慌ててしまいそうですが

```
<%= link_to "Profile", user_path(current_user) %>
```
こちらが省略されているだけみたいです。全くもって紛らわしい。
表示されるページは見ての通り、プロフィールページです。

それともう一つがこれ

```
<%= link_to "Log out", logout_path, method: :delete %>
```
「method: :delete」か。以前は「method: :post」で登場しました。リンクから飛ばすリクエストを指定していましたね。まあ、そういうことですわ。
（sessionsコントローラーのdestroyアクションにリクエストを送ります）

また、リンクをただ表示されるのではなくて、「ドロップダウン」にして洒落た感じにしてます。

この「ドロップダウン」、JavaScriptのテクニックで、何の設定もなしに使うことはできません残念でした。
もちろんレイアウトの話なのでBootstrapが登場しますが、以前行った「bootstrap-sass gem」の一部には自動でインクルードされません。なぜならこれではBootstrapのCSSの部分しか読み込んでないので。。
今回の「ドロップダウン」はJavaScriptのテクニックですのでBootstrapのJavaScriptを読み込まなくてはなりませぬ。
また、Bootstrapに含まれるJavaScriptライブラリの他にjQueryも読み込む必要があります。
RailsにはjQueryがデフォルトで入っていないらいしいので。。
ここら辺は言われた通りにハイハイやるしかなさそうですね。

まずはjQueryとBootstrapのJavaScriptをインストールします

```
$ yarn add jquery@3.4.1 bootstrap@3.4.1
```
その後は、アプリケーションでjQueryを有効にするためWebpackの環境ファイルを編集したり、application.jsファイルを編集したりでjQueryが動くようになります。
ここは・・・コピペしとくしかないっしょ！

そんなこんなでヘッダーのレイアウトは一応完成です。
##8.2.4 レイアウトの変更をテストする##
レイアウトを変更したので「統合テスト」を書いていきましょうヵ
統合テストで見ていく一連の動作は以下になります

1. ログイン用のパスを開く
2. セッション用パスに有効な情報をpostする
3. ログイン用リンクが表示されなくなったことを確認する
4. ログアウト用リンクが表示されていることを確認する
5. プロフィール用リンクが表示されていることを確認する

ということですので、さっそくテストを書いていきましょう・・・
と、いうワケにはいきません。今回は下準備が必要です。
そもそもデータベースにテストで使うためのユーザー情報が登録されていなければログインできません。。
ですのでテスト用のデータを**fixture（フィクスチャ）**に作成します。
fixtureファイルは以前チラッと出てきましたが、その時は中のデータを削除しただけでした。。

作成すると言っても単純で、データベースに登録する時に必要な情報を入れとけばいいのです。
が！そんな簡単にはいかせてくれません。

```fixtures/users.yml
  name: Michael Example
  email: michael@example.com
  password_digest: # ハッシュ化されたpassword
```
名前とemailアドレスはそのままの状態で入っていました。問題は**「パスワード」**です
セキュリティ上「password」カラムは作ってなくて、暗号化されたパスワードが入っている「password_digest」カラムにパスワードを保存していました。

「password_digest」にパスワード入れたいけどハッシュ化はどうやってやるのか問題です。
そこで文字列をハッシュ化してくれる**「digestメソッド」（自分で定義しないといけないやつ）**を使います。忘れかけていましたが、パスワードの暗号化は**has_secure_password**がやってくれてました。
この**has_secure_password**がやってくれたのと同じように文字列をハッシュ化する方法がこちらです

```
BCrypt::Password.create(string, cost: cost)
```
たしか、Gemfileにbcryptって入れましたよね。どうやらあれと関係しているみたいです。
あとは、ハッシュ化の複雑さの度合いを決めることもできます

```
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                  BCrypt::Engine.cost
```
今回MINとしていますので、こうすれば複雑さの度合いは軽くなります。所詮テストなので複雑な暗号にする必要はないですしね。

ハッシュ化する方法も解明しましたので、「digestメソッド」を定義しますか
どこで定義するかについてですが、ポイントはどこで使うかにもよります。
テキスト的にUserモデルに作ると使い勝手がいいらしいのでその通りにしましょう。

```models/user.rb
def User.digest(string)
    cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                  BCrypt::Engine.cost
  BCrypt::Password.create(string, cost: cost)
end
```
ええ〜っと・・・のっけから「User.」ってついてるのは何やねんって感じですよね
これまでメソッドを定義する時こんなんついてなかったっすもんね

基本に戻りましょう
**インスタンスメソッドとクラスメソッドについてです**
超簡単に言うと、クラス内で定義するメソッドがインスタンスメソッドで、クラスを「new」してできた実体に対して処理をします。
クラスはよく「設計図」に例えられますので
設計図から落とし込んで作ったデータを処理する：インスタンスメソッド
とりあえずで作ったデータを処理する：クラスメソッド
となりますかね。

インスタンスメソッドを「クラス名.メソッド名」もしくは「self.メソッド名」とすることで、クラスメソッドにすることができます。

今回の形を見ると、digestの頭についている「User.」はどうやらクラス名っぽいです。
（上を見ると「class User < ApplicationRecord」と書いてありますので）
よってこれは「User.digest」というクラスメソッドを定義しています。
では何で、クラスメソッドにしたのか問題です。それは定義した「digest」メソッドをどこで使うかがポイントです。

```fixtures/users.yml
  password_digest: # ここで使いたい
```
fixturesで使うテスト用のデータを暗号化するために使いたいのですけども、このデータ自体どこかに保存するワケでもないので、わざわざUserクラスをnewする必要もありません。
まさにテスト用に「とりあえず」作るデータです。
よってnewしてないものに処理を加えるメソッドは「クラスメソッド」となります（ちなみにnewしてないものにインスタンスメソッドをつけてもエラーになります）

最終的にfixturesにデータはこのようになります

```fixtures/users.yml
michael:
  name: Michael Example
  email: michael@example.com
  password_digest: <%= User.digest('password') %>
```
これでfixturesは完成です。「password」という文字列を、has_secure_passwordがやってくれるのと同じ形に暗号化してくれます。

テスト用のデータが完成したので、それをテストで使えるようにします。

```
user = users(:michael)
```
これで「user」から必要なところだけ抜き出せるようになります
このテスト用のデータを準備する作業はテスト開始時に一番最初に行うものですので「setup」です

```users_login_test.rb
def setup
  @user = users(:michael)
end
```
あれ。上と若干違うような・・・
（setupでインスタンス変数を定義することでテスト内でそのデータを使うことができるので）
これにだいぶ前に書いてたこれをコードにして書いていきます

1. ログイン用のパスを開く
2. セッション用パスに有効な情報をpostする
3. ログイン用リンクが表示されなくなったことを確認する
4. ログアウト用リンクが表示されていることを確認する
5. プロフィール用リンクが表示されていることを確認する

```users_login_test.rb
  test "login with valid information" do
    # ログイン用のパスを開く
    get login_path
    # セッション用パスに有効な情報をpostする
    post login_path, params: { session: { email:    @user.email,
                                          password: 'password' } }
    assert_redirected_to @user
    follow_redirect!
    assert_template 'users/show'
    # ログイン用リンクが表示されなくなったことを確認する
    assert_select "a[href=?]", login_path, count: 0
    # ログアウト用リンクが表示されていることを確認する
    assert_select "a[href=?]", logout_path
    # プロフィール用リンクが表示されていることを確認する
    assert_select "a[href=?]", user_path(@user)
  end
```
まずは説明ついてない部分について見てきましょう

```users_login_test.rb
  assert_redirected_to @user
```
ログイン後プロフィールページにリダイレクトしようとしているかを確認してますね

```users_login_test.rb
  follow_redirect!
```
これで、リダイレクト先に移動するみたいです。
「リダイレクト先に続く！」と言うことでしょうか・・・リダイレクトにセットにしといたら良さそうですね

```users_login_test.rb
  assert_template 'users/show'
```
プロフィールページが表示されているかを確認してますね

あとは見た通りです

```
assert_select "a[href=?]", リンク名
```
これはこの、リンクありますか〜の確認でした

「ないこと」確認については**「count: 0」**で０個あることを確認しています。
##8.2.5 ユーザー登録時にログイン##
ここでは新規登録後、そのままログインするように設定していきます
createアクションの登録成功時の流れにしれっと「log_in」を追加するだけです

```users_controller.rb
  def create
    @user = User.new(user_params)
    if @user.save
      log_in @user # ここに追加
      flash[:success] = "Welcome to the Sample App!"
      redirect_to @user
```
新たな機能を加えましたので、テストも作っときましょう。
ここでは、ログイン状態を確認するために作った「log_in?」は使いません。
変わりに、「is_logged_in?」ヘルパーメソッドを作ります。
何でかと言いますと・・・

```sessions_helper.rb
  def logged_in?
      !current_user.nil?
  end
```
「logged_in」はこんな風に定義していましが、テストでは「current_user」を呼び出せません。なので、テストヘルパーに「is_loggerd_in?（ログイン状態ですよね？）」を作ることにします

```test_helper.rb
  def is_logged_in?
    !session[:user_id].nil?
  end
```
言ってることはほぼほぼ同じですけどね・・・ログイン状態ですよね？を
作った「is_logged_in?」を統合テストの最後に加えときましょう

```users_signup_test.rb
  follow_redirect!
  assert_template 'users/show'
  assert is_logged_in?
```
#8.3 ログアウト#
さーてとうとうログアウトまできましたか
ヘッダーにログアウト用のリンクも作ってますし、セッションコントローラーにも「destroy」とだけ入ってますので、この「destroy」アクションをログアウト処理を書いていけば機能を実装できそうです。
今回も、セッションヘルパーに「log_out」メソッドを定義します。

```sessions_helper.rb
 def log_out
  session.delete(:user_id)
  @current_user = nil
 end
```
セッションしているユーザーIDを消しちゃってるので、ユーザーがnilになるということみたいですね
ついでに、インスタンス変数で使いまわしてた`@current_user`もnilにして消してます。

定義が済んだのでアクションに組み込みましょうか

```sessions_controller.rb
 def destroy
  log_out
  redirect_to root_url
 end
```
ログアウトしたら、メインページに飛ぶようにしてます。

さて機能の実装も完了しました。これだけかーい。
仕上げのテストにまいりましょう

先に完成したものから見てみますかね

```integration/users_test.rb
  def setup
    @user = users(:michael)
  end

  # 無効なログインテスト
  test "login with valid email/invalid password" do
    get login_path
    assert_template 'sessions/new'
  # 「POST」リクエストをログインパスに送ります
    post login_path, params: { session: { email:    @user.email,
                                          password: "invalid" } }
    assert_not is_logged_in?
    assert_template 'sessions/new'
    assert_not flash.empty?
    get root_path
    assert flash.empty?
  end
  
  # 有効なログイン〜ログアウトまでのテスト
  test "login with valid information followed by logout" do
    get login_path
    post login_path, params: { session: { email:    @user.email,
                                          password: 'password' } }
    assert is_logged_in?
  # プロフィールページへリダイレクトしようとしていますよね
    assert_redirected_to @user
  # リダイレクト先に進む！
    follow_redirect!
  # ユーザー詳細ページが表示されてるよね
    assert_template 'users/show'
  # 「ログイン」リンクは0個あるよね
    assert_select "a[href=?]", login_path, count: 0
  # 「ログイン」リンクはあるよね
    assert_select "a[href=?]", logout_path
  # 「プロフィール」リンクはあるよね
    assert_select "a[href=?]", user_path(@user)
  # 「DELETE」リクエストをログアウトパスに送る
    delete logout_path
  # ログインしていませんよね？
    assert_not is_logged_in?
  # HOME画面にリダイレクトしようとしていますよね
    assert_redirected_to root_url
  # リダイレクト先に進みます
    follow_redirect!
  # 「ログイン」リンクはありますよね
    assert_select "a[href=?]", login_path
  # 「ログアウト」リンクは0個ありますよね
    assert_select "a[href=?]", logout_path,      count: 0
  # 「プロフィール」リンクは0個ありますよね
    assert_select "a[href=?]", user_path(@user), count: 0
  end
end
```
長いですが落ち着いて一個ずつみていけばなんてこたないです。
統合テストなんて一連の流れをそのまま書いてるだけなので。。

ログアウトのためのセッションのdestroyアクションの定義とテストも完成したので、ついにサンプルアプリケーションの基本となる「ユーザー登録・ログイン・ログアウト」の機能すべてが完成しました

#8.4 最後に#
8章が完了しましたので、いつもの流れをやっときましょう

```
$ rails test
$ git add -A
$ git commit -m "Implement basic login"
$ git checkout master
$ git merge basic-login
$ rails test
$ git push
$ git push heroku
```
#8章の要約をようやく作り終えての感想#
冒頭からcookieが出てきて、ネットワークの知識があった方が有利だのどうのこうの言ってましたがあまり関係なかったですね。データベースではなくそこに保存するということだけ押さえとくみたいな感じです。
難易度が上がっていますので、集中して短期間でやることをお勧めします。ある程度の理解を持って次に進むことは必要ですが、時間をかけてやるよりも早くに仕上げて読み返す機会を増やして反復させる方が頭に定着すると思います。