複数のパラメーターを使ってソートする方法が分からず、めちゃくちゃハマった。
そもそも、なぜ複数のパラメーターを使ってソートしようと思ったかと言うと、ブログ等でよく見る「次の記事へ」「前の記事へ」といったページングを実装した際に、1つのパラメーターでソートしてしまうと、うまく実装できなかったからだ。
また、ページングのための場合分けをきちんと理解できていなかったことも相まって、どハマリしてしまった。
Contents
前提
テーブルのイメージ
id | tweet | date(営業日) | created_at | updated_at |
1 | hoge | 2019-04-14 | 2019-04-14 09:00 | 2019-04-14 09:00 |
2 | hogehoge | 2019-04-15 | 2019-04-15 09:00 | 2019-04-15 09:00 |
ビューのイメージ
1 2 3 4 5 6 7 8 9 10 11 12 |
<% if @tweet.previous.present? %> <%= link_to tweet_path(@tweet.previous), class: "btn previous-btn" do %> <i class="material-icons previous">arrow_back_ios</i> <p class="previous">前のツイートへ</p> <% end %> <% end %> <% if @tweet.next.present? %> <%= link_to tweet_path(@tweet.next), class: "btn next-btn" do %> <p class="next">次のツイートへ</p> <i class="material-icons next">arrow_forward_ios</i> <% end %> <% end %> |
もともと実装していたページングのためのコード(model)
1 2 3 4 5 6 7 |
def previous Tweet.where("date < ?", self.date).order("date DESC").first end def next Tweet.where("date > ?", self.date).order("date ASC").first end |
上記のようなコードで、ページングを実装してしまうと、dateが同じtweetが複数存在した時に、どのtweetが「次」もしくは「前」に表示されるかが不定な状態になってしまう。
それに、「”date > ?” , self.date」としているので、dateが同じtweetが複数あった場合に、その中の1つにしか遷移せず、「次」「前」のリンクを押しても、dateが同じ残りのtweetが見れない状態になってしまった。これを解消するのに、かなり時間がかかった。
ページングのための場合分けの考え方
大前提として、一意のレコードを必ず取得することができるように、ソートの数を増やしました。もともとは「date」だけをソートにしていましたが、これだと「date」が同じtweetが出てきた時に、ソートしても一意にtweetを特定することができません。
なので、今回は「date」「created_at」「id」の3つ(複数)のパラメーターを使ってソートをかけることで、一意のtweetを表現できるようにしました。そして、気になる場合分けの考え方は、下記の写真の通りです。
dateが4月13日で同じtweetが3つあると仮定しています。そして、それぞれのtweetを取得するための条件をレコードの横に書いています。このように紙に書き出していくと、とても分かりやすく頭の中が整理されました。後は、これをコードに落とし込んでいきます。
最終的なコード
モデルのコードを下記のように記述することで、3つ(複数)のパラメーターを使ったソートをかけることができ、結果として上手くページングをすることができました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
def previous Diary.where('date < ? OR date = ? AND created_at < ? OR date = ? AND created_at = ? AND id < ?', self.date, self.date, self.created_at, self.date, self.created_at, self.id). order("date DESC, created_at DESC, id DESC").first end def next Diary.where('date > ? OR date = ? AND created_at > ? OR date = ? AND created_at = ? AND id > ?', self.date, self.date, self.created_at, self.date, self.created_at, self.id). order("date ASC, created_at ASC, id ASC").first end |