[rails 初心者]同率順位を踏まえたランキング機能

<h2>初めに

ポートフォリオテーマとして業者のレビューサイトを作成しています。<br>以下の画像のようなレビューサイトを作っていこうと思います。

![top画面２－１.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2822721/a259bdd1-d312-4eb9-219e-ecbbd0526cec.png)


※自分用でまとめてますので、分かり辛かったらすいません。<br>また理解不足・誤りがあったらコメント頂けますと幸いです。m(__)m<ﾖﾛｼｸｵﾈｶﾞｲｼﾏｽ

<h2>前提条件</h2>

<p>
①今回の記事ではランキング機能のみ(※同率順位を踏まえた順位)についてまとめます<br>
②レビュー機能や並び替え機能については、以下URLの記事に記載されている内容を参考にしてください<br>

[[rails初心者]複数権限でのログインを使ったレビューサイトの作成（基本）](https://qiita.com/about10/items/0e376c84022f2ac238d0)
[[rails初心者]複数権限でのログインを使ったレビューサイトの作成（応用）](https://qiita.com/about10/items/24e7ed9f1ae16ea0f548)
</p>
<h2>今回作成するランキング機能について</h2>

・同率順位がある場合、同じ順位として表示すること
・すべての業者に対してランキング機能を実装する（ページネーションの移動後でもその順位は継承されていること）
<h2>主なやること</h2>

・コントローラの編集（配列の付け足し）
・モデルの編集（平均値算出処理の編集）
・ビューページの編集（配列が付け足されたことによる修正作業）

この３点です

<h2>開発環境</h2>
・rails 6.1.7<br>
・ruby 3.1.2 <br>
・raty 3.1.1<br>
・jquery 3.6.1<br>
・bootstrap 4.5

<h2>コントローラの記述</h2>
<h4>配列の付け足し</h4>

今回は**map**メソッドを使って次のようなメソッドを作っていきます
```:app/controllers/平均値を表示したいファイルに関係するコントローラ
  def アクション名
    same_rate = 0
    rank = 1
    traders = Trader.find(Review.group(:trader_id).order('avg(star) desc').pluck(:trader_id)).map.with_index(1) do |trader, i|
    if i == 1 #ランキング処理の一番最初だけ以下の処理を行う
      same_rate = trader.review_average #一番最初だけ「same_rate」に値が入っていないので、先に値を入れる
    end
    if trader.review_average != same_rate #同率順位がない場合以下の処理
      rank = i #同率が無いので、順位を更新
      same_rate = trader.review_average #「same_rate」に現在の値を代入 → 次の値と比べる用として運用するイメージ
      {trader: trader, index: rank } #[trader = trader]という配列と[index = rank]という配列それぞれをセットにしている
    else #同率順位がある場合以下の処理
      same_rate = trader.review_average
      {trader: trader, index: rank }
    end
  end

  @traders = Kaminari.paginate_array(traders).page(params[:page]).per(5)
  #Kaminari.paginate_array → pageメソッドを配列に対して用いる場合に必要な記述
```

<h4>解説１</h4>

まずは`same_rate = 0` , `rank = 1`の部分についてです。
使う意味合いとしては次の通りです。

:::note info
ポイント①同率順位を考慮する＝`same_rate`を使って同率かどうかを調べる
ポイント②順位の表示＝`rank`を使って順位を表示させる
:::

そしてこの行でやっていることは次の通りです。
`same_rate = 0` , `rank = 1`の部分では、これらの変数の初期値を最初に決めている感じです。

<h4>解説２</h4>

次に`traders =Trader.find( 省略 ).map.with_index(1) do |trader, i|`の部分です。
使う意味合いとしては次の通りです。

:::note info
ポイント①もともとある配列(`Trader.find( 省略 )`)に別の配列を付け足す(`.map`)
ポイント②新しい方の配列は１からスタートさせたい(`.with_index(1)`)
ポイント③変数は２種類(`|trader, i|`)
:::

<details>
<summary>補足説明</summary>

実は`map`メソッドは今回が初めてでした　(*´▽｀*)ﾃﾍﾍﾟﾛ
ということで簡単にまとめると

`map`メソッド　＝＝　`each`＋処理を実行　だと思いました( ..)φﾒﾓﾒﾓ
※詳しくは参考サイトのリンクを参照してくださいm(__)mｵﾈｶﾞｲｼﾔｽ

```:基本的な書き方
    配列変数.map {|変数名| 具体的な処理 }
```

今回の`map`メソッドさんのシナリオ（やって欲しい挙動）は次の通りです。
もともとある配列(`Trader.find( 省略 )`)に別の配列(`.with_index(1)`)を付け足す(`.map`)。
その際使う変数は２種類(`|trader, i|`)とする。

また新しく作る配列(`.with_index(1)`)には書き方が二種類あるらしいです。
今回は「１」からスタートしたかったので、`.with_index(1)`を選びました。

ここまでの情報をまとめてみるとこんなイメージです。字が汚いのはすいません(;^ω^)
![mapメソッド.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2822721/92347a26-7ad7-34cb-c9f9-9f67e93e3eae.jpeg)

以下が参考記事のリンクです。
[【Rails入門】mapメソッドを完全攻略!配列操作の基礎を学ぼう](https://www.sejuku.net/blog/58958)
[【Ruby】インデックス有りのループ処理を行う場合、each_with_indexよりもwith_indexの方が便利だった](https://techtechmedia.com/with_index/)
</details>

<h4>解説３</h4>

続いては if の条件部分`trader.review_average != same_rate`です。
使う意味合いとしては次の通りです。

:::note info
ポイント①同率順位がいるかどうかを調べている
ポイント②`trader.review_average`には今回のチェック対象の平均値が入っている
ポイント③`same_rate`には前回チェック時に対象が持っていた平均値が入っている
:::

つまりこんな感じです。
`「前回の平均値　＝＝　今回の平均値」`

または
`「前回の平均値　！＝　今回の平均値」`

どっちでもOK（処理の中身が逆になるだけなので）

<h4>解説４</h4>

続いて`same_rate = trader.review_average`と`{trader: trader, index: rank }`です。
これらの意味合いとしては次の通りです。

:::note info
ポイント①`same_rate`の更新
ポイント②`|trader, i|`に入る値を決めている
:::

##### ポイント①について・・・
先程の解説３の時に同率順位かどうかをチェックしたと思います。
このチェックでは「**一つ前の順位と同じか？**」という見えない条件があり、
それをクリアするためには随時更新作業が必要になります。
なので`same_rate = trader.review_average`処理が記述されているわけです。

また`{trader: trader, index: rank }`については、次の画像のように「配列１と配列２」それぞれにどの値を代入するかを決めているだけです。
![mapメソッド２.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2822721/4a4e59c2-cd81-9f91-ab78-7afbb777e0ed.jpeg)





<h2>モデルの編集（平均値算出処理の編集）</h2>
<h5>平均値算出処理の編集</h5>

まず前回紹介した平均値算出用に作ったメソッドがこちらです
```:app/models/trader.rb
  ##評価点の平均値
  def review_average(trader)
    if trader.reviews.count >= 1 #顧客(client)に評価されたことがあるか否かを確認
      reviews=Review.where(trader_id: trader.id).pluck(:star)#starカラムに保存されている評価点数を取得
      #reviews.map!(&:to_i)#取得した変数（reviews）が文字列の場合、数列型へ変更しましょう
      reviews.sum.fdiv(reviews.length).round(1)#取得した変数をもとに平均値を算出
      #round(1)とすることで小数点第2位に対して四捨五入をします。
    else
      0.0 #顧客(client)に評価されたことが無い場合、平均値を0.0に設定
    end
  end
```

これを次のように書き替えます。
```:app/models/trader.rb
  ##評価点の平均値
    def review_average
    if reviews.count >= 1
      reviews=Review.where(trader_id: id).pluck(:star)#starカラムに保存されている評価点数（文字列型）を取得
      reviews.map!(&:to_i)#取得した変数（reviews）を数列型へ変更する
      reviews.sum.fdiv(reviews.length).round(1)#取得した変数をもとに平均値を算出
      #round(1)とすることで小数点第2位に対して四捨五入をします。
    else
      0.0
    end
  end
```

#### 主な変更点
:::note info
・メソッド名の横にある引数の`(trader)`を排除
・引数が無くなったので、レシーバ( . の左の文字)は`self`になる
・ただ`self`はレシーバである場合、省略可能
:::
やることは以上になります。

<h2>ビューページの編集</h2>

<h4>配列が付け足されたことによる修正作業</h4>

結論からいうと、次のような記述になります。
```:app/views/平均値を表示したいファイル
<% @traders.each do |trader| %>
    <div>第<br><%= trader[:index] %><br>位</div>
    <% if trader[:trader].profile_image.attached? %>
        <%= image_tag(trader[:trader].get_profile_image(100,100)) %>
    <% else %>
        <%= image_tag 'leader_sagyouin_woman.jpg', size: "100x100" %>
    <% end %>
    <%= trader[:trader].name %>
    <%= trader[:trader].email %>
    <%= trader[:trader].telephone_number %>
    <%= trader[:trader].genre.name %>
    <div>評価平均：<%= trader[:trader].review_average %></div>
<% end %>
<%= paginate @traders %>
```
#### 主な変更点
:::note info
・変数(`trader`)の隣に配列(`[:index]` or `[:trader]`)を入れる
:::

##### なぜ配列を記述しないといけないのか？

まずeach文で使っている **@traders**には画像の`traders(ローカル変数)`と同じポジションになります。
※コントローラ内で代入しているため

それを踏まえて次の画像を見てみましょう。

tradersという **大枠の中**に配列①と配列②の **中枠が二つ**あります。

この中枠が２種類存在することで、どちらの枠（配列）なのかを宣言しないといけないからです

![mapメソッド.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2822721/0f85b380-64c2-3c19-9007-836b9e3eccd8.jpeg)


以上で、やることは終わりました。
bootstrapの書き方などを整えると次の様なサイトが出来上がりました。

![top画面２－2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2822721/53875914-6b46-1cc5-840b-b719c7e791e2.png)
![top画面２－3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2822721/b31b465e-658f-65f2-c19d-28e521bf0cb7.png)
![top画面２－4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2822721/239b170e-86dd-1f29-58a3-56b3c91776be.png)

ページネーションで飛んだとしても順位は引き継がれているので、ランキング機能の実装が完了しました。

<h2>さいごに</h2>

今回PF作成のテーマとしてレビューサイト（＋ランキング機能）を作ってみました。
まだ学習中の身なので理解不足・誤りがあったらコメント頂けますと幸いです。m(__)m<ﾖﾛｼｸｵﾈｶﾞｲｼﾏｽ