Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

はじめに

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 のマイグレーションファイルを編集します。
developerpolymorphic: trueindex: true をつけます。
developer は null 値でも可とします。 (今回は dependent: nullify をテストするためです。)

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 を指定します。

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

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

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

Language モデルを修正する

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

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

seed データを作る

seed データを作ります。

db/seeds.rb
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_typedeveloper_idnil になっていることに注意してください。

Rails 5 では

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

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

参考情報

suketa
Rails初心者なRubyコミッター
ruby-dev
Ruby開発はRuby on Railsをコア技術とし、Web系から基幹系まで多様な開発に携わっています。
https://www.ruby-dev.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away