並べ替え(新着順/古い順/いいね順 等)
投稿の中から特定のキーワードで投稿を絞り込みたい場合は"キーワード検索"や"タグ検索"が便利です。
一方、全ての投稿のから"人気の投稿"、"新しい投稿"、"多くのユーザーがブックマークしている投稿"、"コメントの多い投稿"、等を見たい順番に並べ替える機能を実装することで、他の投稿と比較しながら見ることが可能となります。
並べ替えについてもコードの記述方法は一つではありません。
今回は私が記述した内容を、自身の理解を深めるためにもここにまとめます。
まずは基礎編です。
前提として実装済
Postモデル
postsコントローラ
投稿一覧の並び順(デフォルト)
一覧表示の並び順を指定しない場合、デフォルトとして投稿の古い順に並びます。
投稿一覧画面を開いた時に新着順に表示したい場合は、コントローラへの記述が必要です。
def index
@posts = Post.all
@posts = Post.all.order(created_at: :desc)
上の記述はデフォルトで古い順(投稿日昇順)に並びます。
下の記述は、.order(created_at: :desc)
により、新着順(投稿日降順)に並ぶよう指定しています。ここで昇順、降順以外の並び順を指定することも可能です。
定義としてどれか1つのみ記述します。
並べ替え ~新着順/古い順/いいね順~
モデル
scope :latest, -> { order(created_at: :desc) } #desc = 降順
scope :old, -> { order(created_at: :asc) } #asc = 昇順
scope :most_favorited, -> { includes(:favorited_users)
.sort_by { |x| x.favorited_users.includes(:favorites).size }.reverse }
scope :①, -> { order(②: :③) }
① スコープ名(何順に並べるか)
名称は任意です。
latest
= 新着順
old
= 古い順
most_favorited
= いいね順(多い順)
② カラム名
このカラム名を基準に並べ替えます。
created_at:
= 投稿(作成/登録)日時
updated_at:
= 更新日時
③ 並べ替え方法
desc
= 降順 (大→小 / 古→新)
asc
= 昇順 (小→大 / 新→古)
(いいね順は記述が他と異なるので、下の方で解説を書きます。)
scopeヘルパー
モデル側で共通の {条件式(クエリ処理)}
に名前(上記①のスコープ名)をつけて定義します。そしてその名前を以下のように記述することで、対応するorderメソッドを呼び出すことができる仕組みです。
params[:latest]
で order(created_at: :desc) を呼び出す
params[:old]
で order(created_at: :asc) を呼び出す
コントローラ
def index
if params[:latest]
@posts = Post.latest
elsif params[:old]
@posts = Post.old
elsif params[:most_favorited]
@posts = Post.most_favorited
else
@posts = Post.all
end
end
scopeヘルパーにより、対応する並び順を呼び出し、それぞれの場合の一覧表示並び順を@postsに代入しています。
else の結果が初期(=並べ替えを選択していない時)の並び順です。
ビュー
ビューには、並べ替えを実行するリンク(ボタン)を実装します。
<%= link_to '新着順', posts_path(latest: "true") %>
<%= link_to '古い順', posts_path(old: "true") %>
<%= link_to 'いいね順', posts_path(most_favorited: "true") %>
並べ替えリンクの表示の見た目を変更するには、classの記述を追加します。
<%= link_to ..., class: 'btn btn-sm btn-outline-secondary rounded-pill %>
のように。
ここまでで、一覧画面に並べ替えリンクが表示され、そのリンクをクリックすることで並べ替えが実行されるようになりました。
いいね順について
scope :most_favorited, -> { includes(:favorited_users)
.sort_by { |x| x.favorited_users.includes(:favorites).size }.reverse }
-
includes(:favorited_users)
という記述のfavorited_users
は、投稿にいいねしたユーザーのことです。
Postモデルにhas_many :favorited_users, through: :favorites, source: :user
というアソシエーションの記述があることを確認しましょう。 -
include(: )
は N:1問題 を解消します。
N:1問題とは?
Post.all
というのは、allメソッドにより、投稿された全てのpostデータを取得します。
この時、各postには「誰が投稿したのか」というuser情報が必要となります。
(今回の場合、「誰がいいねしているのか」というuser情報)
例えば投稿されたpostが10個ある場合、
「post1のデータを取得 → Userへ、post1投稿者の情報を取りに行く → 取得してPostへ戻る」
という処理を10回繰り返しています。
これが N:1問題 です。
「N」postの数=N回、user情報を取りに行く
「1」投稿一覧(post.all)を取得する
投稿が100個あれば100回繰り返し、1000個あれば1000回繰り返します。
これは大変...
それを助けてくれるincludes
Post.includes(:favorited_users)
includeにfavorited_users の情報が格納されます。
つまり、「投稿は既に、その投稿に対していいねしているユーザーの情報を含んでいる」ということです。既に情報を含んでいるので、「user情報をN回取得しに行く」という処理が不要となります。
.sort_by { |x| x.favorited_users.includes(:favorites).size }.reverse
このコードは、いいねしたユーザー数によって並べ替えるという記述です。
.reverse
により、降順になります。これを書かないと昇順になります。
過去○日間/○ヶ月間のいいね順という指定
例えば、過去6ヶ月間
def index
to = Time.current.at_end_of_day
from = (to - 6.month).at_beginning_of_day
@posts = Post.most_favorited
end
-
Time.current
現在時刻
currentメソッドは、Rails独自のものです。
config/application.rb ファイル内に記載された config.Time_zone = ‘場所‘ に左右されます。 -
.at_end_of_day
1日の終わり23:59 -
(to - 6.month)
toの6ヶ月前
to - 1.month to - 1.year
from + 6.day from + 1.month from + 1.year -
.at_beginning_of_day
1日の始まり0:00
scope :most_favorited, -> { includes(:favorited_users)
.sort_by { |x| x.favorited_users.includes(:favorites).where(created_at: from...to).size }.reverse }
コントローラに to/from の記述をしたので、その内容をモデルの方にも追加します。
.where(created_at: from...to)
作成日(投稿日)がfrom(=6ヶ月前)からto(=現在)の期間である情報を取得するという記述です。
このように、いいねやコメントの多い順に並べ替えたい時には、該当期間を限定することが可能です。
以上です。
次回、別の記述方法もまとめようと思います。
Discussion
@comugiさん、こんにちは。
most_favorited
というscopeですが、これだとリレーションではなく配列を返すため、scopeの再利用性が低くなります。以下の記事で適切な実装方法について解説したので、よかったら参考にしてみてください。
【Rails】ブログに付いたコメントの件数順にブログを並び替える方法 - Qiita