【要約】【Rails】給湯器のサンプルアプリで学ぶDelegatedType [Qiita_Trend] | Summary by TechDistill
> Source: Qiita_Trend
Execute Primary Source
// Problem
Rails開発者が、共通属性を持ちつつ詳細属性が異なる複数のモデルを設計する際、DB設計とコードの抽象化のトレードオフに直面する問題がある。具体的には以下の課題が挙げられる。
- ・STI(単一テーブル継承)を採用した場合、全サブクラスのカラムを1つのテーブルに集約するため、特定の型にしか使われないカラムが大量にNULLとして存在し、テーブルが肥大化する。
- ・
has_oneによるコンポジションでは、テーブルは分割されるが、親モデルから抽象的に関連を扱えず、N+1問題の回避やコードの記述が複雑になる。
// Approach
著者は、Rails 6.1で導入された
DelegatedTypeを用いて、DBの正規化とモデルの抽象化を両立するアプローチを提案している。具体的な手法は以下の通りである。- ・親モデルに
delegated_typeを定義し、具体的なサブクラスを抽象的なインターフェースとして扱う。 - ・各サブクラスは、親モデルに対して
has_one :product, as: :heaterのように、ポリモーフィックな関連を定義する。 - ・これにより、
Product.includes(:heater)といった簡潔な記述で、異なるサブクラスを抽象的に一括ロードできる。
// Result
DelegatedTypeの導入により、DB設計の整合性と開発効率の両面で成果が得られる。具体的には以下の改善が見込まれる。- ・DB設計面では、各サブクラスが自身の属性のみを持つ専用テーブルを持つため、不要なカラムを排除し、スキーマをクリーンに保てる。
- ・実装面では、
Product.includes(:heater)のように、具体的な型を意識せずにポリモーフィックな関連を扱えるため、コードが簡潔になり、N+1問題も容易に解決できる。
Senior Engineer Insight
> 実戦におけるスケーラビリティの観点から、本手法は極めて合理的だ。属性の追加が頻繁なドメインにおいて、STIによるテーブル肥大化は運用上の負債となる。
DelegatedTypeはDBの正規化を維持しつつ、Railsの強力な抽象化の恩恵を受けられる。ただし、関連の向きが「親が子を持つ」から「子が親を持つ」へ逆転する点には注意が必要だ。設計ミスを防ぐため、touch: trueやdependent: :destroyを適切に設定し、データの整合性を担保することが運用の鍵となる。