はじめに
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
でインストールできます。
$ rails --version
Rails 6.0.0.rc1
今回は、User, Company, Language の 3つのモデルを作ります。
Language はプログラミング言語です。
プログラミング言語の開発者は、User の場合もあれば、 Company の場合もあります。
プロジェクトを作る
rails new rails6_0_0rc1
cd rails6_0_0rc1
モデルを作る
User モデルを作ります。
$ bin/rails g model User name
Company モデルを作ります。
$ bin/rails g model Company name
Language モデルを作ります。 Company や User と紐付けるための developer 属性を追加します。
$ bin/rails g model Language name developer:references
マイグレーションファイルを編集する
Language のマイグレーションファイルを編集します。
developer
に polymorphic: true
と index: true
をつけます。
developer
は null 値でも可とします。 (今回は dependent: nullify
をテストするためです。)
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
を指定します。
class User < ApplicationRecord
has_many :languages, as: :developer, dependent: :nullify
end
Company モデルも同様に修正します。
class Company < ApplicationRecord
has_many :languages, as: :developer, dependent: :nullify
end
Language モデルを修正する
Language モデルを修正します。 belongs_to
に polymorphic
オプションを指定します。
developer
属性は、 nil 可とするため、 optional: true
を指定します。
class Language < ApplicationRecord
belongs_to :developer, polymorphic: true, optional: true
end
seed データを作る
seed データを作ります。
User.create(
name: 'Matz',
languages: Language.create(
[
{ name: 'Ruby' }
]
)
)
Company.create(
name: 'Microsoft',
languages: Language.create(
[
{ name: 'C#' }
]
)
)
seedデータを登録する
データベースを作って、 seedデータも登録します。
$ bin/rails db:create db:migrate db:seed
rails console で確認する
今回は、 rails console
で確認してみましょう。
登録した User を削除します。
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(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(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(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