Rails Group参加に承認機能をつける 

# railsのグループ機能でグループ参加承認機能を実装したので記事にまとめてみました。

こんにちは！
Rails初学者の私ですが、先日グループ参加の承認機能を実装しましたので
その実装方法を記事にしてみました。

## 完成系のイメージ
①ゲストユーザーで加入申請をします。
<img width="400" alt="スクリーンショット 2023-06-13 21.56.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3398256/50143ebb-fb45-8d2f-1c66-5f8f49ffd9fc.png">
②加入申請後はボタンが変わり、申請取消しとなっています。<br>こちらを押すことで申請がキャンセルされ、加入申請ボタンに戻ります。
<img width="400" alt="スクリーンショット 2023-06-13 21.57.51.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3398256/0546b082-dc95-7ba8-7079-51403438cf63.png">
③グループオーナーでログインし直して、承認待ち一覧から加入許可ボタンを押せば、グループメンバーに加わることが出来ます。
<img width="400" alt="スクリーンショット 2023-06-13 21.58.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3398256/1621aeb3-9816-6f2b-1cb5-b1393f333836.png">


### 前提
- グループ機能が実装済み
- Deviceでuserログインができる

### リレーションの確認
<img width="700" alt="スクリーンショット 2023-06-13 22.20.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3398256/5499d2e5-2ada-172f-0de7-7d3c17836c29.png">


基本的なグループ関連のリレーションの考えた方は
以前に記事にしている下記と同様になります。
https://qiita.com/fumiya1800/items/ec1b2f4d94c74ef041c7

今回はpermitテーブルを作成して
こちらのテーブルにグループ参加希望者を紐づけていきます。

　permitに登録されている情報をもとに
　group_userを作成することでグループにメンバーを加えていく形になります。

## モデルの作成
```
$ rails g model Permit user:references group:references
```
```db/migrate/20230608135756_create_permits.rb
class CreatePermits < ActiveRecord::Migration[6.1]
  def change
    create_table :permits do |t|
      t.references :user, null: false, foreign_key: true
      t.references :group, null: false, foreign_key: true

      t.timestamps
    end
  end
end
```
忘れずに下記コマンドを行う
```
$ rails db:migrate
```

## モデル編集
```app/models/user.rb
class User < ApplicationRecord
  has_many   :group_users,      dependent: :destroy
  has_many   :permits,          dependent: :destroy
  has_many   :groups,           through: :group_users
end
```
```app/models/group.rb
class Group < ApplicationRecord 
  has_many :group_users, dependent: :destroy
  has_many :permits,     dependent: :destroy
  has_many :users, through: :group_users
end
```
```app/models/permit.rb
class Permit < ApplicationRecord
  belongs_to :user
  belongs_to :group
end
```

## ルーティング編集
```config/routes.rb

scope module: :public do
  resources :groups, except: [:new] do
    resource :permits, only: [:create, :destroy] #追加
    resource :group_users, only: [:create, :destroy]
  end
end
get "groups/:id/permits" => "groups#permits", as: :permits #追加

```

groupsにネストする形でpermitsを追加します。
今回resourceにて記述していますが
resourceメソッドは、コントローラの７つのアクションに対して、indexとid付きのパスが生成されないというものです。
今回は一人のユーザーは一つのグループに対して加入申請を送るという一回しかcreateが出来ないという仕様であるため、ユーザーidとグループidのみ分かればどのpermitを削除すればいいのかが特定できるため、permitのidは必要がなくresouceを使用しております。

また、groupsコントローラーで加入希望者の一覧を表示するpermitsアクションをルーティングに加えております。

## コントローラーの編集
### permitsコントローラを作成する

```
$ rails g controller public/permits
```
```app/controllers/public/permits_controller.rb
class Public::PermitsController < ApplicationController
  before_action :authenticate_user!

  def create
    @group = Group.find(params[:group_id])
    permit = current_user.permits.new(group_id: params[:group_id])
    permit.save
    redirect_to request.referer, notice: "グループへ参加申請をしました"
  end

  def destroy
    permit = current_user.permits.find_by(group_id: params[:group_id])
    permit.destroy
    redirect_to request.referer, alert: "グループへの参加申請を取消しました"
  end

end
```

createでは自分のuser_idとparams[gropu_id]をpermitに保存しています。
destroyではpermitsテーブルから自分のuser_idとparams[gropu_id]が登録されいるデータを探して、
そのデータを削除することでそのグループへの参加申請を取り消ししています。

次にgroup_usersコントローラーも編集します。
```app/controllers/public/group_users_controller.rb
class Public::GroupUsersController < ApplicationController
  before_action :authenticate_user!

  def create
    @group = Group.find(params[:group_id])
    @permit = Permit.find(params[:permit_id])
    @group_user = GroupUser.create(user_id: @permit.user_id, group_id: params[:group_id])
    @permit.destroy #参加希望者リストから削除する
    redirect_to request.referer
  end

end
```
まずは＠permitでグループに参加させたいユーザーの情報を保存します。
createで@permitに紐づいている情報をもとにgroup_userをコピーするような形で作成しております。

group_user作成後は@permitは不要となるので、削除を行っています。


次にgroupsコントローラーを編集します。
```app/controllers/public/groups_controller.rb
class Public::GroupsController < ApplicationController
  before_action :authenticate_user!
  before_action :ensure_correct_user, only: [:edit, :update, :destroy, :permits]

  def permits
    @group = Group.find(params[:id])
    @permits = @group.permits.page(params[:page])
  end

    private

  def group_params
    params.require(:group).permit(:name, :introduction, :group_image)
  end

  # params[:id]を持つ@groupのowner_idカラムのデータと自分のユーザーIDが一緒かどうかを確かめる。
  # 違う場合はグループ詳細ページを再表示させる。（オーナー以外は編集、削除、加入希望者ページの遷移はできない）before_actionで使用する。
  def ensure_correct_user
    @group = Group.find(params[:id])
    unless @group.owner_id == current_user.id
      redirect_to group_path(@group), alert: "グループオーナーのみ編集が可能です"
    end
  end

end
```

グループの参加希望者一覧を表示するpermitsアクションを追加しております。
また、permitsアクションはensure_correct_userメソッドでグループオーナーのみが使えるように設定しています。

その他のアクションは今回は割愛しております。気になる方は上記にもリンクを貼っているグループ作成の記事をご参照ください。

## viewの編集

```app/views/public/groups/show.html.erb

<% if @group.group_users.exists?(user_id: current_user.id) %>
　　　　<%= link_to 'グループ退出', group_group_users_path(@group), method: :delete, class: "btn btn-sm btn-danger", data: { confirm: "本当にグループを退出しますか？" } %>
<% elsif @group.permits.exists?(user_id: current_user.id) %>
　　　　<%= link_to '申請取消', group_permits_path(@group), method: :delete, class: "btn btn-sm btn-danger" %>
<% else %>
　　　　<%= link_to '加入申請', group_permits_path(@group), method: :post, class: "btn btn-sm btn-success" %＞
<% end %>


<% if @group.owner_id == current_user.id %>
    <div class="dropdown">
    <button class="btn btn-sm btn-secondary dropdown-toggle"
            type="button" id="dropdownMenu1" data-toggle="dropdown"
            aria-haspopup="true" aria-expanded="false">
      オーナー専用
    </button>
    <div class="dropdown-menu" aria-labelledby="dropdownMenu1">
      <%= link_to "承認待ち一覧", permits_path(@group),  class: "dropdown-item text-secondary bg-transparent" %>  ⇦編集箇所
      <%= link_to 'グループ編集', edit_group_path(@group),  class: "dropdown-item text-success bg-transparent" %>
      <%= link_to 'グループ削除', group_path(@group), method: :delete,  class: "dropdown-item text-danger bg-transparent", data: { confirm: "本当に削除しますか？" } %>
    </div>
  </div>
<% end %>

```

グループ退出は今回の記事では割愛している部分となります。
グループに参加していない場合は加入申請ボタンが表示され、
グループに加入申請を送っている場合は申請取消しボタンが表示される記述となっています。

加入申請と申請取消しを押すことでpermitsのcreateとdestroyを行っています。

下の<% if @group.owner_id == current_user.id %>からの記述では
新たに承認待ち一覧ボタンを追加しています。
ここから、groupuコントローラーの#permitsアクションを呼び出してグループ参加希望者一覧を表示します。
その他の記述部分は今回の記事では割愛させていただきます。

次が最後となります。
```app/views/public/groups/permits.html.erb
<div class="container">
  <h2 class="text-center"><%= @group.name %></h2>
  <h4 class="text-center">承認待ち一覧</h4>

  <div class="row mt-5">
    <div class="col-11 col-md-12 mx-auto">
    <% if @permits.present? %>
      <div class="table-responsive">
        <table class="table table-hover text-nowrap bg-light">
          <thead class="thead-dark">
            <tr>
              <th>名前</th>
              <th>部署</th>
              <th>役職</th>
              <th></th>
            </tr>
          </thead>
          <tbody>
            <% @permits.each do |permit| %>
              <tr>
                <td><%= link_to permit.user.name, public_user_path(permit.user.id), class: "text-dark" %></td>
                <td>
                  <% if permit.user.department_id.present? %>
                    <%= permit.user.department.name %>
                  <% end %>
                </td>
                <td><%= permit.user.position %></td>
                <td class="text-right">
                  <%= link_to '加入許可', group_group_users_path(@group, permit_id: permit.id), method: :post, class: "btn btn-sm btn-success" %>
                </td>
              </tr>
            <% end %>
          </tbody>
        </table>
      </div>
    <% else %>
      <P class="text-center">承認待ちユーザーはいません。</P>
    <% end %>
    </div>
    <div class="mx-auto mt-5"><%= paginate @permits %></div>
  </div>

  <div class="row mt-5">
    <div class="col-md-9 mx-auto">
    <p class="text-center"><%= link_to "戻る", group_path(@group), class: 'text-dark font-weight-bold' %></p>
    </div>
  </div>

</div>
```

groupsコントローラで@groupに紐ついたpermitを＠permitsで定義しているので
each分で一つずつ取り出しています。
今回重要となるのが下記の部分になります。
<%= link_to '加入許可', group_group_users_path(@group, permit_id: permit.id), method: :post, class: "btn btn-sm btn-success" %>
ここでgroup_usersコントローラーのcreateアクションを呼び出しているのですが、
permit_id: permit.idという記述でデータをコントローラーに送っており、これによって該当するpermitのデータをコントローラー内で@permitに格納しています。
そうすることによって、該当するするユーザーのみをグループに参加させて、参加したユーザーは加入希望一覧から削除することが出来ます。


以上で完成になります。
今回の記事は以前に記事にしたグループ作成の記事から追加の部分のみを記載している為、
分からない箇所があった場合はぜひ、下記の記事も参考にしてみてください。
https://qiita.com/fumiya1800/items/ec1b2f4d94c74ef041c7

## 最後に
最後までご覧いただきありがとうございます。
初学者なので、間違っていることや、分かりづらい箇所もあるかと思います。
何かお気づきがあれば遠慮なくご指摘頂けると幸いです。

ここまで長くなりましたが
最後までお付き合いいただきましてありがとうございました！！