Rails6 のちょい足しな新機能を試す71（implict_order_column 編）

# はじめに

(多分）Rails 6 に追加された新機能を試す第71段。 今回は、 `implicit_order_column` 編です。
Rails 6 では、 `first` や `last` で使われるソートのカラムを `implicit_order_column` で指定できるようになっています。
これは、id を UUID にしていた場合に、 `first` や `last` が予測できる結果となるようにするためのようです。
（Rails 6.0.0 がリリースされましたが、確認当時は、 Rails 6.0.0.rc2 が最新でした。悪しからず :bow:)

Ruby 2.6.3, Rails 6.0.0.rc2, PostgreSQL 10.7 で確認しました。Rails 6.0.0.rc2 は `gem install rails -v 6.0.0rc2 --prerelease` でインストールできます。

```shell
$ rails --version
Rails 6.0.0.rc2
```

今回は、 User モデルを作成して `rails console` を使って確認します。

# プロジェクトを作る

```shell
rails new rails_sandbox --database postgresql
cd rails_sandbox
```

# User モデルを作る

```shell
bin/rails g model User name
```

# id を UUID にする

id を UUID にするために、マイグレーションのファイルを変更します。

```ruby:db/migrate/20190803221758_create_users.rb
class CreateUsers < ActiveRecord::Migration[6.0]
  def change
    enable_extension 'pgcrypto' unless extension_enabled?('pgcrypto') #この行を追加
    create_table :users, id: :uuid do |t| # id: :uuid オプションを追加
      t.string :name

      t.timestamps
    end
  end
end
```
# seed データを作成する

seed データを作成します。 name の昇順にデータを登録します。

```ruby:db/seeds.rb
User.create(name: 'Andy')
User.create(name: 'Bob')
User.create(name: 'Cindy')
```

# マイグレーションを実行し seed データを登録する

```shell
bin/rails db:create db:migrate db:seed
```

# rails console で確認する

`rails console` で確認します。

`User.first` を実行してみます。

```irb
irb(main):001:0> User.first
  User Load (0.3ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1  [["LIMIT", 1]]
=> #<User id: "5da612ca-f055-4678-9c87-927b0a1a28d2", name: "Cindy", created_at: "2019-08-03 22:24:51", updated_at: "2019-08-03 22:24:51">
```

結果が Cindy になってしまいました。 (結果は必ず Cindy になるとは限りません。)
SQL を確認すると `id` でソートされていることもわかります。

`User.last` を実行してみます。

```irb
irb(main):002:0> User.last
  User Load (0.5ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT $1  [["LIMIT", 1]]
=> #<User id: "fe31d9a5-8de0-4d35-8a6f-183902f2884b", name: "Bob", created_at: "2019-08-03 22:24:51", updated_at: "2019-08-03 22:24:51">
```

結果が Bob になりました。
SQLを確認すると `id` の降順にソートした最初のレコードが検索されているのがわかります。

# implicit_order_column を使う

User モデルを変更して implicit_order_column で `:name` を指定してみます。

```ruby:app/models/user.rb
class User < ApplicationRecord
  self.implicit_order_column = :name
end
```

## rails console で確認する

修正を反映させるため `reload!` します。

```irb
irb(main):003:0> reload!
Reloading...
=> true
```

`User.first` の結果は、 Andy になります。 `name` の昇順でソートされていることがわかります。

```irb
irb(main):004:0> User.first
  User Load (0.2ms)  SELECT "users".* FROM "users" ORDER BY "users"."name" ASC LIMIT $1  [["LIMIT", 1]]
=> #<User id: "cc7ff0b1-4dc3-47f4-a5cd-259a804c9690", name: "Andy", created_at: "2019-08-03 22:24:51", updated_at: "2019-08-03 22:24:51">
```

`User.last` の結果は、 Cindy になります。 `name` の降順でソートされていることがわかります。

```irb
irb(main):005:0> User.last
  User Load (0.6ms)  SELECT "users".* FROM "users" ORDER BY "users"."name" DESC LIMIT $1  [["LIMIT", 1]]
=> #<User id: "5da612ca-f055-4678-9c87-927b0a1a28d2", name: "Cindy", created_at: "2019-08-03 22:24:51", updated_at: "2019-08-03 22:24:51">
```

## 複数カラムの指定はできない

```ruby:app/models/user.rb
  self.implicit_order_column = %i[name created_at]
```

とした場合は、 ActiveRecord::StatementInvalid エラーになりました。
まあ、そりゃそうですよね。 複数形 ( `implicit_order_columns` ） じゃないですもんね。

```irb
irb(main):011:0> User.first
Traceback (most recent call last):
        2: from (irb):11
        1: from (irb):11:in `rescue in irb_binding'
ActiveRecord::StatementInvalid (PG::UndefinedColumn: ERROR:  column users.[:name, :created_at] does not exist)
LINE 1: SELECT "users".* FROM "users" ORDER BY "users"."[:name, :cre...
                                               ^
```

# 試したソース

試したソースは以下にあります。
https://github.com/suketa/rails_sandbox/tree/try071_implicit_order_column


# 参考情報

* [What is new in Rails 6.0](https://bogdanvlviv.com/posts/ruby/rails/what-is-new-in-rails-6_0.html)
* [Make it possible to override the implicit order column](https://github.com/rails/rails/pull/34480)
* [Rails の UUIDプライマリキーを試す](https://qiita.com/HeRo/items/2816e27fb3066db6c4e6)