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

# はじめに

Rails 6 に追加されそうな新機能を試す第62段。 今回は、 `polymorphic` 編です。
Rails 6 では、 polymorphic な関連のあるモデルで、 `dependent: :nullify` を指定したとき、モデルを削除したときに `xxx_id` だけではなく `xxx_type` も null になるようになりました。

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

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

今回は、User, Company, Language の 3つのモデルを作ります。
Language はプログラミング言語です。
プログラミング言語の開発者は、User の場合もあれば、 Company の場合もあります。


# プロジェクトを作る

```shell
rails new rails6_0_0rc1
cd rails6_0_0rc1
```

# モデルを作る

User モデルを作ります。

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

Company モデルを作ります。

```shell
$ bin/rails g model Company name
```

Language モデルを作ります。 Company や User と紐付けるための developer 属性を追加します。

```shell
$ bin/rails g model Language name developer:references
```

# マイグレーションファイルを編集する

Language のマイグレーションファイルを編集します。
`developer` に `polymorphic: true` と `index: true` をつけます。
`developer` は null 値でも可とします。 (今回は `dependent: nullify` をテストするためです。)


```ruby:db/migrate/20190720051700_create_languages.rb
class CreateLanguages < ActiveRecord::Migration[6.0]
  def change
    create_table :languages do |t|
      t.string :name
      t.references :developer, polymorphic: true, index: true # ここを修正する

      t.timestamps
    end
  end
end
```

# モデルを編集する

User モデルに `has_many` をつけます。 `dependent: nullify` を指定します。

```ruby:app/models/user.rb
class User < ApplicationRecord
  has_many :languages, as: :developer, dependent: :nullify
end
```

Company モデルも同様に修正します。

```ruby:app/models/company.rb
class Company < ApplicationRecord
  has_many :languages, as: :developer, dependent: :nullify
end
```

# Language モデルを修正する

Language モデルを修正します。 `belongs_to` に `polymorphic` オプションを指定します。
`developer` 属性は、  nil 可とするため、 `optional: true` を指定します。


```ruby:app/models/language.rb
class Language < ApplicationRecord
  belongs_to :developer, polymorphic: true, optional: true
end
```

# seed データを作る

seed データを作ります。

```ruby:db/seeds.rb
User.create(
  name: 'Matz',
  languages: Language.create(
    [
      { name: 'Ruby' }
    ]
  )
)

Company.create(
  name: 'Microsoft',
  languages: Language.create(
    [
      { name: 'C#' }
    ]
  )
)
```

# seedデータを登録する

データベースを作って、 seedデータも登録します。

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

# rails console で確認する

今回は、 `rails console` で確認してみましょう。
登録した User を削除します。

```irb
bin/rails c
irb(main):001:0> User.first.destroy
  User Load (0.4ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1  [["LIMIT", 1]]
   (0.2ms)  BEGIN
  Language Update All (0.6ms)  UPDATE "languages" SET "developer_id" = $1, "developer_type" = $2 WHERE "languages"."developer_id" = $3 AND "languages"."developer_type" = $4  [["developer_id", nil], ["developer_type", nil], ["developer_id", 1], ["developer_type", "User"]]
  User Destroy (0.2ms)  DELETE FROM "users" WHERE "users"."id" = $1  [["id", 1]]
   (7.0ms)  COMMIT
=> #<User id: 1, name: "Matz", created_at: "2019-07-20 06:12:26", updated_at: "2019-07-20 06:12:26">
```

languages テーブルのレコードも更新していますね。
実際に検索して確認してみます。

```irb
irb(main):002:0> Language.find_by(name: 'Ruby')
  Language Load (0.6ms)  SELECT "languages".* FROM "languages" WHERE "languages"."name" = $1 LIMIT $2  [["name", "Ruby"], ["LIMIT", 1]]
  => #<Language id: 1, name: "Ruby", developer_type: nil, developer_id: nil, created_at: "2019-07-20 06:12:26", updated_at: "2019-07-20 06:12:26">
```

`developer_type` と `developer_id` が `nil` になっていることに注意してください。

# Rails 5 では

Rails 5 では、users のレコードを削除すると `developer_id` は `nil` に更新されますが、 `developer_type` は `nil` に更新されません。

```irb
irb(main):001:0> User.first.destroy
  User Load (0.3ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1  [["LIMIT", 1]]
   (0.1ms)  BEGIN
  Language Update All (0.5ms)  UPDATE "languages" SET "developer_id" = NULL WHERE "languages"."developer_id" = $1 AND "languages"."developer_type" = $2  [["developer_id", 1], ["developer_type", "User"]]
  User Destroy (0.2ms)  DELETE FROM "users" WHERE "users"."id" = $1  [["id", 1]]
   (6.9ms)  COMMIT
 => #<User id: 1, name: "Matz", created_at: "2019-07-20 07:08:54", updated_at: "2019-07-20 07:08:54">
 ```

 ```irb
 irb(main):002:0> Language.find_by(name: 'Ruby')
   Language Load (0.5ms)  SELECT  "languages".* FROM "languages" WHERE "languages"."name" = $1 LIMIT $2  [["name", "Ruby"], ["LIMIT", 1]]
 => #<Language id: 1, name: "Ruby", developer_type: "User", developer_id: nil, created_at: "2019-07-20 07:08:54", updated_at: "2019-07-20 07:08:54">
```

# 試したソース

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


# 参考情報

* [What is new in Rails 6.0](https://bogdanvlviv.com/posts/ruby/rails/what-is-new-in-rails-6_0.html)
* [Refs #28025 nullify *_type column on polymorphic associations on :nulify polymorphic *_type column on dependent: :nullify strategy](https://github.com/rails/rails/pull/28078)