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

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

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

##自己完結多対多とは？
まず個別に表示するには？と考えた時、一対多だとどのユーザーが評価した？しか分からない訳です
![0ccf604cb6229dccbee2bd287f8e5832.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/672760/8a68cc9b-bb4d-5540-41f4-426be0fb09f0.png)

よって、一対多のアソシエーションではなく、多対多のアソシエーションを組むことによって誰が誰に評価したかわかりやすくする
例：ラーメン店のレビュー
![19c1fc72fe2f2c9dd5676c1421bb6821.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/672760/95ac1f4d-6911-a272-9620-173230a1bcae.png)

これでレビューは出来るのだが、今回が人が人に評価する図式となるので上記ものだと少し違う
少しややこしいのだが、userテーブル一つにreviweテーブルが中間テーブルと言う形になる
![7759d2e0da9af10219ea667fd740b7f6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/672760/b7593c19-6af6-964b-8c5f-c1d37e49b166.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を利用しました

##view１　表示側

```
        - 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
```
１行目：投稿者と閲覧者が同一ユーザー出ない時に分岐するIF文の作成
同一だと評価できない図式に
２行目：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|
の一文である
ただそれだけ
ちなみにこれは逆だとエラーが起こる、私はこれの解決に時間がかかってしまったがどっちは親かって考えたら普通はわかるだろうと思う💦


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