スクールのポートフォリオ制作で、中学生とその保護者をターゲットと想定した学習管理アプリの作成を進めています。
その中で、保護者が生徒をフォローしている場合にテストの点数などの個人情報が見られるようにしたかったため、フォローリクエストが承認された場合のみフォロー状態となるように実装をしました。
フォロー機能に関してはrailsチュートリアル含めたくさんの参考記事がありましたが、フォローリクエスト機能に関しては参考になる記事が自分では見つけられなかったため、自分なりに実装案を2つ考えました。2つのうち、今回自分が実装した方法を記載してみようと思います。参考になれば幸いです。
環境
ruby 2.5.7
rails 5.2.4
(slim-railsを使用しています)
リレーション
実装する前はparentsテーブルとstudentsテーブルがあるのみ(deviseで作成済み)です。
実装後、ER図は最終的には下図のようになります。
follow_requestsテーブル(保護者から生徒に送られるフォローリクエストを保存するテーブル)とparent_followsテーブル(生徒がフォローリクエストを承認した場合にフォロー状態を保存するテーブル)がstudentsとparentsの中間テーブルになっています。
また、今回はネームスペースでstudentとparentを分けています。
実装案A
今回実装したのはこのAの方法です。
- follow_requestsテーブルにstudent_idとparent_idを追加
- parent_followsテーブルにstudent_idとparent_idを追加
- 保護者がフォローリクエスト申請ボタンを押したらfollow_requestsにデータ作成
- 生徒がフォロー許可を押したらparent_followにデータ作成してfollow_requestsのデータは削除
実装案B
一応案Bも記載しておきます。
- follow_requestテーブルにstudent_id,parent_idを追加
- studentテーブルにallowカラム(フォローリクエストを許可したかどうか判定するカラム)、データ型booleanで追加
- リクエスト承認したらstudentsのallowカラムをtrueに変更、follow_requestのデータは論理削除
- if parent.student.allowで条件つけてコンテンツ表示
保護者から複数(父親と母親)からフォローされた時に困るような気がしたのと、アプリケーションの作成途中(この時点で8割方できてました)で論理削除のgemを入れるのがなんとなく嫌だったので採用はしませんでした。
実装
簡単なところの詳細は省きます。作るテーブル名等は適宜変更して下さい。
1.各テーブル作成
follow_requestsテーブルとparent_followsテーブルを作成し、それぞれにstudent_id,parent_idカラムをデータ型integerで追加→migrate。
2.各モデルにリレーション記載
belongs_to :student
belongs_to :parent
has_many :follow_requests, dependent: :destroy
has_many :parent_follows, dependent: :destroy
3.コントローラ作成
rails g controller student/FollowRequests
rails g controller student/ParentFollows
rails g controller parent/FollowRequests
今回はこの3つを作成します。parent/ParentFollows
は、保護者側がフォロー承認をすることはなかったり、保護者のフォロー一覧を見る必要もなかったりするため作成していません。
4.ルーティング
ネームスペースごとに書きます。
namespace :parent do
resources :students, only: [:show] do
resource :follow_requests, only:[:create, :destroy]
end
end
namespace :student do
resources :students, only: [:show, :edit, :update] do
resources :parent_follows, only:[:destroy, :show, :index]
post '/follow_requests/:id' => 'follow_requests#allow', as: 'allow'
resources :follow_requests, only:[:index, :show, :destroy]
end
end
urlが/parent/students/:student_id/follow_requests
や/student/students/:student_id/follow_requests/:id
のようになるようにします。
また、followrequests#allow
のルーティングはresources:follow_requests
の上に書いてください。よくありますが、下に書いちゃうとエラーになります。
5.保護者がフォローリクエストを送れるようにする
フォローリクエストを送っているかどうかを判定するメソッドをparentモデルに設定します。今回はalready_requested?
としました。
has_many :follow_requests
def already_requested?(student)
self.follow_requests.exists?(student_id: student.id)
end
viewファイルの記述です。先ほどのメソッドを使って、フォローリクエストを送っているかどうかで表示を切り替えます。
- if current_parent.already_requested?(student)
=link_to "フォローリクエスト取消", parent_student_follow_requests_path(student_id:student.id), method: :delete
- else
=link_to "フォローリクエスト送信", parent_student_follow_requests_path(student_id:student.id), method: :post
上記のフォローリクエストを送るページではどの生徒にフォローリクエストを送るのかわかるようにしておいてください。
自分の場合は検索結果画面で、検索した生徒情報をeachで回しています。
コントローラの記述です。follow_requestsテーブルにデータを入れてsaveしたり、そのデータをdestroyしたりします。
def create
student = Student.find(params[:student_id])
request = current_parent.follow_requests.new(student_id: student.id, parent_id: current_parent.id)
request.save
redirect_back(fallback_location: parent_root_path) #同じページにリダイレクト。これは任意のページで。
end
def destroy
student = Student.find(params[:student_id])
request = current_parent.follow_requests.find_by(student_id: student.id, parent_id: current_parent.id)
request.destroy
redirect_back(fallback_location: parent_root_path) #同じページにリダイレクト。これは任意のページで。
end
以上でフォローリクエストの送信と取消ができるようになりました。
6.生徒側でフォローリクエストを表示する
フォローリクエストが送れたので、生徒側で確認します。
def index
@requests = current_student.follow_requests.all
#ログイン中の生徒のフォローリクエストを全て取得
end
table.table.table-condensed
tr
td
|名前
td
td
-@requests.each do |request|
tr
td
=request.parent.name
td
= link_to "フォロー承認", student_student_allow_path(student_id:current_student.id, id:request.id), method: :post
#次の承認で使うコントローラのパスを記載
td
= link_to "フォロー拒否", student_student_follow_request_path(student_id:current_student.id, id:request.id), method: :delete
7.フォローリクエストを承認・拒否する
コントローラで承認(allow)と拒否(destroy)のアクションを記述します。
(書いてて思いましたが、拒否はrefuseですかね。。。resourceに任せる意味合いでもdestroyにしましたが、ちょっと失敗しました。)
def allow
request = FollowRequest.find(params[:id])
parent = Parent.find_by(id:request.parent_id)
follow = current_student.parent_follows.new(student_id:current_student.id, parent_id: parent.id)
#follow_requestsコントローラーですが、parent_followsのnewメソッドです。
follow.save #parent_followに保存。
request.destroy # follow_requestは削除
redirect_back(fallback_location: student_root_path)
end
def destroy
request = FollowRequest.find(params[:id])
request.destroy
redirect_back(fallback_location: student_root_path)
end
これでフォロー承認を押すとフォローリクエスト一覧から消えて、parent_followsテーブルに保存されます。
8.保護者フォロー一覧の作成
確認のため、きちんと保護者からのフォロー一覧ページを作成します。
ここでは一応フォローの解除もできるようにします。
def index
@followers = current_student.parent_follows.all
end
def destroy
follow = ParentFollow.find(params[:id])
follow.destroy
redirect_back(fallback_location: student_root_path)
end
table.table.table-condensed
tr
td
|名前
td
-@followers.each do |follow|
tr
td
=follow.parent.name
td
= link_to "フォロー解除", student_student_parent_follow_path(student_id:current_student.id, id:follow.id), method: :delete
8.フォローしている生徒にフォローリクエストが送れないようにする
フォローしていたらフォローリクエストが送れないようにします。
parentモデルにフォロー済みかどうか判定するメソッドを記述します。今回はalready_followed?
としました。
def already_followed?(student)
self.parent_follows.exists?(student_id: student.id)
end
フォローリクエストを送る画面の記述を少し変更します。
- if current_parent.already_followed?(student)
| すでにフォロー済みです
- elsif current_parent.already_requested?(student)
=link_to "フォローリクエスト取消", parent_student_follow_requests_path(student_id:student.id), method: :delete
- else
=link_to "フォローリクエスト送信", parent_student_follow_requests_path(student_id:student.id), method: :post
まとめ
以上でフォローリクエスト機能を実装することができました。
やり方はいくらでもあると思いますが、学習歴が数ヶ月の自分で思いつく方法はこのぐらいでした。
もしもっと簡単に実装する方法があったり、上記のコードで誤りや不明点がありましたらコメントかTwitterでリプいただけると幸いです。