はじめに
この記事はMastodonにRBSを導入したときの導入背景や手順などをまとめた記事になります。
主に僕個人の備忘録としてまとめています。
導入背景
2017年5月から6年ほどCreatodonというMastodonのサーバーを個人的に運営しています。
直近で開発環境周りの体験向上としてCIの修正とかE2Eテストの追加とか色々やっていたんですが、型関係はまだ手を入れていませんでした。
また独自機能の実装やそのメンテナンスなども行っており、既存のModelやServiceなどの型情報が簡単に確認できる状況が欲しくもなっていました。
そこで
TypeProfを使い、既存のModelやServiceなどのRBSを自動生成- 既存のライブラリの型情報などは
gem_rbs_collectionを使って導入 Steepで生成したRBSの型情報をVS Codeで表示&型情報のチェック
までをMastodonに導入してみました
やったこと
TypeProfを使い、既存のModelやServiceなどのRBSを自動生成
まずはMastodonにある既存のModelやService、Contorollerなどの型をTypeProfを使って自動生成しました。
コードとしては以下のような型の自動生成用タスクを追加して対応しました。
# frozen_string_literal: true require 'fileutils' top_level = self using(Module.new do refine(top_level.singleton_class) do def generate_type_signature(dir_name) files = Dir.glob("app/#{dir_name}/**/*") files.each do |file| next unless file.include?('.rb') result = `bundle exec typeprof #{file}` dir_name = File.dirname(file) FileUtils.mkdir_p("sig/#{dir_name}") File.write("sig/#{file}s", result) end end end end) namespace :type do namespace :prof do task models: :environment do generate_type_signature('models') end task controllers: :environment do generate_type_signature('controllers') end task helpers: :environment do generate_type_signature('helpers') end task lib: :environment do generate_type_signature('lib') end task presenters: :environment do generate_type_signature('presenters') end task serializers: :environment do generate_type_signature('serializers') end task services: :environment do generate_type_signature('services') end task validators: :environment do generate_type_signature('validators') end task workers: :environment do generate_type_signature('workers') end end task prof: ['prof:models', 'prof:controllers', 'prof:helpers', 'prof:lib', 'prof:presenters', 'prof:serializers', 'prof:services', 'prof:validators', 'prof:workers'] end
あとは欲しい場所(Modelならbundle exec rake type:prof:modelsのような感じで)の型情報をタスクとして実行すればOKです。
既存のライブラリの型情報などはgem_rbs_collectionを使って導入
次に既存のライブラリの型情報をgem_rbs_collectionを使って導入しました。
こちらのドキュメントを参考に以下のコマンドを実行。
rbs collection init
実行するとrbs_collection.yamlが生成されます。これを以下のように変更しています。
# Download sources
sources:
- type: git
name: ruby/gem_rbs_collection
remote: https://github.com/ruby/gem_rbs_collection.git
revision: main
repo_dir: gems
# You can specify local directories as sources also.
# - type: local
# path: path/to/your/local/repository
# A directory to install the downloaded RBSs
path: .gem_rbs_collection
gems:
# Skip loading rbs gem's RBS.
# It's unnecessary if you don't use rbs as a library.
- name: rbs
ignore: true
- name: steep
ignore: true
- name: ipaddr
変更内容としては後述するSteepでの型チェック時にパスするために必要なライブラリの設定だけ追加してる感じです。
あとは.gem_rbs_collectionを.gitignoreに追加して、以下のコマンドを実行するだけです。
rbs collection install
これで既存のライブラリの型情報が導入できました。
Steepで生成したRBSの型情報をVS Codeで表示
次にSteepを導入し、TypeProfとgem_rbs_collectionで導入した型情報をVS Code上に表示させます。
まずはGemfileにSteepを追加してbundle installを実行。
gem 'steep'
その後bundle exec steep initを実行して、設定ファイルを生成します。
bundle exec steep init
生成されたSteepfileを以下のように変更します。
target :app do check 'app' signature 'sig' end
checkには型情報をチェックしたい場所のディレクトリを指定し、signatureにRBSがあるディレクトリを指定している感じです。
あとは以下のVS CodeのExtensionをインストールし、VS Codeを再起動すればOKです。
再起動すると以下の画像のように型情報が表示されているかと思います。

型チェックがパスするように修正
次にbundle exec steep checkがパスするように修正しました。
やったこととしては
- デフォルトの設定だと大量のエラーが出るので一時的にIgnore
TypeProfで生成された一部のRBSでの型情報が正しくないと表示されたのを修正app/lib配下の一部のコードで重複エラーが返されたので修正
という感じです。
エラーを一時的にIgnoreするのは以下のようにSteepfileを修正しました。
ignores = [ Steep::Diagnostic::Ruby::MethodDefinitionMissing, Steep::Diagnostic::Ruby::UnknownConstant, Steep::Diagnostic::Ruby::NoMethod, Steep::Diagnostic::Ruby::UnexpectedBlockGiven, Steep::Diagnostic::Ruby::IncompatibleAssignment, Steep::Diagnostic::Ruby::UnknownInstanceVariable, Steep::Diagnostic::Ruby::UnexpectedPositionalArgument, Steep::Diagnostic::Ruby::UnresolvedOverloading, Steep::Diagnostic::Ruby::InsufficientPositionalArguments, Steep::Diagnostic::Ruby::UnknownGlobalVariable, Steep::Diagnostic::Ruby::UnexpectedError, Steep::Diagnostic::Ruby::MethodBodyTypeMismatch, Steep::Diagnostic::Ruby::ArgumentTypeMismatch, Steep::Diagnostic::Ruby::UnsupportedSyntax, Steep::Diagnostic::Ruby::UnsupportedSyntax, Steep::Diagnostic::Ruby::UnexpectedSuper, Steep::Diagnostic::Ruby::UnexpectedYield, Steep::Diagnostic::Ruby::BreakTypeMismatch, Steep::Diagnostic::Ruby::RequiredBlockMissing, Steep::Diagnostic::Ruby::ReturnTypeMismatch, Steep::Diagnostic::Ruby::ImplicitBreakValueMismatch, ] target :app do check 'app' signature 'sig' configure_code_diagnostics do |hash| ignores.each do |ignore| hash[ignore] = :information end end end
あとTypeProfで生成されたRBSの型情報が正しくない件については、以下のようなRangeが返ってくると期待されているものでエラーが返ってきました。
def show: -> Range
現状では泣く泣くuntypedに置き換えて、一旦パスするようにしています......。
def show: -> untyped
app/lib配下のコードの重複エラーについては以下のように対応しました。
def current_time: -> Float | ...
今後
おおよその型情報についてはTypeProfとgem_rbs_collectionのおかげでなんとかなっていますが、untypedなままのものも多い状況です。
なので、この辺を少しづつ片付けていきたいですねー。