Ruby on Rails 第8章

[Rails Tutorial第8章](https://railstutorial.jp/chapters/basic_login?version=5.1#cha-basic_login)を見ていきます。

# この章で学ぶこと
- ログイン周り（第9章へも続く）
    - **Session：ブラウザを閉じると情報が破棄される**
    - Cookie：ログイン情報を自動で保存
    - Remember me：チェックボックスがオンの時のみログインを保存

# Session
## 概念
![image.png](https://qiita-image-store.s3.amazonaws.com/0/180679/85845d08-557c-7ab1-2b6e-a6279b3f5077.png)

>HTTPは言ってみれば、リクエストが終わると何もかも忘れて次回最初からやり直す健忘症的なプロトコルであり、過去を捨てた旅から旅の流れ者的なプロトコル

HTTPはStatelessのため、リクエストを受け渡した時点で全てリセットされてしまう。
そのため、HTTPプロトコルの上の階層のSessionを用いて半永続的にコンピューター間に値を保存する。

SessionもRESTfulなリソースとしてモデリングできた方が便利。

`rails generate controller Sessions new`

- new：ログインページ
- create：ログイン情報を作成
- destory：ログイン情報を破棄

なお、RESTfulでは通常modelを介してDBに永続的なデータを保存するが、
Sessionではcookiesを保存場所として持つ。

## 組立方
1. ログインページに必要なリソースを考える
2. 1.に合わせたRoute設定
3. ログインページのviewを構築（new）
4. ログイン時の認証設定
    - 入力が無効な場合：エラーメッセージを表示
    - 入力が有効な場合：入力された情報が有効かを判定
5. ログイン情報の別ページでの取得
6. ログアウト

##1. ログインページに必要なリソースを考える
既にSessionsのコントローラーを作成した際に下記のRouteが自動生成されている。

```ruby:routes.rb
get 'sessions/new'
```

今回は、ログインということで概念にも記載した通り、ページの表示だけでなく作成（POST）や破棄（DELETE）も必要になる。

##2. 1.に合わせたRoute設定
```ruby:routes.rb
get '/login', to:'sessions#new'
post '/login', to:'sessions#create'
delete '/logout', to:'sessions#destroy'
```

※ 設定されているRoutesの見方
`$ rails routes`
`$ rails routes | grep sessions`


##3. ログインページのviewを構築（new）
ここが結構厄介です。
ユーザー新規作成の時と同様に、 form_forヘルパーを利用するのですが、引数として渡していたインスタンス変数が今回はない。

ただ、form_forヘルパーの役割は、**どのpathにpostリクエストを投げるのか？**がメインと考えると簡単です。
createをするためのpathである login_path にpostリクエストを投げたいので下記のようになる。

```erb:app/views/sessions/new.html.erb
<%= form_for(:session, url:login_path) do |f| %>
...
 <%= f.email_field :email %>
...
 <%= f.password_field :password %>
...
 <%= f.submit "Log in", class:"btn btn-primary" %>
...
<% end %>
```

後は、Log inが押下されると createメソッドの中でparamsハッシュに入る値が下記のように取得できる。

`params[:session][:email]`
`params[:session][:password]`

これら入力された情報を用いて判定して行く。

##4. ログイン時の認証設定
```ruby:sessions_controller.rb
...
def create
 user = User.find_by(email: params[:session][:email].downcase)
 if user && user.authenticate(params[:session][:password])
  #成功
 else
  #失敗
 end
end
...
```

※ authenticateメソッドは、Userモデルの中で定義した `has_secure_password` が提供している認証用のメソッド。

###▼ 入力が無効な場合：エラーメッセージを表示
```ruby:sessions_controller.rb
...
flash.now[:danger] = "Invalid"
render 'new'
...
```

flashだけだとsessionに保存されるためsession切れるまでずっと表示されてしまう。
なので、nowをつけることによって一度だけ表示されるようにする。

###▼ 入力が有効な場合：入力された情報が有効かを判定
`session[:user_id] = users.id`

Railsで事前定義済みのsessionメソッドを使うことで上記のように簡単に実装可能。
上記でやってることは、実行されたブラウザ内の一時cookiesに暗号化済みのユーザーIDを自動で作成している。

##5. ログイン情報の別ページでの取得
▼ 取得方法1
`User.find(session[:user_id])`
=> IDが無効の場合、メソッドがnilを返さず、エラーになる。

▼ 取得方法2
`User.find_by(:id session[:user_id])`
=> IDが無効の場合、メソッドがnilを返す。

▼ 取得方法3

```
if @current_user.nil?
 @current_user = User.find_by(id: session[:user_id])
else
 @current_user
end
```
=> 面倒な書き方に見えるが、何度もDBに接続しなくて済むため高速化が見込まれる書き方。

▼ 取得方法4
`@current_user = @current_user || User.find_by(id: session[:user_id])`
=> current_userが入ってる時だけそれを再利用する。

**▼ 取得方法5（最も王道）**
`@current_user ||= User.find_by(id: session[:user_id])`
=> 取得方法4の簡易版

※ こういった何度も利用するメソッドはヘルパーに記載し、application_controller.rbにincludeするのが良い。
※ そうすることで、controllerやviewなどどこでも呼出をすることができるようになる。

## 6. ログアウト
Log outボタンが押されたらセッションを破棄する処理を入れれば問題ない。

```erb:_header.html.erb
<%= link_to "Log out", logout_path, method: :delete %>
```

```ruby:routes.rb
delete '/logout', to:'sessions#destroy'
```

```ruby:sessions_controller.rb
def destroy
 #ログアウト処理
 log_out
 redirect_to root_path
end
```

```ruby:sessions_helper.rb
def log_out
 sessions.delete(:user_id)
 @current_user = nil
end
```