RBS CollectionをRailsアプリで試してみよう
Leaner 開発チームの黒曜(@kokuyouwind)です。
先週行われたRubyKaigi Takeout 2021に参加しました。今回も興味深いセッションが多かったですが、なかでもThe newsletter of RBS
updatesでは型システムの進化が感じられて面白い内容でした。
発表内で紹介されていた rbs collection を使って既存 Rails アプリケーションへの型検査導入を試してみたので、本記事で手順などをまとめて紹介します。
なお今回の Rubykaigi には自分の他にころちゃん(@corocn)も参加していました。
ころちゃんは debug.gem の話を記事にまとめているので、そちらもぜひご覧ください。
rbs collection について
(この節は The newsletter of RBS updates の発表内容からの抜粋・要約です)
RBS とは
RBS は Ruby のための型記述言語です。 Ruby 3.0 で導入されました。
クラスやモジュールの構造を RBS に書き下すことができ、型検査などの用途に利用できます。
RBS の仕様自体は Ruby に含まれており、標準ライブラリの RBS ファイルが提供されている他、サードパーティーの gem の RBS ファイルを提供する gem_rbs_collection リポジトリも運用されています。
この型定義を利用するツールとして、型検査ツールである Steep や型解析器の TypeProf などがあります。[1]
既存の RBS 依存解決の問題点
RBS 関連のツールを利用する際、これまでは型定義リポジトリである gem_rbs_collection から依存 Gem ごとの RBS を手動で解決する必要がありました。
しかしこの作業には以下のような問題が存在します。
- Gem のバージョンによっては複数リビジョンの gem_rbs_collection を使い分ける必要があり、依存関係解決が大変
- ツールごとに依存解決を記述する設定ファイルやオプションの渡し方が異なるため、それぞれを記述しないといけない
- rbs コマンドでは
rbs -rlogger -r pathname --repo=gem_rbs_collection validate
のようにコマンドラインオプションを渡す - TypeProf も同様に
type -rlogger -r pathname --repo=gem_rbs_collection target.rb
といったコマンドラインオプションを渡す - Steep では設定ファイルである Steepfile に
repo_path
とlibrary
を記述する
- rbs コマンドでは
これらの問題を解決するためのツールが rbs collection です。
rbs collection の機能
rbs collection は RBS ファイルの依存関係を解決するバンドルツールです。
このツールでは Gemfile.lock を用いて依存ライブラリとそのバージョンを解析し、そのバージョンに合わせた RBS ファイルをダウンロードします。
また Steep などのツールで個別に依存解決を設定する代わりに rbs collection の情報を利用することで、依存関係設定を集約できます。
手動での RBS ファイル運用から解放される、非常に便利なツールですね。
rbs collection は Ruby 3.1 にバンドルされる RBS v2 で正式リリースされる予定ですが、最新の RBS を導入することで実験的な機能として利用できます。
Rails アプリの型検査に rbs collection を利用してみる
この節では、既存の Rails アプリケーションに rbs collection を利用して型検査を導入する手順と所感をまとめていきます。
なお前節に書いたとおり rbs collection はまだ実験的な段階なので、実アプリでの利用は Ruby 3.1 での GA を待ったほうが良いでしょう。
型検査関連 Gem の導入
Gemfile
に以下を記述し、rbs collection が含まれたRBSと型検査ツールのSteep、さらに Rails のモデルなどの型情報を生成するためのrbs_railsをインストールします。
group :development, :test do
gem 'rbs', '1.7.0.beta.2'
gem 'rbs_rails', '~> 0.8.2', require: false
gem 'steep', github: 'soutaro/steep'
end
RBS のバージョンは以下のバグ修正が含まれていないと Active Record の RBS ファイルが正しく解決できないため、 1.7.0.beta.2
以上が必要でした。
また steep は以下の Pull Request で rbs collection に対応していますが、こちらは記事執筆時点では rubygems にリリースされていないため、 GitHub の最新を取得するようにしています。
rbs collection の設定
まずは rbs collection を設定していきます。
rbs リポジトリの docs/collection.mdに手順が書かれているため、これに従って設定します。
rbs collection init
コマンドで設定ファイルの雛形を生成します。
また RBS ファイルが置かれるディレクトリは .gitignore
に追加しておきます。
$ rbs collection init
created: rbs_collection.yaml
$ echo /.gem_rbs_collection/ >> .gitignore
次に、生成された rbs_collection.yaml
を編集して、 Gemfile.lock
だけでは解決できない依存 gem の設定を記述します。
ここでは Rails が依存している標準ライブラリを gems
に列挙します。
# Download sources
sources:
- name: ruby/gem_rbs_collection
remote: https://github.com/ruby/gem_rbs_collection.git
revision: main
repo_dir: gems
# A directory to install the downloaded RBSs
path: .gem_rbs_collection
gems:
- name: rbs
ignore: true
- name: pathname
- name: logger
- name: mutex_m
- name: date
- name: monitor
- name: singleton
- name: tsort
- name: time
- name: set
これで準備ができたので、 rbs collection install
コマンドを実行して RBS ファイルをダウンロードできます。
$ rbs collection install
warning: rbs collection is experimental, and the behavior may change until RBS v2.0
Using pathname:0 (/usr/local/bundle/gems/rbs-1.7.0.beta.2/stdlib/pathname/0)
...
It's done! 26 gems' RBSs now installed.
以下のように、 .gem_rbs_collection
以下に RBS ファイルをダウンロードできたことが確認できます。
$ ls .gem_rbs_collection/
actionpack/ actionview/ activejob/ activemodel/ activerecord/ activesupport/ ast/ httpclient/ listen/ nokogiri/ parallel/ rack/ railties/ rainbow/
$ tree .gem_rbs_collection/activerecord/6.1/
.gem_rbs_collection/activerecord/6.1/
├── activerecord-6.1.rbs
├── activerecord-generated.rbs
├── activerecord.rbs
└── patch.rbs
RBS Rails を用いた型情報の生成
続けて、 Rails のモデルなどに RBS ファイルを生成してくれる RBS Rails を設定し、型情報を生成していきます。
rbs_rails リポジトリのREADMEに従って設定しましょう。
rake タスクを定義して rake rbs_rails:all
とすれば、モデルとパスヘルパーの RBS ファイルが生成されます。
require 'rbs_rails/rake_task'
RbsRails::RakeTask.new
$ rake rbs_rails:all
$ tree sig
sig
└── rbs_rails
├── app
│ └── models
│ ├── ...
│ └── user.rbs
├── model_dependencies.rbs
└── path_helpers.rbs
5 directories, 9 files
Steep の設定
rbs collection と RBS Rails の型情報を組み合わせて型検査を行えるよう Steep を設定します。
SteepのREADMEに設定方法が書かれているため、こちらに従っていきます。
まずは steep init
で設定ファイルの雛形を生成します。
$ steep init
Writing Steepfile...
生成された Steepfile
を編集し、 sig
以下の型情報を利用して app
以下の型を検査するよう指定します。
target :app do
signature "sig"
check "app"
end
型検査の実行
それでは、 steep check
で型を検査しましょう。
rbs collection が実験的機能だという警告がたくさん出ますが無視します。
$ steep check
# Type checking files:
warning: rbs collection is experimental, and the behavior may change until RBS v2.0
warning: rbs collection is experimental, and the behavior may change until RBS v2.0
warning: rbs collection is experimental, and the behavior may change until RBS v2.0
warning: rbs collection is experimental, and the behavior may change until RBS v2.0
warning: rbs collection is experimental, and the behavior may change until RBS v2.0
..............................................................................................F...F...............F.F.F...........F.........FFFF....F......
app/controllers/health_controller.rb:4:2: [error] Type `singleton(::Object)` does not have method `skip_before_action`
│ Diagnostic ID: Ruby::NoMethod
│
└ skip_before_action :require_login
~~~~~~~~~~~~~~~~~~
# ...
型エラーがいくつか検出されました。
ここでは skip_before_action
が singleton(::Object)
に定義されていないと怒られていますね。
skip_before_action
は Action Controller の機能ですが、 Action Controller は gem_rbs_collection に RBS ファイルが無いため型情報を取れていないようです。
他にも Action Mailer などライブラリで型情報がないもの、 current_user など独自に生やしたメソッドなどで型エラーが検出されます。
逆に、 rbs collection で型情報が取れている Active Support などや、 RBS Rails で型情報が生成されているモデルなどでは型エラーが検出されませんでした。
symlink 問題が直っていない古いバージョンの RBS などでは ActiveModel の型エラーが多く出ていたため、これらの機能はうまく動いたとみて良いでしょう。
とりあえず、 Rails の型を検査して型エラーを検出できるところまでは確認できましたね。
これらの型エラーを元に、足りない型情報を自分で RBS に記述していくことで正しく型検査が行えるようになるはずです。
感想
rbs collection を実際に試してみて、導入作業や依存性解決の面倒事がほとんどなくなったと感じました。
これが正式リリースされれば、型検査を CI に組み込んでいくのがかなり簡単になるのではないでしょうか。
実際、 RBS v2.0 がリリースされたら今回の作業の続きを行い、型エラーを解消して CI に組み込んでいきたいと画策しています。
参考文献
-
The newsletter of RBS updates
- pocke さんの RubyKaigi での発表スライド
- rbs_collection.yaml の設定などはスライド内のものを参考にした
-
RBS Railsを使ってRailsアプリケーションにSteepを導入する
- rbs_rails と steep の導入手順をまとめた pocke さんの記事
- rbs collection を使わない時代のものなので、それ以外の部分を参考にした
宣伝
Leaner Technologies では Ruby でも型検査したいエンジニアを募集しています!
Discussion