Polymorphicについて
STIやったなら、ポリモーフィック(ポリモーフィズム)についてもやらんといかんだろう
ということでポリモーフィックについても調べてみた
知ってることでもちゃんと言語化するのは大切だとSTI書いた時に学んだ
ポリモーフィックとは
- GoFのデザインパターンで言うところの「プログラムは実装に対してではなく、インターフェースに対して行う(Program to an 'interface', not an 'implementation'.)」を実現する方法の一つ
- インターフェースとは「ある決まった振る舞い」「入出力の定義」のこと
- ポリモーフィックについて一言で表現すると「類似したクラスに対するメッセージの送り方を共通にする仕組み」
- 「具体的にどのクラスのインスタンスであるかを気にせずメッセージを送れる仕組み」と言ってもよい
- 要するにメッセージを送る側が楽をする仕組み
プログラムで見てみよう
↑の説明でわかるならこんな記事を読む必要がないので、どういうことなのかを素のRubyで書いてみる
class Animal
def cry
raise NotImplementedError
end
end
class Baby < Animal
def cry
puts "オギャー"
end
end
class Dog < Animal
def cry
puts "ワン"
end
end
# 相手が誰であっても鳴けと指示するトレーナークラスを定義する
# executeメソッドの引数はAnimalクラスまたはサブクラスのインスタンス(であることを想定している)
class Trainer
def self.execute(animal)
puts animal.cry
end
end
これが基本
ではRailsでのポリモーフィックは
Railsのポリモーフィック関連は、テーブルに 〇〇_id
と 〇〇_type
カラムを作成し、モデルで関連を貼るだけで使用することができる
↑のサンプルではポリモーフィックにし辛いので、ユーザーまたは企業が記事を投稿できるようなアプリを考えてみる
まずは記事のテーブルの作成
(usersとかcompaniesは省略)
# 記事なんだからbodyとかないとアレだが無視
create_table "posts", force: :cascade do |t|
t.string "title"
t.string "postable_type"
t.bigint "postable_id"
t.index ["postable_type", "postable_id"], name: "index_posts_on_postable_type_and_postable_id"
end
続いてモデルの作成
class Post < ApplicationRecord
belongs_to :postable, polymorphic: true
end
class User < ApplicationRecord
has_many :posts, as: :postable
end
class Company < ApplicationRecord
has_many :posts, as: :postable
end
これで posts
テーブルには以下のようなレコードが保存されていく
id | postable_id | postable_type | title |
---|---|---|---|
1 | 1 | User | 記事名1 |
2 | 1 | Company | 記事名2 |
3 | 2 | User | 記事名3 |
そして、記事からは post.postable
で User
や Company
のインスタンスが取得できる
ポリモーフィックなので、それぞれに共通のメソッドを定義してあげないといけない
class User < ApplicationRecord
has_many :posts, as: :postable
def author_name
# last_nameとfirst_nameはusersテーブルのカラム
"#{last_name} #{first_name}"
end
end
class Company < ApplicationRecord
has_many :posts, as: :postable
def author_name
# nameはcompaniesテーブルのカラム
name
end
end
post = Post.first
# postableがなんなのか気にする必要ない!
post.postable.author_name
Post
が最初のサンプルにおける Trainer
にあたる
post.postable.XXXX
みたいなコードを書くのであれば、 postable
でアクセスされる可能性のあるモデルでは必ず XXXX
に当たるメソッドを提供しなければならないのがキモ
そして共通のインターフェース(ここでは author_name
)さえ提供されていれば、その中はどんな処理でも構わない(ただし返り値は統一されている必要がある)
もしポリモーフィック関連で case
文とか使ってモデルによって分岐とかしていたら、何かがおかしいことになっている可能性が高いというか間違いなくおかしなことになっている
ポリモーフィック関連を使うのであれば
- ポリモーフィック関連の対象にはtypeを聞いてはいけない
〇〇_type
カラムにクラス名が入るので、STIと同じデメリットがDBに発生する
と言う点については最低限覚えておく必要がある
参考
- オブジェクト指向でなぜつくるのか 第2版
- ポリモーフィックはオブジェクト指向三大要素のうちの1つなので紹介されている。良書。
- そろそろポリモーフィック関連について一言いっとくか
- Railsのポリモーフィック関連とはなんなのか