🍬

並び替え(⭐️評価順も)

2024/07/11に公開

さぁ切り替えて記事書いていきますよー

https://zenn.dev/goldsaya/articles/9156b88d7fe458
https://zenn.dev/takeda_kaho/articles/31356401c02f1f
並び替えする!ってなったら速攻開くのがsayaさんの記事です🙌神
(と、自分用に細くメモった自分のうっすい記事。笑)

で、前回はsayaさんの記事で実装したんですが、、、、
ただ今回私のPFでは
『投稿に直接評価する』のではなく
『投稿に対して行うコメントと共に評価⭐️を送る』
という形にしてるんですですね。えぇ。
ってことでだいぶ記載違って死にかけたんで備忘録を。。

記載を加える場所

1.post model
2.posts controller
3.views/post/index
4.views/post/search

1.post model

#並び替え
scope :latest, -> {order(created_at: :desc)}
scope :old, -> {order(created_at: :asc)}

# 平均評価順で並び替えるスコープ
scope :highest_rated, -> {
 left_joins(:comments)
  .group(:id)
  .order(Arel.sql('COALESCE(AVG(comments.star), 0) DESC'))
}

んー分からんすぎぃ笑

解説

まずscopeの書き方は

scope :スコープ名(任意), -> {条件式}

評価順に関しては長いから数行になってるだけ。
つまりhighest_rated はこのスコープの名前で、-> { ... } はこのスコープの定義です。

base
scope :highest_rated(名前), -> {highest_ratedの条件式}
left_joins(:comments)とは???

left_joinsメソッドとは左テーブルのレコードすべてと、結合条件にマッチする右テーブルのレコードを返します。(google参照)
つまりは左(post)テーブルと指定した右テーブル(comment)をくっつけてくれるメソッドと覚える
⭐️これはアソシエーションしてないと実装不可!!!
⭐️引数には、アソシエーションで定義した関連名を渡します。
今回はモデルにてhas_many :commentsって書いてるので( )の中は(comments)って事!!!

.group(:id)は何のために書く?

簡単に言うと集計関数(今回はAVG)の使用に使うから!
AVG(平均値)などの集計関数を使用する場合、その関数がどの範囲で計算されるべきかを明確にするためにグループ化が必要です。
今回は各投稿(post_id)ごとに平均求めるからPostテーブルの中でもidカラムでグループ化!
これは、同じ post_id に関連するコメントの評価をまとめて、その平均を計算するためのもの。
なっるほっど💆‍♀️

細かく解説
.order(Arel.sql('COALESCE(AVG(comments.star), 0) DESC'))

COALESCEは、与えられた引数の中で最初にNULLではない値を返す関数。
つまり、AVG(comments.star)NULLの場合に、代わりに0を返すようにします。これにより、ソート中にNULLが問題になることを防ぎます。
AVG(able名.カラム名)で平均値を計算します。
DESC は降順を意味し、星評価の平均値が高い投稿から順に並び替えます。
Arel.sql('SQL文')がよくわかんない

Active RecordやArelのメソッドチェーンだけでは表現しきれない複雑なSQLを実行したい場合に、Arel.sqlを使って純粋なSQL文を挿入します。
出そうで、、んーまぁ柔軟に任意の式に対応できるって事?かな???(sql)

2.posts controller

posts controller
def index
  if params[:latest]
    @posts = Post.latest.includes(:comments)
  elsif params[:old]
    @posts = Post.old.includes(:comments)
  elsif params[:star_count]
   @posts = Post.highest_rated.includes(:comments)
  else
   @posts = Post.includes(:comments)
  end
end

本当は.page(params[:page]).per(12)が全コードの後ろにくっついてたんですけど省略

params[: ]は[: ]が入力された場合は〜〜〜(1個下のコード)ってこと。
星評価が[:star_count]なのはviewでその名前にしてるからってだけ。(任意)
書き方は全部『model名.scope名.includes(:comments)』

.includes(:comments)は今回みたいに評価がコメントに入ってる場合に記載。投稿自体に評価ついてたらいらない。

3.views/post/index

<%= link_to '新しい順', books_path(latest: "true") %>
|<%= link_to '古い順', books_path(old: "true") %>
|<%= link_to '評価の高い順', books_path(star_count: "true") %>

これで実装できた!!!

レイアウトでドロップダウンメニューに変更🕺

<div class="dropdown">
 <button class="btn btn-outline-dark dropdown-toggle" type="button" id="sortDropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">

</button>
 <div class="dropdown-menu" aria-labelledby="sortDropdown">
  <%= link_to '新しい順', posts_path(latest: true) , class: 'dropdown-item' %>
  <%= link_to '古い順', posts_path(old: true), class: 'dropdown-item' %>
  <%= link_to '評価の高い順', posts_path(star_count: true), class: 'dropdown-item' %>
 </div>
</div>

ここはこういうものって覚えよう。って事でAIに教えてもらいました。

Discussion