単数リソースとログイン機能

#単数リソースとは
Railsでは基本的にリソースを扱う場合はそれが集合体であることが大半ですが、Webアプリの中には単数リソースという多くとも1つしか存在しないものがあります。例として「セッション」、「自分のアカウント情報」などがあります。
セッションとは、会員のログイン状態を管理するための情報を指します。アカウント情報も会員のテーブルに格納されている自分自身のレコードになるので0個または1個しか存在しない単数リソースということになります。

単数リソースの場合、集合を扱うindexアクションはありません。また、show,edit,update,deleteにはidパラメータは必要ありません。ログインしていれば自分のidはセッションに格納されています。

#セッションとクッキーについて
会員制のWebアプリではユーザーを識別し、有効な会員であるかを判定する仕組みを使います。そうした接続状態をセッションといいます。
HTTPではクライアントとサーバーはステートレスな関係なのでセッションを使わないと、同じユーザーがリクエストを送っても同じユーザーとはみなしません。

何らかの状態を保っておきたいときはクッキーを使います。クッキーとは、ブラウザがパソコンのハードディスクに保存する小さなデータです。サーバーがリクエストを受け取ったらセッションIDをブラウザに保存し、以降ユーザーを識別しています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/378838/ab7737d4-ec0b-9587-8ce7-291ad1edc194.png)

#セッションデータへのアクセス(ログイン・ログアウト)
セッションデータに会員のIDを保存する(ログイン)
```session[:member_id] = member.id```

セッションデータを消す(ログアウト)
```session.delete(:member_id)```


セッションデータはブラウザを閉じると消去されますが、ブラウザを閉じたり開いたりしてもログイン状態を保つには、セッションデータではなく通常のクッキーに情報を保存します。
```cookies.signed[:member_id] = member.id```

#パスワードの保存とユーザー認証
データベースに生のパスワードを保存するのはセキュリティの観点から望ましくないので、パスワードをハッシュ値で保存します。ハッシュ値とはデータを暗号学的ハッシュ関数と呼ばれる方式で変換するものです。
復元することもできませんし、異なる2つで同じハッシュ値になる可能性は極めて低いです。
暗号化にはbcryptを使います。

```Gemfile
gem 'bcrypt', '~> 3.1.7'
```

ハッシュ値を保存するためにmembers(会員)テーブルに、カラムpassword_digestを追加します。

```$ rails g migration AlterMembers1```

```db/migrate/年月日時分秒_alter_members1.rb
class AlterMembers1 < ActionRecord::Migration
  def change
    add_column :members, :password_digest, :string
  end
end
```
```$ rails db:migrate```

クラスメソッドhas_secure_passwordをモデルクラスに追加する。

```aap/models/member.rb
class Member < ApplicationRecord
  has_secure_password
end
```
この結果、Memberモデルにpassword及びpassword_confirmationの属性が追加されます。前者はパスワードそのもの、後者は確認用のパスワードです。
また、password属性に次のようなバリデーションが追加されます。
①　パスワードは空NG
②　パスワードの長さは72文字以下
③　パスワードと確認用のパスワードは一致しなければいけない

これでレコード挿入時にパスワードが必須になったので、シードデータに設定を加えます。

```db/seeds/development/member.rb
(省略)
  password: "パスワード"
  password_confirmation: "パスワード"
```

クラスメソッドhas_secure_passwordを呼びだすと、authenticateメソッドが追加されます。

```コンソール
member = Member.first

mamber.authenticate("デタラメなパスワード") => false

member.authenticate("設定したパスワード") => <Member id: 1,~~~~~>
```


authenticateメソッドを使ってユーザーをログイン状態にするコントローラを作成します。

```/config/routes.rb
(省略)
  resources :members do
    get "search", on: :collection
  end
  resource :session, only: [:create, :destroy]
end
```

単数リソースなのでresourceと:sessionが単数形になっています。またSessionControllerのアクションはcreateとdestroyしかありません。

```$ rails g controller sessions```
コントローラ作成は複数形です。

```app/controllers/sessions_controller.rb
class SessionsController < AppliationController
  def create
    member = member.find_by(name: params[:name])
    if member&.authenticate(params[:password])
      session[:member_id] = member.id
    else
      flash.alert = "名前とパスワードが一致しません"
    end
    redirect_to :root
  end

  def destroy
    session.delete(:member_id)
    redirect_to :root
  end
end
```

createはログインフォームの送信先となります。まずMemberモデルのfind_byメソッドを使い送信された名前に合致するメンバーを取り出します。そしたauthenticateメソッドで送信されたパスワードが正しいかどうかをチェックします。存在しない名前が送られてきたときは変数memberにnilがセットされるので、ボッチ演算子(.&)を用いてエラーを防いでくれます。
パスワードが正しい場合、Memberオブジェクトのidをセッションデータmember.idに保存します。
正しくない場合、フラッシュに警告メッセージをセットしいずれの場合もトップページにリダイレクトさせています。

続いてcurrent_memberメソッドを定義します。これはセッションデータ:member_idに値がセットされていれば該当する
Memberオブジェクトを返し、そうでなければnilを返すものです。

```app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  private def current_member
    Member.find_by(id: session[:member_id]) if session[:member_id]
  end
  helper_method :current_member
end
```

helper_methodは引数に指定されたメソッドをテンプレートの中でも使えるメソッドとして登録するものです。

ログインフォームを実際に使えるものに変える

```app/views/login_form.html.erb
<% if flash.alert %><p class="alert"><%= flash.alert %></p><% end %>    #ログイン失敗時のフラッシュメッセージ
<%= form_tag :session, id: "login_form" do %>                           #form_tagの送信先に:session(controllerのcreateアクション)、formタグのid属性を保持するために第2引数にオプションを加える
  <div>
    <label>ユーザー名</label>
    <input type="text" name="name>   #ユーザー名をinput要素に加える
  </div>
  <div>
    <label>パスワード</label>
    <input type="password" name="password">   #パスワードをinput属性に加える
  </div>
  <div>
    <input type="submit" value="ログイン">
  </div>
<% end %>
```

トップページでログイン中のユーザーにはメンバーの氏名とログアウトのリンクを表示します。これはcurrent_memberメソッドを使いログイン・ログアウトの表示の仕方を変えています。

```app/views/top.html.erb
(省略)
<if current_member == nil %>
  <%= menu_link_to "ログイン", :login %>   #ログインフォームへのリンク
  <%= menu_link_to "新規登録", :new_member %>   #新規登録画面へのリンク
<% else %>
  <% menu_link_to current_member.name + "さん", :account %>   #アカウント情報画面へのリンク
  <%= menu_link_to "ログアウト", :session, method: :delete, data: { confirm: "ログアウトしますか？ } %>
<% end %>
```

これでログイン時・ログアウト時の表示スタイルが変わりました。また間違ったユーザーまたはパスワードを打ち込んだときはエラーメッセージも表示されます。

参考文献
改訂4版 基礎 Ruby on Rails(IMPRESS KISO SERIES)
https://www.amazon.co.jp/改訂4版-Ruby-Rails-IMPRESS-KISO/dp/429500460X