Rails|過去一週間でいいねの合計カウントが多い順に投稿を表示
開発環境
ruby 3.1.2p20
Rails 6.1.7.4
Cloud9
前提
Userモデル、Bookモデル、Favoriteモデルは作成済み。
step1 モデル間のアソシエーション
Userモデル
has_many :books, dependent: :destroy
has_many :favorites, dependent: :destroy
Favoriteモデル
belongs_to :user
belongs_to :book
Bookモデル
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コントローラを編集します。
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 メソッドでも同様の結果が得られます。
参照
Discussion