[Rails]いろんなランキング表示(いいね、コメント、投稿数)

ランキング機能をつけたいけど、どうやっていいかわからない方向けにランキング機能の解説をしていきます。

ランキングと一口にいっても、いいね数のランキングや投稿数ランキングなど様々なランキングが存在します。
今回は基本的なランキング機能の５つの紹介になります。
ではいきましょう！

ランキング機能に関する他の記事も書いてるのでよかったら
[[Rails]月間、週間、日別でランキング機能を実装(Time.current)](https://qiita.com/nakachan1994/items/682ae620bf3fa6687d5c)

[ランキング機能の順位を同率表示する](https://qiita.com/nakachan1994/items/9ecf25fe1835415d9ae2)

#開発環境

> ruby 2.6.3
> Rails 5.2.6

#前提

- 投稿サイトを想定
- ランキングの順位に応じて１位、２位などの順位を表示
- ranksコントローラーのrankアクションに処理を記述
- rank.html.erbでビューを表示

モデル名、変数名等は適宜、ご自身の開発環境に変換してお考えください。

user  ユーザー
post  投稿
like  いいね
comment  コメント

#紹介するランキング

今回解説するランキングは下記５つです。

- 投稿のいいね数ランキング
- 投稿のコメント数ランキング
- 自分の投稿のいいねランキング
- ユーザーの投稿数ランキング
- ユーザーの全投稿に対するいいね数ランキング

#ビュー

postとuserで渡す変数や表示させたいカラムが違うだけなので、先にやっつけておきます。
postのランキングを例にします。

`１位、２位と順位を表示したいので、with_indexを使います。`

```erb:views/ranks/rank.html.erb
<!--配列の1つ目から順に1位、２位と表示される-->
<% post_ranks.each.with_index(1) do |post, i| %>
  第<%= i %>位
:
<!--表示したいカラムをご自由に-->
:
<% end %>
```

こうするだけで、順位が表示されるようになります。

#投稿のいいね数ランキング

まずは投稿のいいね数ランキングを作っていきます。

```controllers/ranks_controller.rb
def rank
  # 投稿のいいね数ランキング
  @post_like_ranks = Post.find(Like.group(:post_id).order('count(post_id) desc').pluck(:post_id))
end
```

長い記述になったので解説していきます。

```controllers/ranks_controller.rb
# まず、これは見たことあるはず。Postモデルから()内のデータを探してくる
Post.find()
# 次にLikeモデルのpost_idが同じものをまとめる
Like.group(:post_id)
# まとめたものをpost_idの多い順に並び替える
order('count(post_id) desc')
# そのままだとLikeモデルで取り出してしまうので、post_idで値を取りだす
pluck(:post_id)

#まとめるとLikeモデルのpost_idが同じものでまとめて
#それをpost_idの多い順に並び替えて、post_idを取り出す
#Postモデルからいいね順に投稿を取得する
```

これで投稿のいいね数ランキングができます。

#投稿のコメント数ランキング

次に投稿のコメント数ランキングを作っていきます。
ここは基本的な考え方は、さきほどと同じなのでサクッといきます。

```controllers/ranks_controller.rb
def rank
  # 投稿のコメント数ランキング
  @post_comment_ranks = Post.find(Comment.group(:post_id).order('count(post_id) desc').pluck(:post_id))
end
```

以上です

#自分の投稿のいいねランキング

次は、マイページとかでですかね、自分の投稿だけのいいね数のランキングを作っていきます。

方法はたくさんあると思うのですが、今までの方法で全部の投稿を出してきて、そこから自分のデータだけ抜き出す方法もあるのですが、２行になったり、コードが複雑化するので、もっと簡単な方法で実装していきます。

```controllers/ranks_controller.rb
def rank
  # 自分の投稿のいいねランキング
  @my_post_like_ranks = current_user.posts.sort { |a, b| b.likes.count <=> a.likes.count }
end
```

今までとは違って簡素なコードになりました。
もはや解説はいらないかもですが、解説します。

```controllers/ranks_controller.rb
# 現在ログインしているユーザーの投稿を全部取得します
current_user.posts
# 取得した投稿をいいねの多い順で並び替えます。
sort { |a, b| b.likes.count <=> a.likes.count }
```

order以外でもsortを使って並び替えが出来ます。

#ユーザーの投稿数ランキング

ここからはユーザーのランキングです。
あまり使う機会がないかもしれませんが、この人は投稿頑張ってます！てきなランキングを表示したいときにユーザーの投稿数ランキングを作っていきます。

```controllers/ranks_controller.rb
def rank
  # ユーザーの投稿数ランキング
  @user_post_ranks = User.where(id: Post.group(:user_id).order('count(user_id) desc').pluck(:user_id))
end
```

基本的な考え方は投稿のランキングと同じですが、今回はfindではなくwhereを使っていきます。
解説します。

```controllers/ranks_controller.rb
#Postモデルのuser_idが同じのまとめる
Post.group(:user_id)
#user_idが多い順に並べる
order('count(user_id) desc')
#user_idで取り出す
pluck(:user_id)
#Userモデルのidが一致するデータを取得する
User.where(id: )
```

今回の場合だと、where()の中で、user_idが投稿数の多い順で並び替わってるので、それがそのまま配列に入ります。

#ユーザーの全投稿に対するいいね数ランキング

最後にユーザーの全投稿に対するいいね数ランキングです。
これは僕自身かなり苦労しましたし、まだまだ改善の余地があるコードとなっていますが、共有させていただきます。

苦労した点としては以下のような場合、いいねされた数ではなく、いいねした数が表示されてしまう点です。

likesテーブル
user_id  いいねしたユーザーのid
post_id  いいねした投稿のid

このような設計の場合likeｓテーブルからuser_idを取り出して処理を書いても、いいねしたユーザーのidしかないため、いいねした順でランキングが並び替えられてしまいます。
色々考えて試したものの、今の知識では配列を使う方法しか実現できませんでした。（ほんとうは配列を使わない方法で実装したい）
もっといい方法知っている方いればアドバイスください。。

```controllers/ranks_controller.rb
def rank
  # ユーザーの全投稿に対するいいね数ランキング
  post_like_count = {}
  User.all.each do |user|
    post_like_count.store(user, Like.where(post_id: Post.where(user_id: user.id).pluck(:id)).count)
  end
  @user_post_like_ranks = post_like_count.sort_by { |_, v| v }.reverse.to_h.keys
end
```

ん〜、かなり複雑になってますので解説していきます。
大体の流れはこうです。
全ユーザーを配列で回して、ユーザーごとの全投稿に対するいいね数をカウントしていきます。
全ユーザーのカウントが済んだら、いいね数順に並び替えていきます。

```controllers/ranks_controller.rb
# このpost_like_countにuserといいね数のカウントの値を入れていきます
post_like_count = {}
# 全ユーザーを取得してeach文で回していきます
User.all.each do |user|
:
end
# ハッシュオブジェクト.store(key, value)がstoreの基本文法
# post_like_countにkeyとvalueを代入をuserの数だけ繰り返す
post_like_count.store(key, value)
# each文のuserが入る
user
# Postモデルのuser_idがuser.idと一致する投稿のいいね数をカウント
Like.where(post_id: Post.where(user_id: user.id).pluck(:id)).count
# post_like_countの中にはuserといいねのカウント数が入っているので、
# ハッシュをキーとして、valueの値(いいねのカウント数)で並べ替える
post_like_count.sort_by { |_, v| v }.reverse.to_h.keys
```

イメージとしては、
配列の1順目でpost_like_countにidが１のユーザーといいね数２が入って、
配列の2順目でpost_like_countにidが2のユーザーといいね数5が入って、
をユーザーの数だけ繰り返して、いいねのカウント順に並び替えています。

これで、ユーザーの全投稿のいいね数でランキングが表示できます。
ただこのコードはuserの数やpostの数が増えれば増えるほどクエリが発行されるので、参照するデータが多くなって、動作が重くなる可能性があります。（N＋１問題）

#モデルに記載

現状コントローラーに全ての記述をしていて、コントローラーがごちゃごちゃしてると思うので、モデルのメソッドの中で処理を記述すると、コントローラーがすっきりするので、余裕のある方は、やってみてください。

#最後に

今回はよくあるランキング表示のデータの取得方法を中心に記事を書いてきました。
~~また後日、応用編をあげさせてもらいます。~~
できました！
[[Rails]月間、週間、日別でランキング機能を実装(Time.current)](https://qiita.com/nakachan1994/items/682ae620bf3fa6687d5c)

[ランキング機能の順位を同率表示する](https://qiita.com/nakachan1994/items/9ecf25fe1835415d9ae2)

最後のランキング表示には本当にかなり時間を使ってしまったので、困ってる方の解決策になれば幸いです。
ぼく自身まだまだ知らないことばかりなので、日々成長していきます。

最後まで見て頂き、ありがとうございました。