2025/5/26
RailsではSTI(単一テーブル継承)がサポートされている。サポートされているからには適切な場面では積極的に使っていきたい。しかしSTIは開発現場では比較的忌避されている傾向にあるように感じる。
忌避されているように感じるのは、おそらくSTIを採用した結果失敗した経験があるからだろう。自分もサービスクラスに同じような感覚を抱いている。ここではSTIでのアンチパターンであろうものと、その解決策をいくつか書いておく。
そもそも特定の仮装テーブルでしか使わないようなnullableなカラムが発生するのであればSTIを採用すべきではない。RailsでSTIを使うよくあるパターンとしては、紐づくテーブルがtypeによって変わるみたいなものだろうか。
erDiagram
animals {
bigint id PK
bigint dog_category_id FK
bigint cat_category_id FK
string type
string name
}
dog_categories {
bigint id PK
string name
}
cat_categories {
bigint id PK
string name
}
dog_categories |o--o{ animals: is
cat_categories |o--o{ animals: is
class Animal < ApplicationRecord
belongs_to :dog_category, optional: true
belongs_to :cat_category, optional: true
end
class Dog < Animal
class << self
alias category dog_category
end
end
class Cat < Animal
class << self
alias category cat_category
end
end
これだと、dog_category_id
とcat_category_id
カラムはnullableになってしまう。
erDiagram
animals {
bigint id PK
string type
string name
}
animal_dog_categories {
bigint id PK
bigint animal_id FK
bigint dog_category_id FK
}
animal_cat_categories {
bigint id PK
bigint animal_id FK
bigint cat_category_id FK
}
dog_categories {
bigint id PK
string name
}
cat_categories {
bigint id PK
string name
}
animals ||--o| animal_dog_categories: has
animals ||--o| animal_cat_categories: has
animal_dog_categories |o--|| dog_categories: has
animal_cat_categories |o--|| cat_categories: has
class Animal < ApplicationRecord
end
class Dog < Animal
has_one: animal_dog_category
has_one: category, through: :animal_dog_category,
class_name: 'DogCategory'
end
class Cat < Animal
has_one: animal_cat_category
has_one: category, through: :animal_cat_category,
class_name: 'CatCategory'
end
このようにCTI(クラステーブル継承)と組み合わせることで、同じようにcategory
でDogCategory
やCatCategory
にアクセスしつつ、nullableなカラムは発生しない。