ポリモーフィック関連付けって何。
ポリモーフィズムとは
Polymorphism、意味は多様性。オブジェクティブ指向の3大要素の一つ。 一言でいうと、オブジェクトの実態を気にせず、メソッドを呼び出し、そのオブジェクトごとに振舞ってもらうことだそう。 例えばサイドスローの投手、アンダースローの投手、オーバースローの投手がいたとして、3人の投げ方を見たいときに、
「サイドスローで投げてください」 「アンダースローで投げてください」 「オーバースローで投げてください」
とそれぞれの振る舞いごとに命令をいうのではなく、3人共に
「投げてくださいください。」
と命令した方が楽。
実装したいコードを日本語で表すと、
定義 投げる
インスタンス 投げる.オーバー
インスタンス 投げる.サイド
インスタンス 投げる.アンダー
オブジェクトごとにいちいちメソッドを定義するのが面倒だから、一度メソッドを定義(今回の場合だと投げる)して、インスタンスを生成した方が楽。
このように、「違うものがある決まった振る舞い/入出力を持つことで、同じように扱えるようにすること」をダックタイピングと呼ぶ。
そして、その「ある決まった振る舞い」「入出力の定義」のことをインターフェースと呼ぶ。 ポリモーフィック関連とは上記のポリモーフィズムの思想の元、モデルを関連づけること。
例えば、RUNTEQとSUBARUがTimesChannelを所有している場合、TimesChannelがSubaruのものなのか、Runteqのものなのかの二通りある。
ポリモーフィックを使わない場合、
Runteq.rb
class Runteq < ApplicationRecord
has_many: times_channels
end
Runteq.rb
class Subaru < ApplicationRecord
has_many: times_channels
end
Runteq.rb
class TimesChannel < ApplicationRecord
belongs_to: Runteq
belongs_to: Subaru
end
上記3つのコードで一応、関連はつけられているが、問題が2つあって、
- TimesChannelの投稿者の情報を取得する場合、投稿者がSubaru(今回はたまたまSubaru)なのか、Runteqなのか確認しなければならない。
- TimesChannelを使う人のモデル(今回はたまたまSubaru)が増えた場合、TimesChannelモデルにカラムを追加して関連づけなければならない。
これらをの問題を解決してくれるのが、ポリモーフィック関連だそう。
使ってみますか。
ポリモーフィック関連を実装してみる
実装は簡単です。
create_post.rb
class CreateTimesChannel < ActiveRecord::Migration[5.1]
def change
create_table :times_channels do |t|
t.string :name
t.references :creatable, polymorphic: true, index: true
t.timestamps
end
end
end
他のモデルと繋げるためのフックとして、reference
を用いて参照するためのキーをカラムに追加します。
そのときにオプションでpolymorphic
をtrueにするだけです。
これでcreatable
を使って関連した投稿者を取得できます。
この時、自動でcreatable_typeってのが追加されます。ここでチャンネル作成者のクラスを判断してるみたいです。
schema.rb
create_table "times_channels", force: :cascade do |t|
t.string "name"
t.string "creatable_type"
t.integer "creatable_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["creatable_type", "creatable_id"], name: "index_times_channels_on_creatable_type_and_creatable_id"
end
あとは、各々のモデルで関連づけるだけ。
class TimesChannels < ApplicationRecord
belongs_to :creatable, polymorphic: true
end
class Subaru < ApplicationRecord
has_many :times_channels, as: :creatable
end
class Runteq < ApplicationRecord
has_many :times_channels, as: :creatable
end
今後TimesChannelモデルと結び付けたいモデルが現れたら、下記コードをモデルの中に追加するだけで、TimesChannelモデルと結びつけることができる。
has_many :times_channels, as: :creatable
つまり、いちいちTimesChannelモデルにbelongs_toというコードを追加しなくて良くなる。
これをなんかそれっぽくいうと、
「既存コードはいじらず」「新しく追加するコード側のインターフェースのみ気を付ければ良い」ようになる。そうです。