これはなに?
テーブル設計において、リレーションは大切な要素です。
一般的にはbelongs_to
やhas_many
等で紐づけをします。
モデル内で紐づけるテーブルを指定することでこれを実現することができます。
本記事は、紐づけるテーブルを柔軟に設定できる「ポリモーフィック関連付け」という方法を学んだのでそのアウトプットです。
どんなことができるようになるのか?
結論、ポリモーフィック関連付けを活用するには、テーブル内に2つのカラムが必要となります。
- xx_type: 紐づけるテーブルを指定
- xx_id: 紐づけ先のid
xxは、関連付けるモデル名に基づいて命名されます。
例えば今、以下の3つのテーブルがあるとします。
- 子(関連付けられるテーブル): hoge
- 親(関連付け候補のテーブル): fuga
- 親(関連付け候補のテーブル): piyo
ここでhoge, fuga, piyoテーブルはそれぞれポリモーフィック関連付けされているとします。
hogeテーブルにfoo_type
とfoo_id
というカラムを用意したとき、それぞれのインスタンスにて
- foo_type: fuga とするとfugaテーブルと紐付き、foo_idはfugaテーブルのidを参照します。
- foo_type: piyo とするとpiyoテーブルと紐付き、foo_idはpiyoテーブルのidを参照します。
このとき、@hoge.foo によってfoo_idと一致するidを持つfuga(またはpiyo)のインスタンスを呼び出すことが可能となります。
つまり、子が紐づけられる親を選べるようになるということです。
実装例
ポリモーフィック関連付けをRailsで実現するには、親と子のモデルを以下のように実装します。
子
class Hoge < ApplicationRecord
belongs_to :foo, polymorphic: true
end
ポイントは、以下の2点です。
- 関連付けの命名
- 関連付け名をbelongs_toで指定します。今回はfooとしたので、このモデルにはfoo_typeとfoo_idカラムが存在します。
- polymorphic: trueの指定
- これにより、Hogeモデルが複数のモデルに属することを指定できます。
親
class Fuga < ApplicationRecord
has_one :hoge, as: :foo
end
ポイントは以下の2点です。
- 紐づけモデルの指定
- 通常のリレーションと同様に、has_oneで紐づけを指定します。
- 関連付けの指定
- as: で、関連付け名を指定します。今回はfooを指定しているので、子モデルのfoo_typeにfugaが指定されたら紐づけされます。
実装例
例として、Userが登録するときに、Twitterアカウントで登録するかGitHubアカウントで登録するかで紐づけテーブルを変える場合を考えます。
Userが持つカラム
- id
- name
- account_type
- account_id
Twitterが持つカラム
- id
- twitter_id
- twitter_name
GitHubが持つカラム
- id
- github_id
- github_url
で、それぞれのモデルを作成します。
モデルファイル
# app/models/user.rb
class User < ApplicationRecord
belongs_to :account, polymorphic: true
end
# app/models/twitter.rb
class Twitter < ApplicationRecord
has_one :user, as: :account
end
# app/models/github.rb
class GitHub < ApplicationRecord
has_one :user, as: :account
end
マイグレーションファイル
class AddAccountToUsers < ActiveRecord::Migration[6.0]
def change
add_column :users, :account_type, :string
add_column :users, :account_id, :integer
add_index :users, [:account_type, :account_id]
end
end
使用例
# 新規のTwitterアカウントを作成する
twitter_account = Twitter.create(twitter_id: "12345", twitter_name: "example")
# twitter_accountと紐づくUserを作成
user = User.create(name: "Example User", account: twitter_account)
# このとき、account: twitter_accountによって紐づけられるTwitterモデルのオブジェクトが設定される
# user情報からTwitter名を取得
name = user.account.twitter_name
# => "example"
詳細に知りたい方向け