3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

評価機能 + 自己完結型多対多アソシエーション

Last updated at Posted at 2021-02-20

##はじめに
前回作成したrails 評価機能(数字のみ)(https://qiita.com/inoponA/items/60384b454e16024295ff)を投稿しました
まぁ当然と言っちゃ当然だがこれだとユーザー、一人一人の評価が評価される訳ではなくでのページでも同じ評価がなされてしまう

と言う訳で、個別表示+α機能を備えた評価機能を作りました

##自己完結多対多とは?
まず個別に表示するには?と考えた時、一対多だとどのユーザーが評価した?しか分からない訳です
0ccf604cb6229dccbee2bd287f8e5832.png

よって、一対多のアソシエーションではなく、多対多のアソシエーションを組むことによって誰が誰に評価したかわかりやすくする
例:ラーメン店のレビュー
19c1fc72fe2f2c9dd5676c1421bb6821.png

これでレビューは出来るのだが、今回が人が人に評価する図式となるので上記ものだと少し違う
少しややこしいのだが、userテーブル一つにreviweテーブルが中間テーブルと言う形になる
7759d2e0da9af10219ea667fd740b7f6.png

今回はこの自己完結多対多を利用します

##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|
の一文である
ただそれだけ
ちなみにこれは逆だとエラーが起こる、私はこれの解決に時間がかかってしまったがどっちは親かって考えたら普通はわかるだろうと思う💦

##最後に
かなり解釈が間違えているかもですがそこはご愛敬お願いします笑

3
3
0

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
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?