This page looks best with JavaScript enabled
⚠️

【Rails】STIのアソシエーション

 ·   ·  ☕ 2 分で読めます
✏️

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>]

こんな感じ。
とはいえ、私がこうすればいいんじゃね?と思っただけで、多分ParkingTerminalは共通していないカラム・メソッドも持つだろうから、共通とそうでないものの境目がわかりづらくなるデメリットはある。と思った。

Share on

END
END
@aiandrox

 
目次