Railsでモデルを継承する場合、デフォルトではSTI(Single Table Inheritance)を使うことが多いと思います。この時に、同じような振る舞いをするから継承するが、長期的なDBの運用を考えるとテーブルを分けたい場合がしばしばあります。そういった場合のために、MTI(Multi Table Inheritance)というデザインパターンを用いた実装を紹介したいと思います。
また、Scenicを使いサブクラスのテーブルを統合したデータを取得する方法も取り上げます。Scenicについて
ゴール
例として以下のような設計になる実装をしてみます。
モデル | DB |
---|---|
Account | accountsビュー |
CorporateAccount | corporate_accountsテーブル |
PersonalAccount | personal_accountsテーブル |
実装
class Account < ActiveRecord::Base
self.primary_key = :id
def read_only?; true; end
end
class CorporateAccount < Account
self.table_name = 'corporate_accounts'
def read_only?; false; end
end
class PersonalAccount < Account
self.table_name = 'personal_accounts'
def read_only?; false; end
end
Migrationは、CorporateAccount
と PersonalAccount
のみ通常どおりテーブルを定義しておきます。
Accout
はScenicで以下のようなviewを作成します。
SELECT corporate_accounts.id,
corporate_accounts.name,
corporate_accounts.created_at,
corporate_accounts.updated_at,
'CorporateAccount'::text AS type
FROM corporate_accounts
UNION ALL
SELECT personal_accounts.id,
personal_accounts.name,
personal_accounts.created_at,
personal_accounts.updated_at,
'PersonalAccount'::text AS type
FROM personal_accounts
実行
この設計で、CorporateAccount
とPersonalAccount
を通常どおりCRUDします。
Account.all
で統合したレコードを取得することができ、それぞれのActiveRecordオブジェクトはサブクラスとしてインスタンス化されるため、それぞれの振る舞いをそのまま使うことができます。Decoratorで表示を分けることも可能です。
おわりに
今回の例では想定しづらいですが、MTIの目的としてレコード数が多くなったときのパフォーマンス改善も見込めますが、統合レコードを取得するケースは劣化するため個別テーブルで取得するケースを優先したい場合には有効な手段になると思います。
統合レコードの取得の劣化は、PostgreSQLのMaterialized Viewを使う方法もあり、より冗長性の高い手法になります。