つぶやきアプリを作ってみよう⑧

新規登録、ログイン、ログアウトなどを実装するために
ユーザー管理機能を追加していきます。

##deviceというGemを利用します。

Gemfileの最後にdeviceを追加記述しましょう。

```rb:Gemfile
gem 'devise'
```

Gemをインストールしましょう。

```
% bundle install
```

Gemを導入した際にはサーバーの再起動が必要です。

```
% rails s
```

##rails g devise:installコマンド
追加したdeviseというGemの「設定関連に使用するファイル」を自動で生成するコマンドです。

```
% rails g devise:install
```

##rails g deviseコマンド
deviseによるユーザー機能の対象を指定することで、
モデルとマイグレーションの生成やルーティングの設定などをまとめて処理します。
ルーティングにはdeviseに関連するパスが追加されます。

```
# deviseコマンドでUserモデルを作成
% rails g devise user
```

ルーティングを確認してみましょう。

```rb:config/routes.rb
Rails.application.routes.draw do
  devise_for :users
  root to: 'tweets#index'
  resources :tweets
end
```
devise_forは、ユーザー機能に必要な複数のルーティングを
まとめて生成してくれるdeviseのメソッドです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1008745/1f5a4521-f7d1-f28d-9dd6-30ef8e357f3f.png)



マイグレーションファイルを確認してみましょう。

```rb:db/migrate/########_devise_create_users.rb
class DeviseCreateUsers < ActiveRecord::Migration[6.0]
 def change
   create_table :users do |t|
     ## Database authenticatable
     t.string :email,              null: false, default: ""
     t.string :encrypted_password, null: false, default: ""

     ## Recoverable
     t.string   :reset_password_token
     t.datetime :reset_password_sent_at

     ## Rememberable
     t.datetime :remember_created_at

     # 省略

     t.timestamps null: false
   end

   add_index :users, :email,                unique: true
   add_index :users, :reset_password_token, unique: true
   # add_index :users, :confirmation_token,   unique: true
   # add_index :users, :unlock_token,         unique: true
 end
end
```

Eメールやパスワードなどの設定が確認できますね。

##マイグレーションを実行します。

```
# マイグレーションを実行
% rails db:migrate
```
テーブル・カラム情報を変更したため、ローカルサーバーを再起動してください。

```
% rails s
```

##deviseのビューファイルを作成
ビューファイルは自動で生成されないのでコマンドを実行する必要があります。
deviseのGem内に存在するビューファイルを読み込んでいるためです。

##rails g devise:viewsコマンド

```
% rails g devise:views
```
このコマンドでビューファイルを生成することができます。
サインアップ画面はapp/views/devise/registrations/new.html.erb
ログイン画面のビューはapp/views/devise/sessions/new.html.erb
というビューファイルが対応しています。

装飾したい場合はこのビューを変更します。（今回は最低限行います）

```erb:app/views/devise/registrations/new.html.erb
<h2 class="contents" >新規登録</h2>

<div class="center">

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= render "devise/shared/error_messages", resource: resource %>

  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
  </div>

  <div class="field">
    <%= f.label :password %>
    <% if @minimum_password_length %>
    <em>(<%= @minimum_password_length %> characters minimum)</em>
    <% end %><br />
    <%= f.password_field :password, autocomplete: "new-password" %>
  </div>

  <div class="field">
    <%= f.label :password_confirmation %><br />
    <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
  </div>

  <div class="actions">
    <%= f.submit "新規登録する" %>
  </div>
<% end %>

</div>
```

```erb:app/views/devise/sessions/new.html.erb
<h2 class="contents" >ログイン</h2>

<div class="center">
<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
  </div>

  <div class="field">
    <%= f.label :password %><br />
    <%= f.password_field :password, autocomplete: "current-password" %>
  </div>

  <div class="actions">
    <%= f.submit "ログインする" %>
  </div>
<% end %>

</div>
```

cssも当て直します。

```css:
.btn,
.post {
  padding: 8px 20px;
  font-size: 14px;
  border: 2px solid #57C3E9;
  color: #57C3E9;
  font-weight: bold;
  text-align: center;
  border-radius: 3px;
  display: inline-block;
  margin: 5px;
}
.btn:hover,
.post:hover {
  border-color: #9bdbf2;
  color: #9bdbf2;
}

footer {
  margin: 30px auto;
  padding: 10px;
  color: #D8D8D8;
  text-align: center;
}


header {
  margin: 30px auto;
  padding: 10px;
  color: #D8D8D8;
  text-align: center;
}

.content_post {
  text-align: center;
}

.container_box {
 text-align: center;
}

.container {
  margin: auto;
 display: block;
}

.contents {
  text-align: center;
}

.top_contents {
  display: flex;
  justify-content: center;
}

.center {
  width: 100vw;
  display: flex;
  justify-content: center;
}

```

以下にアクセスするとログイン画面が確認できます。
http://localhost:3000/users/sign_in

以下にアクセスするとサインアップ画面が確認できます。

http://localhost:3000/users/sign_up

サインアップ時に登録する情報はメールアドレスとパスワードの2つですが
加えてニックネームを登録できるようにしましょう。


テーブルにカラムを追加するために、マイグレーションを生成します。
以下のコマンドを実行します。
##rails g migrationコマンド

```
rails g migration Addカラム名To追加先テーブル名 追加するカラム名:型
```

必要なコードが記述された状態で、マイグレーションが生成されます。

ファイル名やクラス名などに使用される表記方法のルールをみてみましょう。

|表記方法|説明|例|
|---|---|---|
|キャメルケース|先頭が小文字で、単語の区切りを大文字で表す|adminUserCommentCreator|
|アッパーキャメルケース|キャメルケースの1つ。先頭から単語の区切りを大文字で表す(クラス名)|AdminUserCommentCreator|
|スネークケース|単語の区切りをアンダースコアで表す（メソッド名、変数名）|admin_user_comment_creator|

##usersテーブルにnicknameカラムをstring型で追加

```
# usersテーブルにnicknameカラムをstring型で追加するマイグレーションファイルを作成
% rails g migration AddNicknameToUsers nickname:string

# 作成したマイグレーションを実行
% rails db:migrate
```

テーブル・カラム情報を変更したため、ローカルサーバーを再起動してください。

```
% rails s
```

次はニックネーム情報をフォームから登録できるようにビューを編集します。
今回は、ニックネームを6文字以内で登録させるようにします。

###maxlengthオプション

maxlengthオプションは、inputタグを生成するヘルパーメソッドである、 text_fieldにつけることができるオプションです。入力できる最大文字数を指定できます。

```
#maxlengthオプションの一例
<div class="field">
  <%= f.text_field :nickname, autofocus: true, maxlength: "6" %>
</div>
```

```erb:app/views/devise/registrations/new.html.erb
<h2 class="contents" >新規登録</h2>

<div class="center">

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= render "devise/shared/error_messages", resource: resource %>

  <div class="field">
        <%= f.label :nickname %> <em>(6 characters maximum)</em><br />
        <%= f.text_field :nickname, autofocus: true, maxlength: "6" %>
      </div>

  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
  </div>

  <div class="field">
    <%= f.label :password %>
    <% if @minimum_password_length %>
    <em>(<%= @minimum_password_length %> characters minimum)</em>
    <% end %><br />
    <%= f.password_field :password, autocomplete: "new-password" %>
  </div>

  <div class="field">
    <%= f.label :password_confirmation %><br />
    <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
  </div>

  <div class="actions">
    <%= f.submit "新規登録する" %>
  </div>
<% end %>

</div>
```
##ストロングパラメーターの設定

deviseに関しても、他と同様にストロングパラメーターをコントローラーに記述したいところです。
しかし、deviseの処理を行うコントローラーはGem内に記述されているため、編集することができません。

##devise_parameter_sanitizerメソッド

deviseにおけるparamsのようなメソッドで、deviseのUserモデルに関わる「ログイン」「新規登録」などのリクエストからパラメーターを取得できます。

このメソッドとpermitメソッドを組み合わせることで、deviseに定義されているストロングパラメーターに対し、新しく追加したカラムも指定して含めることができます。

これまでのストロングパラメーターと同じく、新たに定義するプライベートメソッドの中で使用します。deviseの提供元では、新たに定義するメソッド名をconfigure_permitted_parametersと紹介していることから、慣習的にこのメソッド名で定義することが多いようです。

```rb:
private
def configure_permitted_parameters  # メソッド名は慣習
  # deviseのUserモデルにパラメーターを許可
  devise_parameter_sanitizer.permit(:deviseの処理名, keys: [:許可するキー])
end
```
devise_parameter_sanitizerに使用するpermitメソッドの引数の指定の仕方は異なります。

```rb:
# paramsのpermitメソッド
params.require(:モデル名).permit(:許可するキー)

# devise_parameter_sanitizerのpermitメソッド
devise_parameter_sanitizer.permit(:deviseの処理名, keys: [:許可するキー])
```

deviseのpermitは、第一引数にdeviseの処理名、第二引数にkeysというキーに対し、配列でキーを指定することで、許可するパラメーターを追加していきます。

第一引数の処理名には、deviseで設定されているsign_in, sign_up, account_update
が使用でき、それぞれの処理に対応しています。

第一引数で指定した処理に対して、第二引数のkeysで指定された名前と同じキーを持つパラメーターの取得を許可します。ビューに記述した各フォーム部品のname属性値が、フォームから送信されるパラメーターのキーになります。

deviseにストロングパラメーターを追加するコードは、deviseのコントローラーが編集できないため、application_controller.rbに記述していきます。

##application_controller.rbを編集

```rb:app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :configure_permitted_parameters, if: :devise_controller?

  private
  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname])
  end
end
```
:devise_controller?というdeviseのヘルパーメソッド名を指定して、deviseに関するコントローラーの処理のときだけメソッドを実行するように設定しています。

ビューを整えていきましょう。
ログインしているときはログアウトと新規投稿のボタンを表示
ログインしていないときはログインと新規登録のボタンを表示します。

##user_signed_in?メソッド
ユーザーがログインしていればtrueを、ログアウト状態であればfalseを返します。

```
# ログインしているユーザーのとき
user_signed_in?
#=> true

# ログインしていないユーザーのとき
user_signed_in?
#=> false
```


```erb:app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>Tsubuyaki</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <header class="header">
      <div class="header__bar row">
        <h1 class="grid-6"><a href="/">Tsubuyaki</a></h1>
       <div class="top_contents">
        <div class="user_nav grid-6">
       <% if user_signed_in? %>
          <div class="user_nav grid-6">
            <%= link_to "ログアウト", destroy_user_session_path, method: :delete,class: "post"  %>
            <%= link_to "投稿する", new_tweet_path, class: "post" %>
          </div>
        <% else %>
          <div class="grid-6">
            <%= link_to "ログイン", new_user_session_path, class: "post" %>
            <%= link_to "新規登録", new_user_registration_path, class: "post" %>
          </div>
        <% end %>

        </div>
      </div>
    </header>
    <%= yield %>
    <footer>
      <p>
        Copyright Tsubuyaki 2021.
      </p>
    </footer>
  </body>
</html>
```

しかしこのままだと問題があります。
ログインしていなくてもURLを直接入力してしまうと新規投稿パスに行けてしまうのです。
対策をしていきましょう。

##unless
ifと同様に、条件式の返り値で条件分岐して処理を実行するRubyの構文です。
ifは返り値がtrueのときにelseまでの処理が実行されますが、
unlessはfalseのときにelseまでの処理が実行されます。

redirect_toメソッド
Railsでリクエストと違うページに返すリダイレクト処理を行う際に使用するメソッドです。
コントローラー等での処理が終わった後、アクションに対応するビューファイルを参照せずに、別ページへリダイレクトさせることができます。

```
redirect_to action: :リダイレクト先となるアクション名
```

###exceptオプション
before_actionで使用できるオプションです。exceptは「除外する」という意味がありこの中に記述したアクションには処理が適用されません。

```rb:app/controllers/tweets_controller.rb
class TweetsController < ApplicationController
  before_action :set_tweet, only: [:edit, :show]
  before_action :move_to_index, except: [:index, :show]

  def index
    @tweets = Tweet.all
  end

  def new
    @tweet = Tweet.new
  end

  def create
    Tweet.create(tweet_params)
  end

  def destroy
    tweet = Tweet.find(params[:id])
    tweet.destroy
  end

  def edit
  end

  def update
    tweet = Tweet.find(params[:id])
    tweet.update(tweet_params)
  end

  def show
  end

  private
  def tweet_params
    params.require(:tweet).permit(:name, :image, :text)
  end

  def set_tweet
    @tweet = Tweet.find(params[:id])
  end

  def move_to_index
    unless user_signed_in?
      redirect_to action: :index
    end
  end
end

```

以上で終了です。以下の挙動になっていれば成功です。
##新規登録
![neko6.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1008745/ba61966a-7286-6c40-6c92-672d0f937568.gif)


##ログイン
![neko5.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1008745/a6da8c9f-503f-4ddb-39b8-26077120f08b.gif)

今回は以上です。