ポートフォリオを作っていた際にwebアプリケーションでよく見かけるユーザー通報機能を実装したいと思い、実装の仕組みを検索してみたのですがほとんどヒントを見つけられませんでした。
なので、自己流でユーザー通報機能を実装してみました。まだまだ初心者のため、諸々間違いがあるかもしれませんが、この記事が少しでも皆さんの助けになると幸いです。(何かお気づきのことがあればコメントお願いいたします> <)
【環境】
- Rails 6.1.5.1
- ruby 2.6.3
【前提】
- 管理者機能(Admin)を実装しています!
- deviseを使っています!
【実現したいこと】
- 自分以外のユーザーを通報できる機能。
- 通報されたユーザーを管理者が確認できる。
- その通報のステータス(対応済み等)を更新できる。
- 管理者の判断でユーザーを退会扱いとする事ができる。そのユーザーはログインできなくなる。
【テーブル設計】
User | Report |
---|---|
name | reporter_id |
reported_id | |
user_status | reason |
url | |
status |
Userテーブル
- Userテーブルは適宜必要なカラムを用意してください。
- user_status →ユーザーのアカウントを管理者が「有効・無効」に切り替えられるようにする
Reportテーブル
- reporter_id →通報したユーザー
- reported_id →通報されたユーザー
- reason →通報理由
- url →悪質な投稿等のURLがあれば貼ってもらう為
- status →管理者が通報に対して「対応済み」等、ステータスを変えられるようにする。
★ReportテーブルはUserテーブルの中間テーブルになります。(フォロー・フォロワー機能の時のようなイメージ)
通報する人、通報される人もUserになるので、多対多の状態になります。
カラム型、制約等は下記のようにしました!
def change
#deviseで作ったテーブルにカラムを追加したので、add_columnになってます
add_column :users, :name, :string
add_column :users, :user_status, :boolean, default: false
end
def change
create_table :reports do |t|
t.integer :reporter_id, null: false
t.integer :reported_id, null: false
t.text :reason, null: false
t.text :url
t.integer :status, default: 0, null: false
t.timestamps
end
end
【モデル】
Userモデル
reports
とreverse_of_reports
は分かりやすいように名前を付けているだけなので、自由に変えて頂いて大丈夫です!
class_name: "Report"
は↑で好きな名前をつけたので、ちゃんとReportテーブルを参照してもらうための記述です。
foreign_key: ○○
は、 どのカラムを参照するかを指定しています。
has_many :reports, class_name: "Report", foreign_key: "reporter_id", dependent: :destroy
has_many :reverse_of_reports, class_name: "Report", foreign_key: "reported_id", dependent: :destroy
Reportモデル
こちらも同様に、分かりやすい名前をbelongs_to :○○
のところで付けて、class_name: "User"
でUserテーブルを参照するよう指定します。
belongs_to :reporter, class_name: "User"
belongs_to :reported, class_name: "User"
【enum】
enumとは、数値に意味を持たせることが出来るものです。
今回は、管理者が通報に対して対応ステータス(未対応・対応済み等)を変更できるようにしたいので、enumを使いたいと思います!ちなみに、この記事では「enum_help」というgemを導入していきますが、gemを使わなくてもenumは使えます。
enum_helpを導入する
Gemfileの一番下にgem "enum_help"
を記述し、bundle install
をします。
#Gemfile
gem "enum_help"
Reportモデルにenumを定義
Reportテーブルを作成した際に、statusカラムをinteger型で作りました。追加した記述は、status
がカラム名、waiting
・keep
・finish
は好きに命名したものです。0,1,2はstatusカラムに保存される数字です。
私の場合、
waiting →対応待ち
keep →保留
finish →対応済み
にしました。皆さんのお好みで名前を変えたり数を増やしたりしてくださいね!
belongs_to :reporter, class_name: "User"
belongs_to :reported, class_name: "User"
enum status: { waiting: 0, keep: 1, finish: 2 } #ここを追加
アプリケーションを日本語化
このままだと、数字に対応する文字列が英語になってしまうので、日本語化していきたいと思います。
下記ファイルにコードを2つ追加します。これで、アプリケーションの言語が日本語になりました!
※これに伴いdeviseを入れられている方は、deviseのエラーメッセージが「日本語対応していません」のような内容になると思うので、以下の記事を参考にされるとエラーメッセージも日本語化できるかと思います!
https://qiita.com/you8/items/921e0dd1210eb0d158df
class Application < Rails::Application
ーー省略ーー
config.i18n.default_locale = :ja #追加
config.i18n.load_path += Dir[Rails.root.join('config/locales/*.yml').to_s] #追加
end
ymlファイルを作成、記述
ja.yml
ファイルを作成し、先程定義した英語に対応する日本語を記述しましょう!
これで、enumはひとまず完了です。
ja:
enums:
report:
status:
waiting: "対応待ち"
keep: "保留"
finish: "対応済み"
【ルーティング】
私のアプリケーションでは、ユーザー側がpublic、管理者側がadmin、としてコントローラやviewを分けています。
管理者(admin)の方は、一覧(index)、詳細(show)ページを作り、詳細ページからステータスを更新(update)できるようにしたいと思います。
ユーザー(public)の方は、通報したいユーザーの詳細ページから通報作成ページ(new)へのリンクを作り、通報作成(create)できるようにしたいと思います。
Rails.application.routes.draw do
#省略
namespace :admin do
#省略
resources :reports, only: [:index, :show, :update]
resources :users, only: [:index, :show, :update, :edit]
#この記事ではusersはeditとupdateだけ使います!
end
scope module: :public do
#省略
resources :users, only: [:show, :edit, :update, :index] do
resources :reports, only: [:new, :create]
end
end
end
【コントローラ】
まず、通報を作成するユーザー(public)のコントローラーをみてみましょう。
class Public::ReportsController < ApplicationController
def new
@report = Report.new #新しい通報の箱を用意
@user = User.find(params[:user_id]) #どのユーザーに対する通報なのかparamsで取得する
end
def create
@user = User.find(params[:user_id]) #どのユーザーに対する通報なのかparamsで取得する
@report = Report.new(report_params) #ストロングパラメータを通す
@report.reporter_id = current_user.id #通報者(reporter_id)にcurrent_user.idを代入
@report.reported_id = @user.id #通報される人(reported_id)に上で取得した@user.idを代入
if @report.save #保存する
redirect_to user_path(@user), notice: "ご報告ありがとうございます。"
else
render "new"
end
end
private
def report_params
params.require(:report).permit(:reason, :url)
end
end
続いて管理者側です。通報のコントローラです。
class Admin::ReportsController < ApplicationController
def index
@reports = Report.all #全ての通報を取得
end
def show
@report = Report.find(params[:id]) #特定の通報IDを取得
end
def update
@report = Report.find(params[:id])
if @report.update(report_params) #ストロングパラメータを通して更新
flash[:notice] = "対応ステータスを更新しました。"
redirect_to request.referer
end
end
private
def report_params
params.require(:report).permit(:status)
end
end
管理者用のユーザーコントローラです。ユーザーの有効ステータスを更新できるようにします。
class Admin::UsersController < ApplicationController
def edit
@user = User.find(params[:id]) #編集したいuserのIDを取得
end
def update
@user = User.find(params[:id])
if @user.update(user_params) #ストロングパラメータを通して更新
redirect_to admin_user_path(@user), notice: "#{@user.name}さんの会員ステータスを更新しました"
else
render "edit"
end
end
private
def user_params
params.require(:user).permit(:user_status)
end
end
【ビュー】
※bootstrap使ってます!!色々省略したのでこのままだとレイアウトめちゃくちゃです...
まずは、ユーザー詳細ページです。通報作成画面へのリンクを追加します。
#前後は省略
<% if @user != current_user %> #自分の詳細ページでなければ、通報ボタンを表示させる
<%= link_to "ユーザーを通報", new_user_report_path(@user), class: "btn btn-danger" %>
<% end %>
先程のリンクを押した後の、通報作成画面です。ここで作成したものが管理者側のviewで見れるようになります。
<h3>通報</h3>
<%= form_with model:[@user, @report] do |f| %>
<div class="form-group">
<%= f.text_area :reason, rows:'3', class:"form-control" %>
<%= f.text_field :url, class:"form-control" %>
<%= f.submit "通報する", class: "btn btn-danger" %>
</div>
<% end %>
続いて、管理者の通報一覧ページです。
<h3>通報一覧</h3>
<table class="table">
<thead>
<tr>
<th>通報ID</th>
<th>会員名</th>
<th>通報者</th>
<th>ステータス</th>
</tr>
</thead>
<tbody>
<% @reports.each do |report| %>
<tr>
<td>
<%= link_to report.id, admin_report_path(report) %>
</td>
<td>
<%= report.reported.name %>
</td>
<td>
<%= report.reporter.name %>
</td>
<td>
<% report.status_i18n %> #enumで定義した日本語を呼び出す
</td>
</tr>
<% end %>
</tbody>
</table>
通報詳細ページです。
最後の方にある、<%= f.select :status, Report.statuses.keys.map { |k| [t("enums.report.status.#{k}"), k] } %>
この記述をすると、Reportテーブルのstatusカラムにenumで定義した日本語バージョンのセレクトボックスを作ることができます。
<h3 class="page-title">通報詳細</h3>
<table class="table">
<tbody>
<tr>
<td>通報ID</td>
<td><%= link_to @report.id %></td>
</tr>
<tr>
<td>会員名</td>
<td><%= @report.reported.name %></td>
</tr>
<tr>
<td>理由</td>
<td><%= @report.reason %></td>
</tr>
<tr>
<td>URL等</td>
<td><%= @report.url %></td>
</tr>
<tr>
<td>通報者</td>
<td><%= @report.reporter.name %></td>
</tr>
<tr>
<td>対応ステータス</td>
<td>
<%= @report.status_i18n %>
<%= form_with model:@report, url:admin_report_path(@report), method: :patch, local: true do |f| %>
<%= f.select :status, Report.statuses.keys.map { |k| [t("enums.report.status.#{k}"), k] } %>
<%= f.submit '更新' %>
<% end %>
</td>
</tr>
</tbody>
</table>
</div>
ユーザー情報の編集ページです。user_statusはboolean型で作成したので、trueかfalseを選べるようにします。(デフォルトをfalseにしているので、falseが有効です!)
<h3>会員情報編集</h3>
<%= form_with model:@user, url: admin_user_path(@user), method: :patch do |f| %>
<div class="custom-control custom-radio">
<%= f.label :user_status, '会員ステータス' %>
<%= f.radio_button :user_status, false %> <%= f.label :有効 %>
<%= f.radio_button :user_status, true %> <%= f.label :退会 %>
</div>
<%= f.submit "ステータスを更新" %>
<% end %>
【ユーザーログイン制限】
最後に、管理者によってステータスを退会に変更されたユーザーがログインできないようにしたいと思います。
セッションのコントローラにログインできないメソッドを記述します。
# 退会済なら新規登録画面へ遷移させる
def user_state
@user = User.find_by(email: params[:user][:email]) #emailからuserを探す
if @user #もしアカウントが見つかれば
if @user.valid_password?(params[:user][:password]) && @user.user_status
#@userのパスワードが有効かつ退会済み(user_status == true)ならば
flash[:notice] = 'お客様のアカウントは現在ご使用できません。'
redirect_to new_user_registration_path #新規登録画面へ遷移させる
end
end
end
【まとめ】
ここまでで、ユーザー通報機能は完成です!
長々と書いてしまいましたが、最後までご覧いただきありがとうございます!!個人的にはもっといい実装方法がある気がするので、もっと勉強していきたいと思います。