[Sorbet][Rails] tapiocaだけだときついって話
らくしふ を運営している 株式会社クロスビット の hiko1129 です。
この記事は Xbit Advent Calendar 2022 の15日目の記事です。
現在(2024年2月)において
sorbet-railsはアーカイブされているので今後はtapiocaのみでやっていきましょう。
この記事で書いてた問題へのtapiocaでの解法はこちら
前提知識
Ruby の sorbet, tapioca gem を知っている。
概要
tapioca のみの使用だと ActiveRecord 周りの型が大変なので、sorbet-rails gemも使いましょうというお話です。
tapiocaだけを使用するとどうなるか
sorbet の公式ドキュメントには Gemfile に追加するものとして下記が挙げられていますが、 Rails プロジェクトで下記のみを追加したとしてもすぐに壁にぶち当たることになります。
gem 'sorbet', :group => :development
gem 'sorbet-runtime'
gem 'tapioca', require: false, :group => :development
壁とはModel::ActiveRecord_Relation
, Model::ActiveRecord_AssociationRelation
, Model::ActiveRecord_Associations_CollectionProxy
のことです。
Model::ActiveRecord_Relation
, Model::ActiveRecord_AssociationRelation
, Model::ActiveRecord_Associations_CollectionProxy
は private です。つまり当然のことながら(こじ開けない限りは)外部から参照不可能です。
tapioca は Model::ActiveRecord_Relation
, Model::ActiveRecord_AssociationRelation
, Model::ActiveRecord_Associations_CollectionProxy
の実装が private であることに従い、Model::PrivateRelation
, Model::PrivateAssocationRelation
, Model::PrivateCollectionProxy
のような型を生成して型上はその名前のクラスを返します。
まとめると、
- 実装は private である
- tapioca の生成した型上のクラスは実装上存在しないもの
↑の2点が何を示すかというと、Model::ActiveRecord_Relation
, Model::ActiveRecord_AssociationRelation
, Model::ActiveRecord_Associations_CollectionProxy
, Model::PrivateRelation
, Model::PrivateAssocationRelation
, Model::PrivateCollectionProxy
を使用した型定義は(通常)できないということです。
* 常にできないわけではないので通常という記載にしています
ものすごく雑な例(かつ上記の通りの話なの)ですが
↓は書けませんし、
class XXX < ApplicationRecord
class << self
extend T::Sig
sig { returns(XXX::ActiveRecord_Relation) }
def hoge
all
end
end
end
↓も書けません(正しくは書けはするけど実行時に参照先のクラスがなくてエラー
class XXX < ApplicationRecord
class << self
extend T::Sig
sig { returns(XXX::PrivateRelation) }
def hoge
all
end
end
end
「ほな、どないすんねん。Model周りの型定義できへんのかい。」ってなるかと思います。
実際何もしないと上記に挙げた類のものはほぼできないという認識です。
じゃあどうするか
ではどうするのというと以下2点の対応が必要です(一例です。
- sorbet-rails を導入
- tapioca での model の型定義生成の廃止
sorbet-railsというgemは、Model::ActiveRecord_Relation
, Model::ActiveRecord_AssociationRelation
, Model::ActiveRecord_Associations_CollectionProxy
を public_constant に変更して、tapioca のように model 等の型を生成してくれます(他にも色々ありますが説明割愛します。
sorbet-rails を導入して bundle exec rake rails_rbi:models
を実行するようにすることで↓が書けるようになります。
class XXX < ApplicationRecord
class << self
extend T::Sig
sig { returns(XXX::ActiveRecord_Relation) }
def hoge
all
end
end
end
sorbet-rails 導入により tapioca の生成する model の型定義は不要になるので、dsl 生成の対象から除外します。
sotbet/tapioca/config.yml
dsl:
exclude:
- Tapioca::Dsl::Compilers::ActiveRecordRelations
↑の対応でとりあえず解決です🎉🎉🎉
最後に
株式会社クロスビットでは、デスクレスワーカーのためのHR管理プラットフォームを開発しています。 一緒に開発を行ってくれる各ポジションのエンジニアを募集中です。
参考資料
Discussion