はじめに
この記事は、初学者がgem「devise_invitable」のカスタマイズに挑戦してみた記事です。チーム(グループ)があるアプリで、新規ユーザー/既存ユーザーのどちらにも招待メールを送り、招待されたユーザーがパスワードを登録した時にチームに所属できるようにしています。
*この記事ではdevise_invitableの基本的な実装方法は説明していません。実装済みを前提としています。
経緯(読み飛ばしても🙆♀️)
Dive Into Codeというプログラミングスクールの卒業課題で「複数人でタスク管理を行うアプリ」を作成しました。簡単なアプリ紹介としては、複数人が所属するチームを作成し、そこでタスクを管理を行うというものです。
招待したい人に「このチームに入ってよ!」と招待メールを送ることができたらいいなと思い、gem「devise_invitable」を使用することにしました。
しかし、問題が。。🫠
アプリのイメージとしては1人のユーザーがたくさんのチームに所属できるというものだったのですが、デフォルトのdevise_invitableの機能では、新規のユーザー(まだアカウントを作っていない人)にしかメールを送ることができず、既存ユーザー(既にアカウントをもっている人)には送れません。(=1人1つのチームにしか所属できない)
ただし、絶妙になんとかなりそう感もある。。🤔
devise_invitableのREADMEのSending an invitation after user creationを見ると、下記のように書かれていて、なんとかなりそうな気配もありました。(DeepLでの翻訳を追加しています)
Sending an invitation after user creation(ユーザー作成後の招待状送信)
You can send an invitation to an existing user if your workflow creates them separately:
(ワークフローで既存のユーザーを個別に作成した場合、そのユーザーに招待状を送信することができます。)user = User.find(42) user.invite!(current_user) # current user is optional to set the invited_by attribute
..というわけでこのカスタマイズに奮闘した次第です!
制作したアプリ
【アプリ名】 TOPPA!
【アプリURL】http://43.206.187.158/
【GitHub】https://github.com/Asumi8/TOPPA.git
開発環境
MacBook Air (M1, 2020)
Rails 6.1.7
ruby 3.0.1
yarn 1.22.19
説明する項目
下記の順で説明します。
- 実装の前提
- 基本的なdevise_invitableの実装について
- invitataions/new.html.erbの変更
- invitations_controller.rbをカスタマイズ
- createアクションのの変更
- createアクションの解説
- updateアクションの変更
- updateアクションの解説
実装の前提
私が実現したい、招待⇨チームに所属の流れ
招待する側、招待される側の動きとしては、下記のような流れを考えました。
招待された側が招待メールにあるアプリへのリンクをクリック、アプリでパスワードの設定をしたタイミングでチームに所属しているという状態にしたいと思います。
new, create, edit, updateがそれぞれ呼び出されるタイミングは下記の通りです。
各テーブルの状況
ユーザーはチームに複数所属でき、チームにも複数のユーザーが所属できる仕様を考えているので、中間テーブルとしてassignsテーブルを用意し、user_idとteam_idを管理するようにしています。
usersテーブルのグレー背景部分はdeviseもしくはdevise_invitableによって追加したカラムです。黄色の背景のinvited_by_team_id
については独自に追加したカラムです。後ほど説明します。
基本的なdevise_invitableの実装について
基本的な実装方法については他に良い記事が多数ありますので、この記事では紹介しません🙇♀️
devise, devise_invitable共に実装済み状態になってから次に進んでください。
(最後に参考にさせていただいた記事を掲載しております)
invitataions/new.html.erbの変更
招待メールの送信時に、team_idも送信できるようにinvitataions/new.html.erbファイルに
<%= f.hidden_field :team_id, :value => (params[:team] || team) %>
を追記。
params[:team]
もしくは変数team
が:team_idに入るように記述しています。
変数team
については「render部分について」で説明しています。
<% resource.class.invite_key_fields.each do |field| -%>
<div class="field">
<%= f.label field %>
<%= f.text_field field, :placeholder => "例:xxx@xx.com" %>
<%= f.hidden_field :team_id, :value => (params[:team] || team) %>
</div>
<% end %>
invitations_controller.rbをカスタマイズ
デフォルトの処理の際は、invitations_controller.rbはsuperで継承するだけですが、今回はここの処理を変更したいのでcreate
アクション、update
アクションを変更していきます。
createアクションのの変更
createは招待したい相手のメールアドレスを入力し、招待メールを送る時に呼び出されるアクションです。
全体としては、下記のように変更しました。
class Users::InvitationsController < Devise::InvitationsController
def create
@user = User.new
user_email = params[:user][:email]
team_id = params[:user][:team_id]
if User.find_by(email: user_email.downcase).present? # 既存ユーザーの処理
user_id = User.where(email: user_email).pluck(:id)
user = User.find(user_id[0])
user.invite!(current_user)
user.invited_by_team_id = team_id
user.save
redirect_to teams_path(current_user), notice: "招待メールが#{user_email}に送信されました。"
elsif User.invite!(email: user_email, invited_by_team_id: team_id).valid? # 新規ユーザーの処理
redirect_to teams_path(current_user), notice: "招待メールが#{user_email}に送信されました。"
else
flash[:notice] = 'メールアドレスを正しく入力してください。'
render 'new', locals: { team: team_id }
end
end
createアクションの解説
@user
について
@user
の部分に関しては、newのビューのdevise特有の「resource」を書き換えるために記述しています。
@user = User.new
createで定義したのち、ビューのresource部分を@user
に書き換えます。
これによりredirect_toでエラーがでなくなりました。
<%= form_for(@user, as: resource_name, url: invitation_path(resource_name), html: { method: :post }) do |f| %>
既存ユーザーへの招待メール
入力された既存ユーザーの情報を変数user
に代入し、
user.invite!(current_user)
のinvite!メソッドで招待メールを送ります。
(READMEのSending an invitation after user creationで紹介されていたものです)
user.invited_by_team_id = team_id
は、
updateの時に招待されたチームのidを呼び出してチームに所属させるために、
usersテーブルに「invited_by_team_id」というカラムを新たに作成し、その中にteam_idを入れています。
updateアクションの際に説明しますが、招待された側がパスワード等を登録しupdateアクションが呼び出された時に、team_idを取り出してチームに所属させるためです。
if User.find_by(email: user_email.downcase).present?
user_id = User.where(email: user_email).pluck(:id)
user = User.find(user_id[0])
user.invite!(current_user)
user.invited_by_team_id = team_id
user.save
redirect_to teams_path(current_user), notice: "招待メールが#{user_email}に送信されました。"
新規ユーザーへの招待メール
User.invite!(email: user_email, invited_by_team_id: team_id)
でinvite!メソッドを使い新規ユーザーに招待メールを送ります。
invite!()の記載は、以下のgemのissueを参考にしました。
::invite! modifies existing users #747
elsif User.invite!(email: user_email, invited_by_team_id: team_id).valid?
redirect_to teams_path(current_user), notice: "招待メールが#{user_email}に送信されました。"
render部分について
render 'new', locals: { team: team_id }
は、
バリデーションにひっかかり、newのビューを再表示する時にteam_idを保持できるように、
変数team
の中にteam_idをいれています。
else
flash[:notice] = 'メールアドレスを正しく入力してください。'
render 'new', locals: { team: team_id }
end
updateアクションの変更
updateは、招待されたユーザーが招待メールのリンクをクリックし、パスワード登録をした際に呼び出されるアクションです。
全体としては、下記のとおりに変更しました。
teamsテーブルとusersテーブルは多対多の関係で、中間テーブルのassignsテーブルでチームの所属を管理しています。
user_id = resource.id
user = User.find(user_id)
user_assigned_teams = Assign.where(user_id: user_id).pluck(:team_id)
new_team_assign = []
new_team_assign << user_assigned_teams
new_team_assign << user.invited_by_team_id
new_team_assign = new_team_assign.flatten
user.update(team_member_ids: new_team_assign)
updateアクションの解説
user_assigned_teams = Assign.where(user_id: user_id).pluck(:team_id)
で
assginsテーブルから現在所属しているチームの情報を取得し、変数user_assinged_teams
に代入。
変数new_team_assign
に、user_assinged_teams
の値と、
createの際にusersテーブルのinvited_by_team_idカラムに入れていたteam_idを入れ、
所属しているチームのidが全て入った状態になります。
user_assigned_teams = Assign.where(user_id: user_id).pluck(:team_id)
new_team_assign = []
new_team_assign << user_assigned_teams
new_team_assign << user.invited_by_team_id
new_team_assign = new_team_assign.flatten
そのnew_team_assign
を使用し、assignsテーブルにあるuserの所属しているチームの情報を更新しています。
user.update(team_member_ids: new_team_assign)
以上となります!!
このような手順で考えていたような、新規ユーザー/既存ユーザーに関わらず招待メールを送ることができ、チームに所属させるタイミングをupdateで行うことができました。
最後に
今回の記事部分の実装を通して、gemのGitHubに書かれている内容やREADMEをじっくり読むことの大切さを学びました。
また、卒業課題の作成にあたりたくさんのQiita記事に助けられましたが、今回初めて自身が記事を書いてみて、記事を書くということの大変さと学びを共有くださる方々への感謝を改めて感じました。
もし抜けている点や至らぬ点ありましたらご指摘いただけますと幸いです。