💡

[Sorbet][Rails] tapiocaだけだときついって話

2022/12/15に公開

らくしふ を運営している 株式会社クロスビット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 のような型を生成して型上はその名前のクラスを返します。

まとめると、

  1. 実装は private である
  2. 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管理プラットフォームを開発しています。 一緒に開発を行ってくれる各ポジションのエンジニアを募集中です。

https://x-bit.co.jp/recruit/

https://herp.careers/v1/xbit/requisition-groups/3c2c9f83-0185-4e82-b0ff-5df823172992

https://note.com/xbit_recruit

参考資料

https://github.com/Shopify/tapioca/blob/main/manual/compiler_activerecordrelations.md

https://github.com/chanzuckerberg/sorbet-rails

https://github.com/Shopify/tapioca

Discussion