polymorphic ポリモーフィック関連

使用可能な関連付け

  • belongs_to

対となる関連に指定するオプション

概要

例えば、BookのauthorとしてPerson(個人)とGroup(団体)のいずれかを指定できるようにしたい場合に利用できるのがポリモフィック関連である。

book = Book.new
book.author = Person.last
book.author.class # => "Person"
book.author = Group.last
book.author.class # => "Group"

定義

マイグレーションファイルを以下のように記述する。

class CreateBooks < ActiveRecord::Migration[8.0]
  def change
    create_table :books do |t|
      t.references :author, null: false, polymorphic: true

      t.timestamps
    end
  end
end

referencesのカラムにpolymorphic: trueを指定することで、当該カラムがポリモフィック関連のカラムになる。外部キー制約(foreign_key: true)との併用できない。この点については後述する。

モデルの定義は以下のようにする。

class Book < ApplicationRecord
  belongs_to :author, polymorphic: true
end

これで、「概要」に書いたような例を利用できるようになる。

外部キー制約

外部キー制約(foreign_key: true)との併用できない。これはauthor_idauthor_typeの2カラムで複数の関連先と繋ぐ仕組みになっているため。

Person.create # => id: 1
Group.create # => id: 1
Group.create # => id: 2
Book.create(author: Person.find(1)) # => author_id: 1, author_type: "Person"
Book.create(author: Group.find(2)) # => author_id: 2, author_type: "Group"

ただし、belongs_toのデフォルトの挙動で、関連先がない場合はバリデーションエラーとなる。

# Person.find(100000) がActiveRecord::RecordNotFoundならバリデーションエラー
Book.create(author_id: 100000, author_type 'Person')

しかし、以下のような使い方をした場合はバリデーションでチェックされないので注意する必要がある。

person = Person.last
book = Book.new(author: person)
Person.last.destory # => 関連先レコードを削除
book.save # => true

この挙動はvalidates :author, presence: trueを書いても止められないため、確実にチェックしたいのであればvalidateを追加する必要がある。

validate do
  unless author_type.constantize.exists?(author_id)
    errors.add(:author, :blank)
  end
end