Deviseでログイン機能

#1. はじめに
今回はDeviseを使ってログイン機能を作成していきます。
またDeviseの日本語化やBootstrap4の適用までを行っていきます。


#2. 準備

```
$ rails new devise-sample 
$ rails g controller homes index
```
```config/routes.rb
Rails.application.routes.draw do
  root 'homes#index'
end
```

#3. ナビバーを追加し，レスポンシブ対応のためのmetaタグを追加

```app/views/layouts/application.html.erb
 (略)
     <%= csrf_meta_tags %>
     <%= csp_meta_tag %>
     <meta name="viewport" content="width=device-width,initial-scale=1">　　//追加
 (略)
  <body>
     <%= render 'shared/header' %>     //追加
     <%= yield %>
   </body>
```



### 新しくsharedフォルダと各renderファイルを作成

#### フラッシュメッセージ

エラーメッセージを呼び出す。

```app/views/shared/_flash_messages.html.erb
<% flash.each do |msg_type, msg| %>
  <div class="alert alert-<%= msg_type %>" role="alert" id="alert">
    <a href="#" class="close" data-dismiss="alert">×</a>
    <%= msg %>
  </div>
<% end %>
```

```app/views/homes/index.html.erb
<%= render 'shared/flash_messages' %>
```
####ヘッダー
 ```app/views/shared/_header.html.erb
<header>
  <nav class="navbar navbar-expand navbar-light">
    <%= link_to "Deviseサンプル", root_path, class: 'navbar-brand' %>
    <div id="Navber">
      <ul class="navbar-nav">
        <% if user_signed_in? %>
          <li class="nav-item active">
            <%= link_to 'アカウント編集', edit_user_registration_path, class: 'nav-link' %>
          </li>
          <li class="nav-item active">
            <%= link_to 'ログアウト', destroy_user_session_path, method: :delete, class: 'nav-link' %>
          </li>
      <% else %>
          <li class="nav-item active">
            <%= link_to "新規登録", new_user_registration_path, class: 'nav-link' %>
          </li>
          <li class="nav-item active">
            <%= link_to "ログイン", new_user_session_path, class: 'nav-link' %>
          </li>
        <% end %>
      </ul>
    </div>
  </nav>
</header>
```

#4. Gemの追加

Gemfileに以下を追加して`$ bundle install`

```Gemfile
# ログイン機能に必要なGem
gem 'devise'

# 日本語化に必要なGem
gem 'rails-i18n', '~> 5.1'
gem 'devise-i18n'

# Bootstrapに必要なGem
gem 'bootstrap', '~> 4.4.1'
gem 'jquery-rails'
gem 'devise-bootstrap-views', '~> 1.0'
```
```
$ bundle install
```

##Bootstrapの導入

* `application.css`の拡張子を`scss`に変更

* `application.scss`から，`*= require_tree .`と`*= require_self`を削除

* `application.scss`に`@import "bootstrap";`を追加

* スタイルも追加

```app/assets/stylesheets/application.scss
@import "bootstrap";

// ログイン画面

.container-login {
  @extend .container-fluid;
  max-width: 576px;
  padding: 2rem;
}

// 「ログインしました」などのフラッシュ用スタイル

.alert-notice {
  @extend .alert-info;
}

.alert-alert {
  @extend .alert-danger;
}
```

###application.jsに３つ追加

Bootstrapと依存関係をapplication.jsに追記する

```app/assets/javascripts/application.js
//= require jquery3
//= require popper
//= require bootstrap-sprockets
```

補足
- Bootstrapのtooltipsやpopoverはpopper.jsに依存している
- bootstrapの依存gemにpopper_jsが指定されているため新たにインストールは不要
- コンパイルを高速化したい場合はbootstrap-sprocketsの代わりにbootstrapを指定する


##Deviseの導入
`Devise`をインストール（userの箇所は，任意のモデル名でOKです）

```
$ rails g devise:install
$ rails g devise user
$ rails db:create db:migrate
```

問題がなければ，`$ rails s`の後，`http://localhost:3000`からログインボタンを押せば，ログイン画面が表示されます。

![スクリーンショット 2020-11-16 15.19.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/752006/dd512864-c4ee-b2e8-b1ca-6b877cc8562e.png)

##Deviseの日本語化
```config/application.rb
module AssociationTutorial
  class Application < Rails::Application
    # 以下を追加すれば日本語に
    config.i18n.default_locale = :ja 
    # タイムゾーンも変更するなら，以下を追加
    config.time_zone = 'Asia/Tokyo'   
  end
end
```

サーバーを落として`$ rails s`
で再起動すれば日本語に変更されます。

![スクリーンショット 2020-12-17 18.16.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/752006/03399748-dd52-5352-2d20-c0547142edeb.png)


## 日本語訳を変更
日本語訳を変更したい場合は，次のコマンドで`config/locales/devise.views.ja.yml`を作成し，編集すればOKです。


```
$ rails g devise:i18n:locale ja
```

例えばアカウント登録を新規登録に変更したい場合は，`config/locals/devise.views.ja.yml`の該当文字を置換すればOKです。

パスワードを忘れましたか?も違和感がありますので，パスワードの再設定に置換するのがよいと思います


##ログイン画面などの変更
まず，次のコマンドでビューファイルを作成します。

```
$ rails g devise:i18n:views
$ rails g devise:views:bootstrap_templates -f
```

【参考】それぞれのコマンドの最後に例えばuserをつけることで，usersディレクトリ内にファイルを作成することもできますが，その場合は，次の3つの作業を行わないと反映されません。

`devise.views.ja.yml`30行目の`devise`を`users`に変更
`config/initializers/devise.rbにあるconfig.scoped_views = false`のコメントアウトを外して`true`に変更
サーバーを落として`$ rails s`で再起動


お好みで変えていく
例えば

```
<div class="container-login">
  # 元のプログラム
</div>
```
![スクリーンショット 2020-11-17 21.05.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/752006/b18e1cd9-4812-ecd1-dce6-95976cb542f3.png)

##エラーメッセージの変更
エラーメッセージの表示がいまいちなので変更するためにオーバーライドします。
`app/helpers/devise_helper.rb`ファイルを作成し、以下を記述。

```app/helpers/devise_helper.rb
module DeviseHelper
  def bootstrap_devise_error_messages!
    return "" if resource.errors.empty?

    html = ""
    resource.errors.full_messages.each do |error_message|
      html += <<-EOF
      <div class="alert alert-danger alert-dismissible" role="alert">
        <button type="button" class="close" data-dismiss="alert">
          <span aria-hidden="true">&times;</span>
          <span class="sr-only">close</span>
        </button>
        #{error_message}
      </div>
      EOF
    end
    html.html_safe
  end
end
```

![スクリーンショット 2020-11-17 21.16.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/752006/eee35f4e-f827-84e7-3cda-465cd30c9fb1.png)

###ログイン画面にもエラーメッセージを追加

```app/views/devise/sessions/new.html.erb
 <div class="container-login">
   <h1><%= t('.sign_in') %></h1>
   <%= render 'shared/flash_messages' %>    //追加

   <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
```

* `app/views/devise`ディレクトリ内のファイルの`btn btn-primary`を`btn btn-primary btn-block`に置換すればボタンの横幅が自然になります。


![スクリーンショット 2020-11-17 21.21.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/752006/33c11bf6-9e59-f8fe-5a88-c1c783fec992.png)

##バリデーション
バリデーションはフロント側にも簡単に入れられます。

* `f.email_field，f.password_field`に`required: true`を入れることで空欄投稿できなくなります。
* 新規登録（アカウント登録）画面では，例えば，`f.password_field`に`required: true, minlength: @minimum_password_length, maxlength: '30'`を追加すれば，文字数のバリデーションを追加できます。


```app/views/devise/registrations/new.html.erb
 <div class="form-group">
       <%= f.label :password %>
-      <%= f.password_field :password, autocomplete: 'current-password',
-                                      class: 'form-control' %>
+      <%= f.password_field :password, autocomplete: 'current-password',
+                                      class: 'form-control',
+                                      required: true,
+                                      minlength: @minimum_password_length,
+                                      maxlength: '30' %>
```

![スクリーンショット 2020-11-17 21.33.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/752006/a6992649-7fc0-0b6d-da76-b4c14ae8d494.png)

`_links.html.erb`を編集して，一番下のリンクをボタンにしてみます。

```app/views/devise/shared/_links.html.erb
<hr class="border-dark my-5">
<div class="form-group">
  <%- if controller_name != 'sessions' %>
    <%= link_to t(".sign_in"), new_session_path(resource_name), class: 'btn btn-info btn-block' %><br />
  <% end -%>

  <%- if devise_mapping.registerable? && controller_name != 'registrations' %>
    <%= link_to t(".sign_up"), new_registration_path(resource_name), class: 'btn btn-info btn-block' %><br />
  <% end -%>

  <%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
    <%= link_to t(".forgot_your_password"), new_password_path(resource_name), class: 'btn btn-secondary btn-block' %><br />
  <% end -%>

  <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
    <%= link_to t('.didn_t_receive_confirmation_instructions'), new_confirmation_path(resource_name), class: 'btn btn-secondary btn-block' %><br />
  <% end -%>

  <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
    <%= link_to t('.didn_t_receive_unlock_instructions'), new_unlock_path(resource_name), class: 'btn btn-secondary btn-block' %><br />
  <% end -%>

  <%- if devise_mapping.omniauthable? %>
    <%- resource_class.omniauth_providers.each do |provider| %>
      <%= link_to t('.sign_in_with_provider', provider: OmniAuth::Utils.camelize(provider)), omniauth_authorize_path(resource_name, provider), class: 'btn btn-info btn-block' %><br />
    <% end -%>
  <% end -%>
</div>
```
![スクリーンショット 2020-11-17 21.37.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/752006/4ead8d0a-6e65-75e2-9529-be259698393f.png)

さらに，リンクのログイン，新規登録（アカウント登録）を次のように変更してみます

```
# 上２つを次に置き換え

  <%- if controller_name != 'sessions' %>
    <%= link_to "アカウントをお持ちの方", new_session_path(resource_name), class: 'btn btn-info btn-block' %><br />
  <% end -%>

  <%- if devise_mapping.registerable? && controller_name != 'registrations' %>
    <%= link_to "アカウントをお持ちでない方", new_registration_path(resource_name), class: 'btn btn-info btn-block' %><br />
  <% end -%>
```

```app/views/devise/registrations/edit.html.erb
<!-- ここから -->
  <p><%= t('.unhappy') %>
    ? <%= link_to t('.cancel_my_account'), registration_path(resource_name), data: {confirm: t('.are_you_sure')}, method: :delete %>
    .</p>

  <%= link_to t('.back'), :back %>
<!-- ここまでを次に置き換える -->

  <hr class="devise-link my-5">
  <div class="form-group">
    <%= link_to "トップページ", root_path, class: 'btn btn-info btn-block mb-4' %>
    <%= link_to t('.cancel_my_account'), registration_path(resource_name), data: {confirm: t('.are_you_sure')}, method: :delete, class: 'btn btn-danger btn-block' %>
  </div>
```
![スクリーンショット 2020-11-17 21.47.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/752006/14b66ed3-4061-49b9-7e3f-d8cfdae3c8cf.png)

これで一応単純なスタイルは完成です。
もし間違っているところがあればご教授いただけると幸いです。