LoginSignup
4
1

単一テーブル継承(STI)について

Last updated at Posted at 2024-05-09

はじめに

チームとしてDB設計力をあげていかないとなぁと思っています。

1:1、1:N、N:Nの関連付けの設計や、正規化がしっかりとできるようになった後は、
単一テーブル継承(STI)について考え始めるタイミングかなと思います。
その時に備えて、ここに記しておこうと思います。

単一テーブル継承(STI)とは

STIを利用する目的は、複数の関連するクラスを単一のテーブルで管理する事により、
DB設計を単純にする事です。

Railsガイドに内容が簡潔に記載されていますが、もう少し噛み砕いて説明しようと思います。

STIを利用しない場合

Car.rb
class Car < ApplicationRecord
end
Motorcycle.rb
class Motorcycle < ApplicationRecord
end
Bicycle.rb
class Bicycle < ApplicationRecord
end

DB設計

car

Field Type
color string
price integer

motorcycle

Field Type
color string
price integer

bicycle

Field Type
color string
price integer

構造が同じで名前だけ異なるテーブルが3つ存在することになります。
共通化してDBテーブル数を減らしたい気持ちになります。

STIを利用する場合

Vehicleモデルは親です。
Carモデル、Motorcycleモデル、Bicycleモデルは子(サブクラス)です。
子(サブクラス)は、親を継承します。

Vehicle.rb
class Vehicle < ApplicationRecord
end
Car.rb
class Car < Vehicle
end
Motorcycle.rb
class Motorcycle < Vehicle
end
Bicycle.rb
class Bicycle < Vehicle
end

DB設計

vehicle

Field Type
color string
price integer
type string

typeに入る値が、'Car'、'Motorcycle'、'Bicycle' に応じて、
RailsのModelが決定される形となります。

また、2通りのレコード作成方法があります。

  • Carクラスでcreateする
Car.create(color: 'Red', price: 10000)
  • typeに'Car'を指定してcreateする
Vehicle.create(color: 'Red', price: 10000, type: 'Car')

この方法により、DBテーブル数を1つに共通化する事ができます。

処理の共通化

各モデルで共通の処理があったとすると、それを親側で定義して共通化できます。

Vehicle.rb
class Vehicle < ApplicationRecord
  # 共通のバリデーション
  validates :color, presence: true
  validates :price, numericality: { greater_than: 0 }

  # 共通のメソッド
  def display_info
    p "#{type}: Color - #{color}, Price - #{price}"
  end
end

例えば、carインスタンスはVehicleを継承しているのでdisplay_infoを呼び出せます。

car.display_info # Car: Color - Red, Price - 10000"

特段、STIでなくても親Classを作成すれば処理の共通化はできるのですが、
STIを利用することで親側に処理を寄せようと感じるきっかけになれば、それはそれで良いかなと思います。

STIの課題

異なるサブクラスが異なる属性を持つ場合、多くのNULL値が発生する可能性があります。

vehicle

Field Type Description
type string 'car', 'motorcycle', 'bicycle'
color string 共通属性
price integer 共通属性
doors integer car専用属性
engine_capacity integer motorcycle専用属性
has_basket boolean bicycle専用属性

この場合、typeが'Car'だと、engine_capacityとhas_basketはNULLになります。
Carにとっては不要な属性という訳です。

STIの使用が適切な場合

サブクラス間で多くの共通属性があり、異なる属性が少しだけ存在する場合にSTIを利用するのが良いと言われています。
また、オブジェクトのタイプ(type)が比較的少ない場合、という条件も必要でしょう。
typeが増えると、その分あるModelに対して不要な属性が肥大化する懸念があるためです。
弊社案件に関しては、そこまでtypeを大量に扱うような要件はなさそうなので、これについてはそこまで気にしなくて良いかなと思います。

最後に

どのテーブルならSTIを利用できるかな〜と考えるきっかけになれば良いなと思います。

因みに、Rails7.1からDelegated Typesという項目が追加されています。

本記事で述べたSTIの課題を解決するために編み出されたようですが、
少し複雑に見えるので、どうなのかな。。
次の記事の内容にするかどうかはもう少し考えよう。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1