複数権限でのDevise使用

# はじめに

2024年2月からプログラミン学習を始めたばかりなので間違ったことを記載している可能性もあります。間違っている部分やアドバイスがありましたらご指摘していただけたら幸いです。

また、今回の記事は以下の記事の続きになっています。
https://qiita.com/sslevel5/items/abe275d948f55f9f34d3

## 前提
ruby
rails

上記の環境で作成します。

## 実装

管理者側と顧客側でログイン等の認証機能を分けるので、管理者と顧客側を分けて'Devise'の導入を行います。

↓'Devise'の導入の参考記事

https://zenn.dev/h_hana/articles/6dac69de42cbfe

### 'Devise'のインストール

Gemfileの最後の行に下記を追記します。
```
gem 'devise'
```
gemをインストールします。
```
bundle install
```
deviceをインストールします。
```
rails g devise:install
```

### 管理者・顧客のモデルを作成

無事、deviceのインストールが完了したので管理者と顧客の情報を保存するテーブルを作成します。

```
【顧客用】
$ rails g devise Customer

【管理者用】
$ rails g devise Admin
```

前回の記事で先にAdminのnamespaceを作成しているので、ここで下記のエラーが出ます。
```
The name 'Admin' is either already used 
in your application or reserved by Ruby on Rails. 
Please choose an alternative or use --skip-collision-check or 
--force to skip this check and run this generator again.
```

Adminの名前のフォルダが既に存在していると言われています。
しかし、Adminという名前で作成したいので下記の手段で実行します。
```
#先ほど作りかけたAdminものを一旦を削除
rails d devise Admin

# Adminモデルの生成（既存のファイルがあっても上書き）
rails d devise Admin -f

# Deviseの設定を追加
rails g devise Admin
```

### カラムの追加

管理者側はメールアドレスとパスワードのみでログインする予定なので編集はしません。
顧客側はメールアドレスとパスワードに加え名前と今後退会処理に使用するステータスを登録してもらう予定なので以下のカラムを追加します。

```〇〇〇〇〇〇〇〇_devise_create_customers.rb
#ニックネーム
t.string :name, null: false

#会員ステータス
t.boolean :is_active, null: false, default: true
```

保存が完了しましたら、データベースへマイグレーションします。
```
rails db:migrate
```

### コントローラーの作成

今回は、管理者側にはログイン機能のみ、顧客側には退会処理を追加したいのでDevise 用のコントローラーを作成します。

```
【管理者用】
$ rails g devise:controllers admin

【顧客用】
$ rails g devise:controllers public
```

### Viewの作成

管理者用と顧客用のビューファイルを作成します。

```
【管理者用】
$ rails g devise:views admins

【顧客用】
$ rails g devise:views publics
```

この時、作成されたadminsフォルダとpublicsフォルダ内のファイルをすでに作成しているadminフォルダとpublicフォルダへ移動し、adminsフォルダとpublicsフォルは削除します。

- admins → admin
- publics → public

フォルダの移動に伴いフォルダ名が変更されたことになりエラーが発生するので、以下の修正を行います。

```app/views/admin/sessions/new.html.erb
【変更前】
<%= render "admins/shared/links" %>

【変更後】
<%= render "admin/shared/links" %>
```

```app/views/public/registrations/new.html.erb, app/views/public/sessions/new.html.erb
【変更前】
<%= render "publics/shared/links" %>

【変更後】
<%= render "public/shared/links" %>
```

```app/views/public/registrations/new.html.erb
【修正前】
<%= render "publics/shared/error_messages", resource: resource %>

【修正後】
<%= render "public/shared/error_messages", resource: resource %>
```

また、新規登録時に名前の登録を必須とするため下記の編集をします。
```app/views/public/registrations/new.html.erb
<h2>Sign up</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= render "public/shared/error_messages", resource: resource %>
--------------------------------ここから追加--------------------------------
  <div class="field">
    <%= f.label :name %><br>
    <%= f.text_field :name %>
  </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>

```

### ルーティングの編集

ルーティングは、現状おそらく以下のものが追加されていると思います。
```config/routes.rb
devise_for :customers
devise_for :admins
```
しかし、このままだと先ほど作成したコントローラーを編集してもその処理を実行することができません。
また、管理者側の新規登録機能やパスワードの再設定機能は今回のアプリには使用しないため、下記のように編集します。
```config/routes.rb
devise_for :customers #削除
devise_for :admins #削除

# 顧客用
devise_for :customers,skip: [:passwords], controllers: {
  registrations: "public/registrations",
  sessions: 'public/sessions'
}

# 管理者用
devise_for :admin, skip: [:registrations, :passwords] ,controllers: {
  sessions: "admin/sessions"
}
```

<details>
<summary>編集の詳細</summary>

上記のコードは、Deviseを使用して2つの異なるユーザータイプ（顧客と管理者）の認証を設定しています。
1.顧客（customers）用のDevise設定の定義
```config/routes.rb
devise_for :customers,skip: [:passwords], controllers: {
  registrations: "public/registrations",
  sessions: 'public/sessions'
}
```

- skip: [:passwords]は、パスワード関連の機能（パスワードの変更やリセットなど）を無効にしています。
- controllersオプションは、指定されたコントローラーが使用されることを示しています。顧客の登録とセッションにはそれぞれ独自のコントローラーが使用されます。

2.管理者（admin）用のDevise設定の定義
```config/routes.rb
devise_for :admin, skip: [:registrations, :passwords] ,controllers: {
  sessions: "admin/sessions"
}
```

- skip: [:registrations, :passwords]は、管理者用の登録（registrations）とパスワード関連の機能を無効にしています。
- controllersオプションは、管理者のセッションに独自のコントローラーが使用されることを示しています。

つまり、このコードは、顧客と管理者それぞれに対して異なるDevise設定を提供し、それぞれのユーザータイプに適した認証とセッション管理を行うようにしています。
</details>


ルーティングを確認すると余分なルーティングが削除されているのが確認できます。

![スクリーンショット (393).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3794246/2b89a40c-c4ff-3f91-a0b9-cd34e77ba41b.png)

次にルーティングを削除したことによってビューファイルに残っているdeviceの機能がエラーを引き起こすので下記の編集を行います。

```app/views/public/shared/_links.html.erb
<%- if controller_name != 'sessions' %>
  <%= link_to "Log in", new_session_path(resource_name) %>

<% end %>

<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
  <%= link_to "Sign up", new_registration_path(resource_name) %>

<% end %>

--------------------------------ここから削除--------------------------------
<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
  <%= link_to "Forgot your password?", new_password_path(resource_name) %>

<% end %>

<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
  <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>

<% end %>

<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
  <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>

<% end %>

<%- if devise_mapping.omniauthable? %>
  <%- resource_class.omniauth_providers.each do |provider| %>
    <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), method: :post %>

  <% end %>
<% end %>
--------------------------------ここまで削除--------------------------------
```
```app/views/admin/shared/_links.html.erb
<%- if controller_name != 'sessions' %>
  <%= link_to "Log in", new_session_path(resource_name) %>

<% end %>

--------------------------------ここから削除--------------------------------
<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
  <%= link_to "Sign up", new_registration_path(resource_name) %>

<% end %>

<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
  <%= link_to "Forgot your password?", new_password_path(resource_name) %>

<% end %>

<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
  <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>

<% end %>

<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
  <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>

<% end %>

<%- if devise_mapping.omniauthable? %>
  <%- resource_class.omniauth_providers.each do |provider| %>
    <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), method: :post %>

  <% end %>
<% end %>
--------------------------------ここまで削除--------------------------------
```

実装が完了したので'rails s'をしてそれぞれのURLへ確認しに行きます。

/customers/sign_up
![スクリーンショット (395).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3794246/096f0178-b45b-632d-eb0d-45a2e34453e9.png)

/customers/sign_in
![スクリーンショット (396).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3794246/8604932e-b6a8-f404-f11d-cfbf9a543d9b.png)

/admin/sign_in
![スクリーンショット (397).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3794246/233dd44f-cacc-a34a-150e-a2bdf6e52e5e.png)


## 最後に
これで管理者側と顧客側それぞれのログイン機能等が実装できました。
次回は管理者側の機能を実装していきたいと思います。