ポリモーフィック関連付けとは

業務でポリモーフィック関連付けという言葉が出てきたが知らなかったので調べる。

ドキュメント読む

railsguides.jp

ポリモーフィック関連付け(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

一見問題はないが、今回のケースで EmployeeProduct が同時に1つの Picture に関連付けられることはないため冗長な構造だと言える。なぜならどちらかのカラムは必ず NULL になるから。ならば共通のインターフェースとして imageable_id を設けてあげる。そして EmployeeProduct からは "同質" なものとして扱いましょう。ということだと理解した。

もっと理解できるよい記事

qiita.com

Better than Nothingが深く突き刺さっている

Better than Nothing

ある発表が自分の価値観に深く突き刺さっている。

それは昨年11月のAWS Dev Day 2022 Japanでの以下の発表。

youtu.be

内容を要約すると、イスラエル在住の日本人エンジニアの方が現地での暮らしを引き合いにBetter than Nothing(ないよりマシ)という考え方を繰り返し説くもの。ソフトウェア開発においても完璧を求めすぎるがあまりnothingになるより5%の完成度でもあった方がいいでしょ?ということ。

視聴してから半年は経っているが今も何か尻込みしそうなときに思い出す。心配事や不安といった雑念が道を塞いでいるような状況でこの言葉がブルドーザーのように障害物を掻き分けて現れる。

例えば、経験のない業務にチャレンジしたいとき。「失敗したらいやだな、評価を下げるのがいやだな」といった雑念を押しのけて、「でも失敗という経験もないよりマシか」と思考を上向きにシフトチェンジできる。

Two-way Door

Two-way Doorも好きな言葉だ。

これはAWS社内で浸透しているカルチャーなのだとか。

00:00:17 Amazonイノベーションを支えるメカニズム

youtu.be

双方向のドア、一度入ってもまた内側から開けられるドアのことだが、つまりは「後からやり直しが利く」という意味。反意語はOne-way Doorで、一度入ったら出られない。

毎日大なり小なり決断をしながら生きているが、それがOne-wayなのかTwo-wayなのか考えるようにしている。それがOne-wayであればより慎重に、Two-wayであれば失敗を恐れずに気軽にやってみる。

ひとこと

こういう言葉は意味を説明されると対したことないが、耳馴染みがよくてすぐに思い出せることが重要なのかもしれない。

学問としてのコンピュータサイエンスの範囲

アメリカの職場ではエンジニアの専門性が高いため、ドキュメントを残さなくても問題にならないという主旨の記事を読んだ。

note.com

アメリカにおけるエンジニアはコンピュータサイエンスを学んでいる(記事内では”出ている”という表現だったので学位を取得している)ため、実装のレベルが高く、読んでも分からないようなものは PR 時点で弾かれるとのこと。

記事は日本のソフトウェア業界においても専門性を重んじようと帰着している。

記事を読んで

コンピュータサイエンスを大学で専攻していなかった自分にとっては耳の痛い記事だった。

専攻してこなかったとはいえ大学でしか学べないような学問でもないし、理論なら実験器具も設備も要らないのでこれから体系的に学んでみても遅くないだろう。

しかしながら学問としてのコンピュータサイエンスが指す範囲が分からないと勉強のしようがない。

ということで一般的な大学におけるコンピュータサイエンスのカリキュラムを調べてみた。

学問としてのコンピュータサイエンスの範囲

日本における一般的な大学といえばやはり東京大学だろう。

東京大学理学部情報科学科のカリキュラムが公開されていた。

引用: 講義内容|東京大学 理学部情報科学科/大学院情報理工学系研究科コンピュータ科学専攻

講義の詳細は東京大学授業カタログで調べられる。

理論(緑部)は情報数学やアルゴリズムに始まり、基盤(青部)はアセンブリやディジタル回路といった懐かしい響きが並んでいる。学年が上がるとより具体的な理論や演習が増えていく。

とにかく日本最高峰の情報科学科のカリキュラムなのだからこれが学問としてのコンピュータサイエンスの範囲と捉えてもよいだろう。

冒頭の実装レベルを上げるという意味においては、オペレーティングシステム、計算機構成論、計算量理論、言語モデル論あたりを学んでおくとよいだろうか。