ActiveRecordの order
メソッドを使ったソート機能で、予想した動きをしなかったので調べてみました。
追記(6/15)
正しい結論が判明したので修正しました
環境
Ruby: 2.6.3
Rails: 5.2.3
前準備
簡単なpostsテーブルを想定してみます。
postsテーブル
id | title |
---|---|
1 | 最初の投稿 |
2 | 2番目の投稿 |
3 | 3番目の投稿 |
コントローラのindexアクションで、idの降順(値が大きい順番)にします。
def index
@posts= Post.order(id: :desc)
end
これにより、postsテーブルの中身がidの降順で表示されます。
疑問1
ここで、以下の要件が付与されました。
- 通常はidの降順で表示させるが、titleの昇順でソートさせるボタンもつけること
そこで以下のようにしました。
<%= link_to 'Titleでソート', posts_path(title_sort: :true) %>
def index
@posts = Post.order(id: :desc)
if params[:title_sort]
@posts = @posts.order(title: :asc)
end
end
アプリを起動してTitleでソート
をクリックしましたが、タイトルの昇順でソートされない!なんで???
検証&&仮説
そこでTitleでソート
をクリックしたときのSQLをログで確認しました。
SQLを見ると
①まずidの降順(DESC)でソート
②続いてtitleの昇順(ASC)でソート
をしています。
一見、問題なさそうなのですが、ORDER BY句で複数の条件を指定した場合、①を実行し、同じものがあった場合に②でさらにソートします。しかし①はidであり同じものが存在しませんので、①でソートした結果をくつがえして②の結果を反映させることはできないのです。
解決方法
ですので、これを解決するには以下のようにします。
def index
if params[:title_sort]
@posts = Post.order(title: :asc)
else
@posts = Post.order(id: :desc)
end
end
title_sortのパラメータが存在する場合のみタイトルでソートし、それ以外はidでのソートにするのです。
これでtitleの昇順にソートされました!
疑問2
ただ、ここで疑問が。
①のソートをしているのは、コントローラの
@posts = Post.order(id: :desc)
の場所であり、②のソートをしているのは、コントローラの
@posts = @posts.order(title: :asc)
です。それぞれ独立したコードなのに、なぜ①と②をまとめたSQLで実行しているのでしょう?
(てか、まとめて実行しなきゃ、タイトルでもソートできるんじゃない?)
検証&&仮説
SQLの実行タイミングに原因があるのではないか、ということで、以下のようなことを試しました。
def index
@posts = Post.order(id: :desc)
if params[:title_sort]
@posts = Post.order(title: :asc)
end
binding.pry #これを追記
end
これで実際にタイトルでのソートを実行したところ、コンソールが起動した画面は以下のとおりでした。
あれ?
orderメソッドがある7行目と9行目が終わっても、SQLが発行されてないじゃん!
では思い切って、viewsファイルにこんなコードを追加しました。
// 省略
<tbody>
<% puts "今からeachメソッドを実行します" %> # これを追記
<% @posts.each do |post| %>
<tr>
<td><%= post.id %></td>
<td><%= post.title %></td>
<td><%= link_to 'Show', post %></td>
<td><%= link_to 'Edit', edit_post_path(post) %></td>
<td><%= link_to 'Titleでソート', posts_path(title_sort: :true) %></td>
<td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
// 省略
つまり、eachメソッドが実行されるタイミングがわかるようにしたのです。
これでタイトルでのソートを実行した時のログがこれ。
なんと!
eachメソッドが実行されるタイミングで、ようやくSQLが実行されておりました!
結論
order
や where
などは複数つなげてまとめて実行できる性質をもちます。まとめて実行ではなく、コードごとに毎回実行してしまうとDBとのやりとりが増えてしまいます。
そこでRailsでは、SQLをコントローラでは あえて 実行せずに、実際に必要になったタイミング(今回で言えばeachメソッドのタイミング)で まとめて 実行している、というのが今回の理屈でした(Railsかちこい)。
とても勉強になりました!