業務でポリモーフィック関連付けという言葉が出てきたが知らなかったので調べる。
ドキュメント読む
ポリモーフィック関連付け(polymorphic association)は、関連付けのやや高度な応用です。ポリモーフィック関連付けを使うと、ある1つのモデルが他の複数のモデルに属していることを、1つの関連付けだけで表現できます。
ポリモーフィックなbelongs_toは、他のあらゆるモデルから利用可能なインターフェイスを設定する宣言と考えてもよいでしょう。
なるほど、分からない。
実際に触ってみる
ドキュメントにあった下記コードを自分の手元でも試してみる。
class Picture < ApplicationRecord belongs_to :imageable, polymorphic: true end class Employee < ApplicationRecord has_many :pictures, as: :imageable end class Product < ApplicationRecord has_many :pictures, as: :imageable end
まずはそれぞれのモデルを作る。
❯ rails g model employee name:string ❯ rails g model product name:string ❯ rails g model picture imageable:references{polymorphic} name:string
生成物。
# app/models class Employee < ApplicationRecord end class Product < ApplicationRecord end class Picture < ApplicationRecord belongs_to :imageable, polymorphic: true end
# db/migrate class CreateEmployees < ActiveRecord::Migration[7.0] def change create_table :employees do |t| t.string :name t.timestamps end end end class CreateProducts < ActiveRecord::Migration[7.0] def change create_table :products do |t| t.string :name t.timestamps end end end class CreatePictures < ActiveRecord::Migration[7.0] def change create_table :pictures do |t| t.references :imageable, polymorphic: true, null: false t.string :name t.timestamps end end end
❯ rails db:migrate
生成されたスキーマは次の通り。
# db/schema.rb create_table "employees", force: :cascade do |t| t.string "name" t.datetime "created_at", null: false t.datetime "updated_at", null: false end create_table "products", force: :cascade do |t| t.string "name" t.datetime "created_at", null: false t.datetime "updated_at", null: false end create_table "pictures", force: :cascade do |t| t.string "imageable_type", null: false t.integer "imageable_id", null: false t.string "name" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["imageable_type", "imageable_id"], name: "index_pictures_on_imageable" end
ドキュメントの通り Employee
モデルと Product
モデルに has_many
関連付けを書き足す。
# app/models class Employee < ApplicationRecord has_many :pictures, as: :imageable end class Product < ApplicationRecord has_many :pictures, as: :imageable end
rails c
で実際にオブジェクトを作りながら試していく。
# Employee > emp = Employee.create => #<Employee:0x00000001142d7ef8 id: 2, name: nil, created_at: Fri, 02 Jun 2023 15:12:56.040521000 UTC +00:00, updated_at: Fri, 02 Jun 2023 15:12:56.040521000 UTC +00:00> > pic1 = Picture.create(imageable: emp) => #<Picture:0x0000000111df3600 id: 2, imageable_type: "Employee", imageable_id: 2, name: nil, created_at: Fri, 02 Jun 2023 15:14:20.147889000 UTC +00:00, updated_at: Fri, 02 Jun 2023 15:14:20.147889000 UTC +00:00> # Product > prd = Product.create => #<Product:0x00000001147f7988 id: 3, name: nil, created_at: Fri, 02 Jun 2023 15:18:33.911347000 UTC +00:00, updated_at: Fri, 02 Jun 2023 15:18:33.911347000 UTC +00:00> > pic2 = Picture.create(imageable: prd) => #<Picture:0x0000000115b551b0 id: 4, imageable_type: "Product", imageable_id: 3, name: nil, created_at: Fri, 02 Jun 2023 15:18:37.119137000 UTC +00:00, updated_at: Fri, 02 Jun 2023 15:18:37.119137000 UTC +00:00>
Picture が持つ imageable_id
に異種モデル (Employee と Product) の id を紐付いている。なんとも不思議な光景だ。これを実現するのが imageable_type
である。pic1 と pic2 を見るとそれぞれのモデル名が入っていることが分かる。これにより異種モデルであっても imageable_id
がどちらのモデルのものかを識別できている。
使い所
モデル A を異種モデル B, C に対して紐づけたい。かつ、B, C モデルにおける A の扱いが対称性を持つときに便利。(うまく説明できない)
上のサンプルで Picture
の所有者は Employee
Product
のどちらかになる。それをポリモーフィック関連付けなしで実現すると、
class Picture < ApplicationRecord belongs_to : employee belongs_to : product end class Employee < ApplicationRecord has_many :pictures end class Product < ApplicationRecord has_many :pictures end
となり、そのテーブル構造は次のようになる。
column |
---|
id |
name |
employee_id |
product_id |
一見問題はないが、今回のケースで Employee
と Product
が同時に1つの Picture
に関連付けられることはないため冗長な構造だと言える。なぜならどちらかのカラムは必ず NULL になるから。ならば共通のインターフェースとして imageable_id
を設けてあげる。そして Employee
と Product
からは "同質" なものとして扱いましょう。ということだと理解した。