ActiveRecordのorderを複数つなげた場合の動きを調べてみた

ActiveRecordの `order` メソッドを使ったソート機能で、予想した動きをしなかったので調べてみました。

**追記（6/15）**
正しい結論が判明したので修正しました

## 環境
Ruby: 2.6.3
Rails: 5.2.3

## 前準備
簡単なpostsテーブルを想定してみます。

`postsテーブル`

|id |title|
|:-:|:-:|
| 1 | 最初の投稿 |
| 2 |2番目の投稿 |
| 3 |3番目の投稿 |

コントローラのindexアクションで、idの降順（値が大きい順番）にします。

```app/controllers/posts_controller.rb
  def index
    @posts= Post.order(id: :desc)
  end
```
これにより、postsテーブルの中身がidの降順で表示されます。
![スクリーンショット 2019-05-21 17.14.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159704/5096b368-4d53-f388-8790-1921328cd328.png)


SQLも以下の通り。
![スクリーンショット 2019-05-21 14.05.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159704/94a053cd-a9b8-d60c-c237-40039c2bf8a0.png)

## 疑問1
ここで、以下の要件が付与されました。
- 通常はidの降順で表示させるが、titleの昇順でソートさせるボタンもつけること

そこで以下のようにしました。

```app/views/posts/index.html.erb
<%= link_to 'Titleでソート', posts_path(title_sort: :true) %>
```


```app/controllers/posts_controller.rb
  def index
    @posts = Post.order(id: :desc)
    if params[:title_sort]
      @posts = @posts.order(title: :asc)
    end
  end
```

アプリを起動して`Titleでソート`をクリックしましたが、タイトルの昇順でソートされない！なんで？？？
![スクリーンショット 2019-05-21 17.16.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159704/54cee1f8-898d-0444-971c-dab2706e29e7.png)


## 検証&&仮説
そこで`Titleでソート`をクリックしたときのSQLをログで確認しました。
![スクリーンショット 2019-05-21 14.47.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159704/5b40601a-b516-04f8-5561-e4be8157a651.png)

SQLを見ると
①まずidの降順（DESC）でソート
②続いてtitleの昇順（ASC）でソート
をしています。
一見、問題なさそうなのですが、ORDER BY句で複数の条件を指定した場合、①を実行し、同じものがあった場合に②でさらにソートします。しかし①はidであり同じものが存在しませんので、①でソートした結果をくつがえして②の結果を反映させることはできないのです。

## 解決方法
ですので、これを解決するには以下のようにします。

```app/controllers/posts_controller.rb
  def index
    if params[:title_sort]
      @posts = Post.order(title: :asc)
    else
      @posts = Post.order(id: :desc)
    end
  end
```
title_sortのパラメータが存在する場合のみタイトルでソートし、それ以外はidでのソートにするのです。
これでtitleの昇順にソートされました！
![スクリーンショット 2019-05-21 17.17.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159704/ffb91b85-28e9-3f25-4b05-403d6567bd8f.png)

## 疑問2
ただ、ここで疑問が。
①のソートをしているのは、コントローラの

```
@posts = Post.order(id: :desc)
```
の場所であり、②のソートをしているのは、コントローラの

```
@posts = @posts.order(title: :asc)
```
です。それぞれ独立したコードなのに、なぜ①と②をまとめたSQLで実行しているのでしょう？
（てか、まとめて実行しなきゃ、タイトルでもソートできるんじゃない？）
　
## 検証&&仮説
SQLの実行タイミングに原因があるのではないか、ということで、以下のようなことを試しました。

```app/controllers/posts_controller.rb
  def index
    @posts = Post.order(id: :desc)
    if params[:title_sort]
      @posts = Post.order(title: :asc)
    end
    binding.pry #これを追記
  end
```
これで実際にタイトルでのソートを実行したところ、コンソールが起動した画面は以下のとおりでした。
![スクリーンショット 2019-06-15 14.18.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159704/92c5a61c-63c6-8f01-1da0-f1a3b3e0e4c3.png)
あれ？
orderメソッドがある7行目と9行目が終わっても、SQLが発行されてないじゃん！

では思い切って、viewsファイルにこんなコードを追加しました。

```views/posts/index.html.erb
// 省略
  <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メソッドが実行されるタイミングがわかるようにしたのです。
これでタイトルでのソートを実行した時のログがこれ。
![スクリーンショット 2019-06-15 14.23.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159704/75de8f46-59eb-8608-e567-2888eb1af27f.png)
なんと！
eachメソッドが実行されるタイミングで、ようやくSQLが実行されておりました！

## 結論
`order` や `where` などは複数つなげてまとめて実行できる性質をもちます。まとめて実行ではなく、コードごとに毎回実行してしまうとDBとのやりとりが増えてしまいます。
そこでRailsでは、SQLをコントローラでは **あえて** 実行せずに、実際に必要になったタイミング（今回で言えばeachメソッドのタイミング）で **まとめて** 実行している、というのが今回の理屈でした（Railsかちこい）。

とても勉強になりました！