deviseを使ったログイン機構の実装手順

# はじめに
Railsアプリケーションでログイン機構を実装する上で便利なgem"devise"の使い方を備忘録を兼ねてまとめました。


# （0.準備）
以下のdeviseの説明以前に新しいRailsプロジェクトを立ち上げサインインに使うトップページのインデックスアクションを用意しておきました。

```zsh
$ rails new sample_app
$ rails g controller home index
    
Running via Spring preloader in process 4619
      create  app/controllers/home_controller.rb
       route  get 'home/index'
      invoke  erb
      create    app/views/home
      create    app/views/home/index.html.erb
      invoke  test_unit
      create    test/controllers/home_controller_test.rb
      invoke  helper
      create    app/helpers/home_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    scss
      create      app/assets/stylesheets/home.scss
```

# 1.deviseのインストール
deviseをインストールします。
gemfileに追記し、ターミナルで bundle install してdeviseをインストールする。
(bundle install：Gemfile.lockとの差分をインストール
bundle update：Gemfile.lockの内容に関わらずGemfileをイチからインストール。)

```ruby:gemfile.rb
gem 'devise' # gemfileの一番下に記述
```

# 2.deviseの設定ファイルの生成

```zsh
$ rails g devise:install
```
設定ファイルのまとまったconfigディレクトリ配下にdeviseの設定ファイルが２つ生成されていることが確認できます。

```
(実行結果)
Running via Spring preloader in process 3816
      create  config/initializers/devise.rb
      create  config/locales/devise.en.yml
===============================================================================

Depending on your application's configuration some manual setup may be required:

  1. Ensure you have defined default url options in your environments files. Here
     is an example of default_url_options appropriate for a development environment
     in config/environments/development.rb:

       config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

     In production, :host should be set to the actual host of your application.

     * Required for all applications. *

  2. Ensure you have defined root_url to *something* in your config/routes.rb.
     For example:

       root to: "home#index"
     
     * Not required for API-only Applications *

  3. Ensure you have flash messages in app/views/layouts/application.html.erb.
     For example:

       <p class="notice"><%= notice %></p>
       <p class="alert"><%= alert %></p>

     * Not required for API-only Applications *

  4. You can copy Devise views (for customization) to your app by running:

       rails g devise:views
       
     * Not required *

===============================================================================
```
# 3.Userコントローラーの生成

```zsh
$ rails g devise:controllers users
```
devise専用コマンドでUserコントローラーを生成します。
app/controllers/users配下に６つのファイルが生成されたことが確認できます。

```
(実行結果)
      create  app/controllers/users/confirmations_controller.rb
      create  app/controllers/users/passwords_controller.rb
      create  app/controllers/users/registrations_controller.rb
      create  app/controllers/users/sessions_controller.rb
      create  app/controllers/users/unlocks_controller.rb
      create  app/controllers/users/omniauth_callbacks_controller.rb
===============================================================================

Some setup you must do manually if you haven't yet:

  Ensure you have overridden routes for generated controllers in your routes.rb.
  For example:

    Rails.application.routes.draw do
      devise_for :users, controllers: {
        sessions: 'users/sessions'
      }
    end
===============================================================================
```

# 4.Userモデルの生成

```zsh
$ rails g devise User
```
devise専用コマンドでUserモデルを生成します。


```
(実行結果)
Running via Spring preloader in process 4065
      invoke  active_record
      create    db/migrate/20210830043528_devise_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml
      insert    app/models/user.rb
       route  devise_for :users
```
```
$ rails db:migrate
```
忘れずにdb:migrateしてマイグレーションファイルをDBに反映させましょう。


# 5.viewファイルの生成

```terminal
kanji@MacBook-Pro % rails g devise:views 
Running via Spring preloader in process 4355
      invoke  Devise::Generators::SharedViewsGenerator
      create    app/views/devise/shared
      create    app/views/devise/shared/_error_messages.html.erb
      create    app/views/devise/shared/_links.html.erb
      invoke  form_for
      create    app/views/devise/confirmations
      create    app/views/devise/confirmations/new.html.erb
      create    app/views/devise/passwords
      create    app/views/devise/passwords/edit.html.erb
      create    app/views/devise/passwords/new.html.erb
      create    app/views/devise/registrations
      create    app/views/devise/registrations/edit.html.erb
      create    app/views/devise/registrations/new.html.erb
      create    app/views/devise/sessions
      create    app/views/devise/sessions/new.html.erb
      create    app/views/devise/unlocks
      create    app/views/devise/unlocks/new.html.erb
      invoke  erb
      create    app/views/devise/mailer
      create    app/views/devise/mailer/confirmation_instructions.html.erb
      create    app/views/devise/mailer/email_changed.html.erb
      create    app/views/devise/mailer/password_change.html.erb
      create    app/views/devise/mailer/reset_password_instructions.html.erb
      create    app/views/devise/mailer/unlock_instructions.html.erb
```
多くのファイルが自動生成されましたが特に重要なファイルが以下の２つ。
ユーザー登録画面：app/views/devise/registrations/new.html.erb
ログイン画面：app/views/devise/sessions/new.html.erb



# 6.フォームの生成
生成したUserモデルをもとに、トップページのビューに新規登録フォームを作ります。

```erb:index.html.erb
<h1>App Name</h1>

<% if user_signed_in? %> 
  <h4> メールアドレス： <%= current_user.email %> </h4>
  <%= link_to "ログアウト", destroy_user_session_path, method: :delete %> 
<% else %>
  <h2> 現在ログインしていません </h2>
  <%= link_to "ログイン", new_user_session_path, class: 'post' %> 
  <%= link_to "新規登録", new_user_registration_path, class: 'post' %> 
<% end %>
```


# 終わりに（nameカラムを加えるには）
ここまでででrails serverを立ち上げ"http://localhost:3000/home/index" へアクセスすると作成されたフォームが確認できます。
Railsチュートリアルと比較すると現在ではUserモデルにnameカラムがないので追加する場合は以下の手順で。
・usersテーブルにnameカラムを追加
<details><summary>ターミナル</summary><div>

```zsh
$ rails g migration add_name_to_users name:string
$ rails db:migrate 
```
</div></details>
・新規登録画面に名前の入力フォームを追加
<details><summary>app/views/devise/registrations/new.html.erb</summary><div>

```erb:app/views/devise/registrations/new.html.erb
<h2>Sign up</h2>

<%= 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 :name %><br />
    <%= f.text_field :name %>
  </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 "Sign up" %>
  </div>
<% end %>

<%= render "devise/shared/links" %>
```
</div></details>
・ストロングパラメーター（マスアサインメント脆弱性への対策）
<details><summary>login_app/app/controllers/application_controller</summary><div>

```rb:login_app/app/controllers/application_controller
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

  # ここから追加します
  before_action :configure_permitted_parameters, if: :devise_controller?

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
  end
  # ここまで追加します
end
```
</div></details>
・新規登録画面に名前の入力フォームを追加
<details><summary>app/views/devise/registrations/new.html.erb</summary><div>

```erb:views/comments/index.html.erb
<h1>App Name</h1>

<% if user_signed_in? %> 
  <h2> 現在 <%= current_user.name %> さんがログインしています </h2> 
  <h4> メールアドレス： <%= current_user.email %> </h4>
  <%= link_to "ログアウト", destroy_user_session_path, method: :delete %> 
<% else %>
  <h2> 現在ログインしていません </h2>
  <%= link_to "ログイン", new_user_session_path, class: 'post' %>
  <%= link_to "新規登録", new_user_registration_path, class: 'post' %> 
<% end %>
```
</div></details>

少しでもどなたかの参考になれば幸いです。
Twitterで日々の学習をアウトプットしています。
未熟者ですが、ご興味あれば覗いてやってください。[→Twitter](https://twitter.com/ishii_engneer)