deviseを使用した複数権限

# 初学者の備忘録
初学者の備忘録です。今回はdeviseの複数権限編


## 複数権限とは？
deviseは認証機能を簡単に作れるGemです。簡単に説明するとログイン関連を簡単に作れるということです。
ですが普通に作成するとひとつの認証機能しかできません。ログインする対象が一つ、例えばユーザーのみとかならそれでもいいのですが、EC２サイトのように管理者側と顧客側と対象が複数存在する場合少し工夫しなければなりません。今回はその複数権限でのdeviseの実装方法になります。


## 実装
【１】　モデル作成
モデルを作成します。複数権限の場合コマンドでnamespaceを使用するのですが、deviseでは使用できません。deviseの独自のコマンドを使用する必要があります。
管理者：admin
ユーザー：user
管理者用、ユーザー用モデル作成のための命名規則に則ってコマンドをターミナルに入力します。
```ruby:ターミナル
【顧客用】
$ rails g devise User

【管理者用】
$ rails g devise Admin
```
必要があればマイグレーションにカラムを追加してください。
カラムを追加できたらマイグレーションファイルをUPします。
```ruby:ターミナル
$ rails db:migrate
```
これでモデルが作成できました。


【２】　コントローラー作成
ここからが注意です。
モデルに関しては複数権限ありなしに関係なく先ほどのコマンドでモデルを作成できます。
ですがコントローラーはどの権限を持たせるかが重要になってきます。Devise用のコントローラーを作成することで使用する機能のみをルーティングを行うことや、ログイン前に特定のメソッドを実行することができる before_action などを使用することができます。
例えば管理者側はログインできればよくてサインアップ機能はいらないが、ユーザーは両方必要。管理者側は退会機能はいらないけどユーザーは退会機能が必要など、分けることができます。それ以外にも遷移先を判ることも可能になります。

説明は以上で実際にコントローラーを作成していきます。
管理者用、ユーザー用コントローラー作成のために、命名規則に則ってコマンドをターミナルに入力します。
```ruby:ターミナル
【顧客用】
$ rails g devise:controllers user

【管理者用】
$ rails g devise:controllers admin
```
コントローラーの作成が完了しました。ルーティングも変更しなければいけませんは後ほど。
<details>
  <summary>補足</summary>
  
    ec2-user:~/environment/Qiita (devise) $ rails g devise:controllers admin
    # 作成されたファイルを表示しています
    Running via Spring preloader in process 18784
          create  app/controllers/admin/confirmations_controller.rb
          create  app/controllers/admin/passwords_controller.rb
          create  app/controllers/admin/registrations_controller.rb
          create  app/controllers/admin/sessions_controller.rb
          create  app/controllers/admin/unlocks_controller.rb
          create  app/controllers/admin/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
    
    ===============================================================================
    
</details>


【３】　ビュー作成
ビューの作成です。
同じく管理者用、ユーザー用のビューフォルダを作成するために、命名規則に則ってコマンドをターミナルに入力します。
```ruby:ターミナル
【顧客用】
$ rails g devise:views users

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


【４】　ビュー編集
作成されたフォルダ名を変更します。
作成されたフォルダ名は複数形になっているので単数型に修正します。（Railsは複数形、単数型で決まりがあるので余程の理由がない限り単数型に修正しましょう）
![スクリーンショット 2023-09-19 15.54.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3503032/0254683f-0722-9369-16b5-2acc5532fc3f.png)
フォルダ名を変更したので、一緒に作成されたビューファイルの記述も複数形から単数型に変更します。
<管理者側>
ログインページの修正
```diff_ruby:app/views/admin/sessions/new.html.erb
【変更前】
-<%= render "admins/shared/links" %>
【変更後】
+<%= render "admin/shared/links" %>
```
<ユーザー側>
サインアップページ編集
```diff_ruby:app/views/user/registrations/new.html.erb
【変更前】
-<%= render "users/shared/links" %>
【変更後】
+<%= render "user/shared/links" %>
```
```diff_ruby:app/views/user/registrations/new.html.erb
【修正前】
-<%= render "users/shared/error_messages", resource: resource %>
【修正後】
+<%= render "user/shared/error_messages", resource: resource %>
```
ログインページ編集
```diff_ruby:app/views/user/sessions/new.html.erb
【変更前】
-<%= render "users/shared/links" %>
【変更後】
+<%= render "user/shared/links" %>
```


【５】　ルーティングの編集
現状のルーティングは、Devise 用のモデル作成を作成したタイミングでルーティングが設定されるので
次のように設定がされているはずです。
```ruby:config/routes.rb
devise_for :users
devise_for :admins
```
この記述だと、先程作成したコントローラーの記述を変更してもその処理を行うことができません。
いらないものはskipさせないといけません。
```ruby:config/routes.rb
# 顧客用
# URL /user/sign_in ...
devise_for :users,skip: [:passwords], controllers: {
  registrations: "user/registrations",
  sessions: 'user/sessions'
}

# 管理者用
# URL /admin/sign_in ...
devise_for :admin, skip: [:registrations, :passwords] ,controllers: {
  sessions: "admin/sessions"
}
```
<details>
  <summary>補足</summary>
    簡単な説明になっていましますが、<br>
    devaiseの補助の一つでURLが自動的にsign_inといった形にしてくれます。
    skipはWebページに表示されて、使用しないフォルダをskipさせるコードです。不安なら使用しないもの以外は全てskipの中に記述しても問題はありません。なくてもあってもいい程度。<br>
    現状ここまでの認識で問題ないのでここまでにしておきます。
</details>



【６】 リンク削除
必要ないリンクを削除していきます。コメントアウトでも大丈夫です。
ユーザー側は、サインアップ・サインインの機能のみが存在しているのでそれ以外の記述を削除します。
```diff_ruby: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 %>
--------------------------------ここまで削除--------------------------------
```

管理者側はサインイン機能のみ残します。
```diff_ruby: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 %>
--------------------------------ここまで削除--------------------------------
```

以上で実装終了です。お疲れ様でした。


## おまけ
これからの機能もユーザー側、管理者側でコントローラーを分ける必要があります。
管理者側は admin フォルダ・ユーザー側はuserフォルダ内に存在した方が使い勝手が良いためそれらのフォルダにコントローラーに作成する方法を紹介します。
```rubyターミナル
【ユーザーフォルダに作成】
$ rails g controller user/コントローラー名
【管理者フォルダに作成】
$ rails g controller admin/コントローラー名
```