STIとポリモーフィックの違い
|
ポリモーフィック |
STI |
|
関連テーブルが複数ある |
複数モデルで1つのテーブルを共有する |
|
自分と紐づく親が複数 |
基幹モデルを継承したモデルを複数持つ |
|
アソシエーションを司る |
アソシエーションと直接的な関係はない |
管理するカラム |
hogeable_type で関連を管理する。 |
type で継承するモデルを管理する。 |
メリット |
正規化できる |
カラムを共有できる |
デメリット |
テーブルが増えるのでjoinが面倒になる |
共通ではないカラムがあるとnullが入る |
STI
1
2
3
4
5
6
7
|
class Vehicle < ApplicationRecord
# データはvehiclesテーブル
end
class Car < Vehicle
end
class Train < Vehicle
end
|
ポリモーフィック
1
2
3
4
5
6
7
8
9
10
11
|
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
|
Picture
と、Employee
, Product
の間にImageable
クラスが(仮想的に)あり、それが「あなたと関連付けられているのはこっちのテーブルですよ〜」と振り分けてくれているイメージ。
Active Record の関連付け - Railsガイド
STIとアソシエーション
VehicleからCarとTrainを分岐させる。そこからそれぞれが共通しない別の関連モデルを持つようにする。
ただし、その関連モデルから見たときは、あくまで同じVehicle
として扱いたい。
(ちょっと例は悪いかもしれない)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
class Vehicle < ApplicationRecord
# このアソシエーション自体に意味はない。共通するアソシエーションはここに書けばいいということ。
belongs_to :company
has_one :driver
end
class Car < Vehicle
has_many :parkings, foreign_key: 'vehicle_id' # ……①
end
class Train < Vehicle
has_many :terminals, foreign_key: 'vehicle_id'
end
class Parking < ApplicationRecord
belongs_to :vehicle # ……②
end
class Terminal < ApplicationRecord
belongs_to :vehicle
end
|
①foreign_key
を指定しないと、parkingsテーブルのcar_id
を探そうとしてエラーになる。
②class_name
を指定しなくても、勝手にtype
カラムから推測してCar
クラスを返してくれる。
1
2
|
Parking.first.vehicle.class
=> Car
|
ついでに
1
2
3
4
5
6
7
|
class Car < Vehicle
has_many :stops, class_name: 'Parking', foreign_key: 'vehicle_id', inverse_of: 'vehicle'
end
class Train < Vehicle
has_many :stops, class_name: 'Terminal', foreign_key: 'vehicle_id', inverse_of: 'vehicle'
end
|
こんな風にしたら、Car, Trainからの扱いも同じようにできる。
RuboCopの Rails/InverseOf について調べた - sometimes I laugh
1
2
3
4
|
Car.first.stops
=> [#<Parking:0x00007fac650e6078>, #<Parking:0x00007fac5634e608>]
Train.first.stops
=> [#<Terminal:0x00007fac553be4b8>, #<Terminal:0x00007fac536cd6b0>]
|
こんな感じ。
とはいえ、私がこうすればいいんじゃね?と思っただけで、多分Parking
とTerminal
は共通していないカラム・メソッドも持つだろうから、共通とそうでないものの境目がわかりづらくなるデメリットはある。と思った。