🙄

Rails|過去一週間でいいねの合計カウントが多い順に投稿を表示

2023/08/02に公開

開発環境

ruby 3.1.2p20
Rails 6.1.7.4
Cloud9

前提

Userモデル、Bookモデル、Favoriteモデルは作成済み。

step1 モデル間のアソシエーション

Userモデル

user.rb
  has_many :books, dependent: :destroy
  has_many :favorites, dependent: :destroy

Favoriteモデル

favorite.rb
  belongs_to :user
  belongs_to :book

Bookモデル

book.rb
  belongs_to :user
  has_many :favorites, dependent: :destroy
  has_many :favorited_users, through: :favorites, source: :user

ポイントは Bookモデルの has_many :favorited_users, through: :favorites, source: :user です。

この記述のおかげで、favoriteモデルを通じて、userモデルから、favorited_usersを参照できます。具体的には、以下のような流れになっています。

1. Bookモデルは Favoriteモデルと has_manyの関係です。
 つまり、1つの Bookは複数の Favoritesを持つことができます。

2. Favoriteモデルは Userモデルと belongs_toの関係です。
 つまり、1つのFavoriteは 1つのUserに紐づいています。

つまりBookモデルは Favoriteモデルを通じて Userモデルへの間接的な繋がりがあります。
has_many :favorited_users, through: :favorites, source: :userはそれを表しています。

ここで favorites_usesは関連付け名(任意の名前)、
:through オプションで間接的な関連付けを持っているモデル(favorite)を指定、
:source オプションで最終的な関連付け先(user)を指定しています。

このような流れがあるため、今後 book.favorited_usersのように記述すれば、
そのbookにfavoriteしたusersの情報が取得できます。

step2 コントローラの編集

次に、booksコントローラを編集します。

books_controllers.rb
  def index
    # @books = Book.all 元はこの記述
    @book = Book.new
    to = Time.current.at_end_of_day
    from = (to - 6.day).at_beginning_of_day
    @books = Book.includes(:favorited_users).
      sort_by {|x|
        x.favorited_users.includes(:favorites).where(created_at: from...to).size
      }.reverse
  end

@books = Book.all はもう使わないので、コメントアウトします。

まず、

    to = Time.current.at_end_of_day
    from = (to - 6.day).at_beginning_of_day

この部分を解説します。

Time.current
config/application.rbに設定してあるタイムゾーンを元に現在日時を取得します。

.at_end_of_day
時刻を23:59:59に設定します。

.at_begginning_of_day
時刻を0:00:00に設定します。

つまり、

to = Time.current.at_end_of_day は、
本日の23:59:59を toという変数に入れる、という意味です。

from = (to - 6.day).at_beginning_of_day は、
to の 6日前の 0:00を fromという変数に入れる、という意味です。

最後に、

@books = Book.includes(:favorited_users).
      sort_by {|x|
        x.favorited_users.includes(:favorites).where(created_at: from...to).size
      }.reverse

この部分を解説します。

Book.includes(:favorited_users)
Bookモデルのデータを取得しています。
その際同時に、favorited_usersデータも取得しています。

*includesは、データベースから情報を取得する際に、関連するデータも一緒に取得するために使用します。取得したい関連するデータを引数に渡します。関連するデータは、モデルでアソシエーションが記述されている必要があります。

includesは使わなくても処理が可能です。しかし、includesを使うことで処理が軽く高速になりますので、使用を推奨します。

.sort_by {|x| ... }
呼び出し元の配列の中で ...に対応する値を 1つずつ xという変数に代入して、昇順に並び替えるメソッドです。ここでは、Bookの各インスタンスx をx.favorited_users.includes(:favorites).where(created_at: from...to).sizeによって評価しています。

x.favorited_users.includes(:favorites).where(created_at: from...to).size
各Bookインスタンス(x)が持つ favorited_users のうち、favoritesの created_atが fromから toの間にあるものの数を取得します。

.reverse
sort_byメソッドは値を昇順に並び替えます。しかしここでは降順にしたいので、reverseメソッドを使い、順番を並び替えます。

つまり、「過去1週間でいいねが多く付けられた Bookを、いいねの数が多い順に並べる」という動作になります。

sortメソッドを使った方法

今回は sort_by メソッドを使いましたが、sort メソッドでも同様の結果が得られます。
https://zenn.dev/airiin/articles/c3d7b8f6b7dd8f

参照

https://zenn.dev/goldsaya/articles/2351abc914cada
https://qiita.com/ooyama-tetu/items/1e19fea32908a6a737f5
https://zenn.dev/hikaru_q/articles/55a1b0076e817a
https://qiita.com/kodai_0122/items/111457104f83f1fb2259
https://edgeapi.rubyonrails.org/classes/Date.html#method-i-at_end_of_day
https://style.potepan.com/articles/30459.html#sort_by

Discussion