Railsでレビュー評価に基づくランキング機能の実装方法です。ランキングの作り方は他にも記事が出ていますが、同率(例えば1位が2つ合った場合、次が3位になる)の実装方法の記事はあまり見られなかったので、記事を残したいと思います。
条件
- 投稿(post)の評価(1~5)の平均に応じた降順のランキング
- Userモデル,Postモデル(投稿)が多対多の関係であり、中間テーブルのReview(口コミ)モデルが存在する
- ルーティングは任意
STEP1
任意のコントローラーにアクションを作ります。今回はranking
にしました。アクション内で評価に基づくデータの所得のコードを記入します。また、Reviewモデルの対象カラムはrate
です。取得したデータを@rank
に格納します。
データの取得方法ですが2つあります。
ActiveRecordのメソッドを利用する場合
@ranks=Post.find(Review.group(:post_id).order('avg(rate) desc').pluck(:post_id))
ポイント
Reviewモデルに対して.group(:post_id)
でPostモデルのidごとにグループ化します。平たく言うと、Postモデルのidごとに口コミを集計します。続いて、.order('avg(rate) desc')
で評価を平均にし、降順で並べます。そして、.pluck(:post_id))
でReviewモデルからPostモデルのidを取得し、Postモデルのデータを@rank
に格納します。
Rubyのsortメソッドを利用する場合
@ranks=Post.all.sort {|a,b| b.reviews.average(:rate) <=> a.reviews.average(:rate)}
ポイント
これはPostモデルにhas_many :reviews
を追加した場合に使用できます。Postモデルの各要素の評価の平均値で.sort
メソッドを使い、並べ替えています。
STEP2
ビューファイルを作成し、コードを記述します。同率を実現するために、<% last_rate = 0 %>,<% j = 1 %>
を用意します。last_rateが前回の口コミの評価平均値、jが順位に使うものになります。
そして、last_rateと今回の評価平均値でif文で分岐をしていく形です。どちらも最後に今回の値をlast_rateに格納します。それで.each文でループさせていくことにより、同率を含むランキングが実現できます。
<% last_rate = 0 %>
<% j = 1 %>
<% @ranks.each.with_index(1) do |post,i|%>
<% if post.reviews.average(:rate).to_f.round(1) != last_rate %>
<% j = i %>
<div>
<%=j%> 位<%=post%>
</div>
<% last_rate = post.reviews.average(:rate).to_f.round(1) %>
<% else %>
<div>
<%=j%> 位<%=post%>
</div>
<% last_rate = post.reviews.average(:rate).to_f.round(1) %>
<% end %>
<% end %>
</div>