LoginSignup
13
16

More than 1 year has passed since last update.

【Rails】多対多のアソシエーションを用いてコミュニティ加入の申請/承認機能の実装を行う

Last updated at Posted at 2020-07-10

実施したいこと

申請・承認の仕組みを実装する

具体的には?

「ユーザ(小西)が、とあるコミュニティに加入したいと思いますが、
加入にはコミュニティの管理者(大西)の承認が必要になる」といったケースでの実装例です。

ざっくりイメージ

キャプチャ.PNG

処理フロー概要

  1. 該当コミュニティ画面にて、ユーザ(小西)が「申請」ボタンを押す。
  2. 該当コミュニティの申請待ちリストにユーザ(小西)が追加される。管理者が、該当コミュニティの申請待ち一覧画面を見て、申請者を承認するかどうか判断。
  3. 申請を承認する場合は、承認ボタンを押す。申請ユーザがコミュニティに加入し、申請待ちリストからは消去される。
  4. 申請を却下する場合は、否認ボタンを押す。申請待ちリストから消去される。

環境

Ruby 2.6.5
Rails 5.2.4.2
mysql 5.7

現在のアプリケーション機能の構成

ユーザが色々なコミュニティに所属できるアプリケーションを作成しています。

  • userモデル:アプリの利用ユーザ情報を保持
  • communityモデル:コミュニティ情報を保持
  • belongingモデル:ユーザが所属するコミュニティ情報を保持。ユーザは複数のコミュニティに所属できます。
    ※userとcommunityはbelongingモデルを介して多対多の関係となっています。

以下ソースは関連部分のみ抜粋

user.rb
class User < ApplicationRecord
  has_many :belongings, dependent: :destroy
  has_many :applies, dependent: :destroy
  has_many :communities, through: :belongings # ユーザが所属しているコミュニティ

end

community.rb
class Community < ApplicationRecord
    has_many :belongings, dependent: :destroy
    has_many :applies, dependent: :destroy
    has_many :users, through: :belongings # コミュニティに所属しているユーザ

    # ユーザがコミュニティに所属していればtrueを返す
    def user_belonging?(user)
      users.include?(user)
    end

end

belonging.rb
class Belonging < ApplicationRecord
    belongs_to :user
    belongs_to :community

    validates :user_id, presence: true
    validates :community_id, presence: true
    validates  :user_id, uniqueness: { scope: :community_id }
    validates  :community_id, uniqueness: { scope: :user_id }

end

Applyモデルを追加し、申請状況を保持させる。

Applyモデルのルーティングやコントローラ、ビューファイルを追加します。

apply.rb
class Apply < ApplicationRecord
    belongs_to :user
    belongs_to :community
    validates :user_id, presence: true
    validates :community_id, presence: true
    validates  :user_id, uniqueness: { scope: :community_id}
    validates  :community_id, uniqueness: { scope: :user_id}
end

ルーティング(抜粋)

routes.rb
Rails.application.routes.draw do
  root 'home#index'

  resources :communities do
    resources :applies, only: %i[index create destroy]
    resources :belongings, only: %i[index create destroy]
  end

  resources :users, only: [:index, :show]

end

1.該当コミュニティ画面にて、ユーザ(小西)が「申請」ボタンを押す。

  • 申請ボタン(コミュニティ詳細画面に配置)
    (views/communities/show.html.erb)
show.html.erb
<!-- ログインユーザが当該コミュニティに所属している場合 -->
<% if @community.user_belonging?(current_user) %>
    <%= link_to '退会する', community_belonging_path(@community, @belonging), method: :delete, data:{ confirm: "コミュニティ「#{@community.name}」を退会します。よろしいですか?" } ,class:"mini-red-link-btn font-bold text-line-none" %>
<!-- 当該コミュニティには所属していないが、ログインはしている場合 -->
<% elsif current_user %>
    <% if @apply %>
        <%= link_to '申請取消', community_apply_path(@community, @apply), method: :delete, class: "mini-red-link-btn font-bold text-line-none" %>
    <% else %>
        <%= link_to '加入申請', community_applies_path(@community), method: :post, class: "mini-green-link-btn font-bold text-line-none" %>
    <% end %>

申請ボタンは、以下の条件で表示させます。

  1. コミュニティにすでに加入している場合:退会するボタンが表示
  2. コミュニティにまだ加入していない場合:申請ボタンが表示
  3. すでに加入申請済の場合:申請取消ボタンを表示
  • 申請ボタン押下後(createアクションが発生)
applies_controller.rb
class AppliesController < ApplicationController

  def create
    current_user.applies.create(community_id: apply_params[:community_id])
    redirect_to community_url(apply_params[:community_id]), notice: "加入申請しました"
  end

  private

    def apply_params
      params.permit(:community_id)
    end

end

Ajaxは使わず、同画面へのリダイレクトが発生するようにします。
(Ajaxを使ってもよさそう)

これで、applyモデルに申請情報が追加されました。

2.該当コミュニティの申請待ちリストにユーザ(小西)が追加される。管理者が、該当コミュニティの申請待ち一覧画面を見て、申請者を承認するかどうか判断。

次に、申請待ちリストの確認です。
管理者(大西)が該当コミュニティ画面から「申請待ち一覧画面」を開きます。

  • 申請待ち画面へのリンク(コミュニティ詳細画面)
    (views/communities/show.html.erb)
show.html.erb
<% if user_admin_flg(current_user,@community) == 1 %>
    <%= link_to "承認待ち一覧", community_applies_path(@community), class:"btn btn-primary" %>
<% end %>

リンクをクリックすると、申請待ち一覧画面が開きます。

  • 申請待ち一覧画面
    (views/applies/index.html.erb)
index.html.erb
<div class="container applicant-wrapper">
    <h3>承認待ちユーザ一覧</h3>
    <div class="row">
        <% @applies.each do |app| %>
        <div class="col-6">
            <% if app.user.image.attached? %>
                <%= link_to app.user.image, user_path(app.user), class:"user-icon" %>
            <% else %>
                <%= link_to user_path(app.user) do %>
                    <%= image_tag ("no_image.png"), class:"user-icon" %>
                <% end %>
            <% end %>
            &nbsp<%= app.user.username %><br>
            <%= link_to "承認", community_belongings_path(app.community, user_id: app.user.id, apply_id: app.id), method: :post, class:"mini-green-link-btn font-bold text-line-none" %>
            <%= link_to "却下", community_apply_path(app.community, app), method: :delete, class:"mini-red-link-btn font-bold text-line-none" %>
            <br>
        </div>
        <% end %>
    </div>
</div>

applyモデルから、該当コミュニティへの申請情報を一覧表示させています。
同画面において、承認ボタン、却下ボタンも配置しています。

3.申請を承認する場合は、承認ボタンを押す。申請ユーザがコミュニティに加入し、申請待ちリストからは消去される。

承認ボタンを押すと、以下の処理が実行されます。

  1. belongingsコントローラのcreateアクションが実行される。
  2. 申請待ち一覧から削除するため、Applyモデルの該当の申請情報が削除される。
  3. 申請待ち一覧画面へリダイレクト
  • belongingsコントローラ
belongings_controller.rb
class BelongingsController < ApplicationController

    def create
        @belonging = Belonging.create(community_id: belonging_params[:community_id], user_id: belonging_params[:user_id])
        Apply.find(belonging_params[:apply_id]).destroy!
        redirect_to community_applies_url(@belonging.community), notice:"「#{@belonging.user.username}」が、コミュニティ:#{@belonging.community.name}へ加入しました。"
    end

    private

        def belonging_params
            params.permit(:community_id, :user_id, :apply_id)
        end

end

4.申請を却下する場合は、否認ボタンを押す。申請待ちリストから消去される。

却下ボタンを押すと、以下の処理が実行されます。

  1. 申請待ち一覧から削除するため、appliesコントローラのdestroyアクションが実行され、Applyモデルの該当の申請情報が削除される。
  2. 申請待ち一覧画面へリダイレクト
  • appliesコントローラ
applies_controller.rb
class AppliesController < ApplicationController

  def destroy
    @apply = Apply.find(params[:id])
    @apply.destroy!
    @comminity = Community.find(params[:community_id])
    redirect_to community_url(@comminity), notice: "加入申請を取り消しました"
  end

end

以上になります。

文中でappliesコントローラとbelongingsコントローラはアクション毎に分けて紹介していますが、最後に全部まとめて載せておきます。

applies_controller.rb
class AppliesController < ApplicationController

  def create
    current_user.applies.create(community_id: apply_params[:community_id])
    redirect_to community_url(apply_params[:community_id]), notice: "加入申請しました"
  end

  def destroy
    @apply = Apply.find(params[:id])
    @apply.destroy!
    @comminity = Community.find(params[:community_id])
    redirect_to community_url(@comminity), notice: "加入申請を取り消しました"
  end

  def index
    @applies = Apply.where(community_id: params[:community_id])
  end

  private

    def apply_params
      params.permit(:community_id)
    end

end
belongings_controller.rb
class BelongingsController < ApplicationController

    def create
        @belonging = Belonging.create(community_id: belonging_params[:community_id], user_id: belonging_params[:user_id])
        Apply.find(belonging_params[:apply_id]).destroy!
        redirect_to community_applies_url(@belonging.community), notice:"「#{@belonging.user.username}」が、コミュニティ:#{@belonging.community.name}へ加入しました。"
    end

    def destroy
        @belonging = Belonging.find(params[:id])
        @belonging.destroy!
        @comminity = Community.find(params[:community_id])
        redirect_to community_url(@comminity), notice: "コミュニティ「#{@comminity.name}」を退会しました。"    
    end

    private

        def belonging_params
            params.permit(:community_id, :user_id, :apply_id)
        end

end
communities_controller.rb
class CommunitiesController < ApplicationController
  skip_before_action :authenticate_user!, only: %i[index show]
  before_action :validate_community, only: %i[edit update destroy]

  def index
    # 検索オブジェクト
    @search = Community.ransack(params[:q])
    # 検索結果
    @communities = @search.result
    @communities = @communities.page(params[:page]).per(12)
  end

  def show
    @community = Community.find(params[:id])
    @belongings = Belonging.where(community_id: @community.id)
    @code = Code.all
    # 検索オブジェクト
    @search = Post.ransack(params[:q])
    # 検索結果
    @posts = @search.result
    @posts = @posts.where(community_id: @community.id)
    @posts = @posts.page(params[:page]).per(9)
    return unless current_user

    @belonging = Belonging.find_by(community_id: @community.id, user_id: current_user.id)
    @apply = Apply.find_by(community_id: @community.id, user_id: current_user.id)
  end

  def new
    @community = Community.new
    @code = Code.all
  end

  def edit
    @community = Community.find(params[:id])
    @code = Code.all
  end

  def create
    @community = current_user.communities.build(community_params)

    if @community.save
      Belonging.create!(community_id: @community.id, user_id: current_user.id, admin_flg: '1')
      redirect_to communities_url, notice: "グループ「#{@community.name}」を登録しました。"
    else
      render :new
    end
  end

  def update
    community = current_user.communities.find(params[:id])
    community.update!(community_params)
    redirect_to community_url(community), notice: "グループ「#{community.name}」を更新しました。"
  end

  def destroy
    community = Community.find(params[:id])
    community.destroy
    redirect_to communities_url, notice: "グループ「#{community.name}」を削除しました。"
  end

  private

  def community_params
    params.require(:community).permit(:name, :create_user_id, :publish_flg, :description, :image)
  end

  def validate_community
    @community = Community.find(params[:id])
    redirect_to community_path(@community), alert: "作成ユーザのみ変更操作ができます" if @community.create_user_id != current_user.id
  end
end

初心者が自分の頭でロジック・コードを考えて実装したので、
間違いや、もっと適した実装方法があるかもしれません。
ぜひご指摘いただけますと幸いです。

この実装で、多対多の関係のことがより理解できたなと思います。

13
16
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
16