##はじめに
前回作成したrails 評価機能(数字のみ)(https://qiita.com/inoponA/items/60384b454e16024295ff)を投稿しました
まぁ当然と言っちゃ当然だがこれだとユーザー、一人一人の評価が評価される訳ではなくでのページでも同じ評価がなされてしまう
と言う訳で、個別表示+α機能を備えた評価機能を作りました
##自己完結多対多とは?
まず個別に表示するには?と考えた時、一対多だとどのユーザーが評価した?しか分からない訳です
よって、一対多のアソシエーションではなく、多対多のアソシエーションを組むことによって誰が誰に評価したかわかりやすくする
例:ラーメン店のレビュー
これでレビューは出来るのだが、今回が人が人に評価する図式となるので上記ものだと少し違う
少しややこしいのだが、userテーブル一つにreviweテーブルが中間テーブルと言う形になる
今回はこの自己完結多対多を利用します
##DB,カラム
_create_reviews.rb
t.integer :speedy
t.integer :kindness
t.integer :frantically
t.references :reviewer, null: false, foreign_key: { to_table: :users }
t.references :reviewing, null: false, foreign_key: { to_table: :users }
t.timestamps
注目していただきたいのはreviewerとreviewingと言うカラムが作らているがこれはforeign_key: { to_table: :users }でuserテーブルを外部キーとして指定している
でもこれだけだとただのreviewer、reviewingカラムができただけであるので下記のモデルにこのカラムが擬似的なuser.idだとするようにしてあげる必要がある
reviewモデル
class Review < ApplicationRecord
belongs_to :reviewer, class_name: "User"
belongs_to :reviewing, class_name: "User"
end
ここできもなのがclass_nameオプションである
これによって同じuser_idをreviewer_id、reviewing_idと言う形で分ける事が可能になるので誰が誰にと言う図式が完成する
コレで中間テーブルは完成
でも中間テーブルがあると言うことは多の部分も作成していくことになる
user.rb
has_many :active_reviews, class_name: "Review",foreign_key: :reviewing_id
has_many :reviewings, through: :active_reviews, source: :reviewer
has_many :passive_reviews, class_name: "Review",foreign_key: :reviewer_id
has_many :reviewers, through: :active_reviews, source: :reviewing
def followed_by?(user)
passive_reviews.find_by(reviewing_id: user.id).present?
end
一つ一つ説明します
まずactive_reviewsとかpassive_reviewsなんやねんと思ったでしょうが、reviewと言う中間テーブルを扱ってもいいのだがこれだと後述の評価を一人一回と言う制限をする為には少々不都合なので
”レビューする側のテーブルとされる側のテーブル”とclass_nameで名前を変える
要するに
- active_reviewsはレビューされる側
- passive_reviewsはレビューする側
である
def followed_by?(user)
passive_reviews.find_by(reviewing_id: user.id).present?
end
はpassive_reviewsのレビューされる側からレビューされるIDを探せと言うコードを記述し一人一回しか評価できないようにします
##routes.rb
resources :users do
resources :reviews, only: :create
end
ユーザーにレビューをネストさせる
レビューされる為にはユーザーのIDが欲しいのでこれをする必要がある
##controller-review
class ReviewsController < ApplicationController
def create
@review = Review.create(review_params)
if @review.save
redirect_to root_path
else
render :show
end
end
private
def review_params
params.require(:review).permit(:speedy, :kindness, :frantically).merge(reviewer_id: current_user.id, reviewing_id: params[:user_id])
end
end
言うまでもないが、mergeで各ユーザーIDも保存している
- reviewer_idは評価した人(評価の人)
- reviewing_idはされた人(ユーザーページの人)
controller-user
def show
@posts = @user.posts
@review = Review.new
@kindness = Review.where(reviewing_id: params[:id]).average(:kindness)
@speedy = Review.where(reviewing_id: params[:id]).average(:speedy)
@frantically = Review.where(reviewing_id: params[:id]).average(:frantically)
if @kindness != nil && @speedy != nil && @frantically != nil
@comprehensive = (@kindness + @frantically + @speedy) / 3
else
@speedy = 0
@kindness = 0
@frantically = 0
@comprehensive = @kindness + @frantically + @speedy
end
今回はshowページで一連の流れを行っているのでshowに必要な事を記述しています
@review = Review.newでレビュー内容を生成
以下は平均値を求める内容です
Review.where(reviewing_id: params[:id])でされた人のIDを探しその中でも特定のカラムの平均が欲しかったので.averageを利用しました
##view1 表示側
- if @user.id != current_user.id
- if current_user.followed_by?(@user)
.UserEvaluate
評価は一人一回です
- else
.UserEvaluate
%button.EvaluateBtn
= "#{@user.name}さんを評価する"
= render "evaluate"
.UserStatus
他のユーザーの評価
.UserStatus__Kindness
= icon('fa','smile') + "親切度 : #{@kindness.round(1)} point"
.UserStatus__speedy
= icon('fa','shipping-fast') +"迅速さ : #{@speedy.round(1)} point"
.UserStatus__frantically
= icon('fa','redo')+"repeat : #{@frantically.round(1)} point"
.UserStatus__comprehensive
= icon('fa','star')+"総合力 : #{@comprehensive.round(1)} point"
.UserEdit
1行目:投稿者と閲覧者が同一ユーザー出ない時に分岐するIF文の作成
同一だと評価できない図式に
2行目:followed_by?メソッドをビューで利用してもしログインしているユーザーがレビューした側にいたら評価は一人一回ですと言う表示をしていなければ評価出来るように分岐する
ビューはこれだけ
view2 入力側
.EvaluateSide
.EvaluateSide__EvaluateBox
= form_with model:[@user,@review],local: true do |f|
.close
= icon('fas','times')
.UserEvaluate
.UserEvaluate__title
= "#{@user.name}さんを評価しよう"
.UserEvaluate__UserKindness
= "#{@user.name}さんをは親切でしたか?"
.form
.form__KindnessGood
= f.radio_button :kindness,"3",id: "kaidness-good" ,class: "UserKindnessEvaluate"
.form__KindnessGood__good
= f.label "親切" ,for:"kaidness-good"
.form__KindnessUsually
= f.radio_button :kindness,"2",id: "kaidness-usually" ,class: "UserKindnessEvaluate"
.form__KindnessUsually__Usually
= f.label "普通" ,for:"kaidness-usually"
.form__KindnessBad
= f.radio_button :kindness,"1",id: "kaidness-bad" ,class: "UserKindnessEvaluate"
.form__KindnessBad__bad
= f.label "不親切" ,for:"kaidness-bad"
.UserEvaluate__UserSpeedy
= "#{@user.name}さんをは迅速な対応でしたか?"
.form
.form__SpeedyGood
= f.radio_button :speedy, "3",id: "speedy-good" ,class: "UserKindnessEvaluate"
.form__SpeedyGood__good
= f.label "迅速" ,for:"speedy-good"
.form__SpeedyUsually
= f.radio_button :speedy, "2",id: "speedy-usually" ,class: "UserKindnessEvaluate"
.form__SpeedyUsually__Usually
= f.label "普通" ,for:"speedy-usually"
.form__SpeedyBad
= f.radio_button :speedy, "1",id: "speedy-bad" ,class: "UserKindnessEvaluate"
.form__SpeedyBad__bad
= f.label "遅い" ,for:"speedy-bad"
.UserEvaluate__frantically
= "#{@user.name}さんにもう一度お願いしたいですか?"
.form
.form__franticallyGood
= f.radio_button :frantically, "3",id: "frantically-good" ,class: "UserKindnessEvaluate"
.form__FranticallyGood__good
= f.label "頼みたい" ,for:"frantically-good"
.form__FranticallyUsually
= f.radio_button :frantically, "2",id: "frantically-usually" ,class: "UserKindnessEvaluate"
.form__FranticallyUsually__Usually
= f.label "普通" ,for:"frantically-usually"
.form__FranticallyBad
= f.radio_button :frantically, "1",id: "frantically-bad" ,class: "UserKindnessEvaluate"
.form__FranticallyBad__bad
= f.label "これっきり" ,for:"frantically-bad"
.UserEvaluate__SendBox
= f.submit "#{@user.name}さんを評価する",class:"send-btn"
ざっと長いコードになってしまったが重要なのは
= form_with model:[@user,@review],local: true do |f|
の一文である
ただそれだけ
ちなみにこれは逆だとエラーが起こる、私はこれの解決に時間がかかってしまったがどっちは親かって考えたら普通はわかるだろうと思う💦
##最後に
かなり解釈が間違えているかもですがそこはご愛敬お願いします笑