はじめに
Railsでnamespace付きモデルクラスを使用してSTI(Single Table Inheritance)を実現する場面に何度か遭遇していますが、そのたびに調べ直してしまっているのでまとめようと思います。
概要
Railsにおいて愚直にnamespaceがついたモデルクラスで、STI(Single Table Inheritence)をしようとすると以下のような挙動になります。
- Read
- 検索条件の
type
カラムにnamespaceを含めた定数名が入る
- 検索条件の
- Write
-
type
カラムにnamespaceを含めた定数名が保存される
-
class Namespaced::Vehicle < ApplicationRecord; end
class Namespaced::Car < Namespaced::Vehicle; end
class Namespaced::Airplane < Namespaced::Vehicle; end
例えば、上記のようなnamespace付きモデル構成でSTIを利用するとします。
この場合、ActiveRecord越しにRead/Writeすると以下のようなクエリが発行されます。
app(dev)> Namespaced::Car.create(name: "lamborghini")
TRANSACTION (0.4ms) BEGIN
Namespaced::Car Create (6.1ms) INSERT INTO `vehicles` (`name`, `type`, `created_at`, `updated_at`) VALUES ('lamborghini', 'Namespaced::Car', '2024-09-17 14:30:48.257055', '2024-09-17 14:30:48.257055')
TRANSACTION (3.9ms) COMMIT
app(dev)> Namespaced::Car.all
Namespaced::Car Load (3.4ms) SELECT `vehicles`.* FROM `vehicles` WHERE `vehicles`.`type` = 'Namespaced::Car' /* loading for pp */ LIMIT 11
大抵の場合、このnamespace付きで扱うのは都合が悪いはずです。
解決策
基底クラスにself.store_full_sti_class = false
を設定することで、namespaceを省略して扱うことができます。
関連するPRは以下です。
実装例
class Namespaced::Vehicle < ApplicationRecord
self.store_full_sti_class = false
end
class Namespaced::Car < Namespaced::Vehicle; end
class Namespaced::Airplane < Namespaced::Vehicle; end
app(dev)> Namespaced::Car.create(name: "lamborghini")
TRANSACTION (0.2ms) BEGIN
Namespaced::Car Create (3.7ms) INSERT INTO `vehicles` (`name`, `type`, `created_at`, `updated_at`) VALUES ('lamborghini', 'Car', '2024-09-17 14:43:39.617122', '2024-09-17 14:43:39.617122')
TRANSACTION (2.0ms) COMMIT
app(dev)> Namespaced::Car.all
Namespaced::Car Load (1.3ms) SELECT `vehicles`.* FROM `vehicles` WHERE `vehicles`.`type` = 'Car' /* loading for pp */ LIMIT 11