👯

【Rails】いいねが多い順に並び替え

2023/07/08に公開

本の投稿サイトの投稿一覧ページに、過去一週間でいいねの合計カウントが多い順に投稿を表示させます!

実装にあたり、以下記事も参考にしてくださいね。

では実装していきます。

Model追記

Bookモデルに以下を記載します。

app/models/book.rb
has_many :week_favorites, -> { where(created_at: 1.week.ago.beginning_of_day..Time.current.end_of_day) }

解説:
Bookモデルに対してweek_favoreitesという関連付けを追加します。
この関連付けをすることで、1週間以内のいいねを取得します。

  • has_many
    BookモデルとFavoriteモデル間に1対多の関係にします。

  • :week_favorites
    関連付けの名前を定義します。

  • -> { ... }
    ラムダ(Lambda)または無名関数を表す構文です。関連付けに特定の条件を指定することができます。関連するデータを絞り込んだり、条件を満たすデータのみを取得するために使います。

  • where(created_at: 1.week.ago.beginning_of_day..Time.current.end_of_day)
    関連するデータを絞り込むための条件です。created_at カラムが指定した期間に該当するデータのみを取得します。
    1.week.ago.beginning_of_day は、1週間前の開始日時を表し、Time.current.end_of_day は現在の終了日時を表します。

コントローラ修正

indexを以下のように修正します。
ここでは二通り紹介します。

  1. sort_byを使った実装
app/controllers/books_controller.rb
def index
  to = Time.current.at_end_of_day
  from = (to - 6.day).at_beginning_of_day
  @books = Book.includes(:favorites).sort_by { |book| -book.favorites.where(created_at: from...to).count }
  
  @book = Book.new
end

解説:

  • to = Time.current.at_end_of_day
    現在の日時を基準にして期間の終了日を設定します。

  • from = (to - 6.day).at_beginning_of_day
    終了日から6日前の日時を開始日として設定します。これにより、1週間の期間が設定されます。
    例えば、今が7月10日の場合、「to」には7月10日の23時59分59秒が設定されます。「(to - 6.day)」で、6日前の7月4日の23時59分59秒を取得します。「at_beginning_of_day」によって2023年7月4日の0時0分0秒に変換します。

  • @books = Book.includes(:favorites)
    N+1問題を解決するために Book モデルに関連するいいね情報を一括で取得するために使用します。

  • .sort_by { |book| -book.favorites.where(created_at: from...to).count }
    各書籍のいいね数を降順にソートするための処理です。期間内に作成されたいいね情報を取得し、その数でbookをソートしています。

文法解説
  • .sort_by
    要素の並び替えを行うためのメソッド。ブロックを受け取り、ブロック内の式の結果に基づいて要素をソートする。

  • |book|
    ブロック内で使用する変数で、ソートの対象となる要素(book)。

  • book.favorites.where(created_at: from...to)
    特定の期間内に作成されたいいね情報のクエリを実行。book の関連するいいね情報を取得し、その中から指定した期間内のものを抽出しています。

  • .count
    クエリ結果の行数(いいねの数)を返すメソッド。book.favorites.where(created_at: from...to) の結果に対して行数を数えます。

  • -
    マイナス符号を使用することで、降順(大きい順)にすることができる。

  1. sortを使った実装
app/controllers/books_controller.rb
def index
  to  = Time.current.at_end_of_day
  from  = (to - 6.day).at_beginning_of_day
  @books = Book.all.sort {|a,b| 
    b.favorites.where(created_at: from...to).size <=> 
    a.favorites.where(created_at: from...to).size
  }
  @book = Book.new
end

解説:

  • @books = Book.all
    Bookモデルからすべてのbookを取得し、@booksインスタンス変数に格納。

  • sort {|a,b| ... }: sort
    bookをいいねの数でソート。ab はそれぞれ比較対象のbookを表す。

  • b/a.favorites.where(created_at: from...to).size
    b/aのいいねの数を取得。where メソッドで created_at の範囲を指定し、その結果の数を取得する。

  • <=>
    比較演算子で、左辺と右辺の値を比較。この場合、b.favorites.where(created_at: from...to).sizea.favorites.where(created_at: from...to).size のいいねの数を比較する。

sortの働き:
例えば以下のようなデータがあったとします。

book いいね数
book A 5
book B 8
book C 3

いいね数が大きい順にbookを並び替えます。
sortを使うと次の処理がされます。

初めに、a にはbook A、b にはbook Bが割り当てられ、以下の比較が行われます。

5 (book Aのいいね数) <=> 8 (book Bのいいね数)

この比較結果はマイナス(-1)です。a(book A) のいいね数が b(book B)のいいね数よりも少ないため、順序は変わりません。

a <=> b の計算結果は、以下のルールに基づいて決定します。
左辺の値が右辺の値よりも小さい場合、比較結果はマイナスの値(-1)。
左辺の値が右辺の値と等しい場合、比較結果はゼロ(0)。
左辺の値が右辺の値よりも大きい場合、比較結果はプラスの値(+1)。

次に、a にはbook B、bにはbook Cが割り当てられ、以下の比較が行われます。

8 (book Bのいいね数) <=> 3 (book Cのいいね数)

この比較結果はプラス(+1)です。a(book B)のいいね数が b(book C)のいいね数よりも多いため、順序が逆転します。

最終的に、bookを大きい順に並び替えると、以下の通りです。

book B > book A > book C

このように、ab を使って比較することで、いいね数の多い順に並び替えることができます。

完成

  • Before

  • After

参考

並び替えはいろいろな書き方ができそう。
以下のような書き方もあるようなので参考までに。
いいねをしたユーザーを経由してbookをソートする、というやり方です。
https://qiita.com/ooyama-tetu/items/1e19fea32908a6a737f5

続きはこちら。
https://zenn.dev/ganmo3/articles/8361e3ddc8bc86

Discussion