It's okay to be weird

Rubyで型を扱うためのRBSの概要とRailsプロジェクトへの痛みのない導入方法

Date: 2023-12-08

この記事の概要

これはSmartHR Advent Calendar 2023シリーズ 1の8日目の記事です。

この記事では、Rubyで型を扱うための基盤であるRBSについて、いくつかのリソースを参照しながら、説明および、既存のRuby on Railsプロジェクトに恩恵だけに預かる形での導入方法を紹介します。

sinsokuさんのRailsアプリと型検査というスライドを見て、そんな形で型定義の導入ができるのか、となったことがきっかけで試してみたことです。ありがとうございます。

RBSとは

RBSを含めたRubyの静的解析機能周りについてはRuby 3の静的解析機能のRBS、TypeProf、Steep、Sorbetの関係についてのノート - クックパッド開発者ブログに短くまとまっています。
これによると、RBSはその言語を主体とした4種類からなる「型を扱うための基盤」と言えるようです。

また、RBS単体については、RBSのコミッターであるPockeさんがRubyWorld Conference 2023で講演したRBS Tutorialに15分でよくまとまっています。

かいつまんでまとめると、以下のような感じです:

この講演では後半でRBSの小さな始め方についても触れられているので、興味のある方はぜひ動画を観てみてください。

実際に既存のRuby on Railsプロジェクトに導入する

RBSが何なのかぼんやり分かったところで、さっそく既存のRuby on Railsプロジェクトに導入してみましょう。

ここではRBSに対応した静的型検査ツールであるSteepを利用して、Visual Studio Codeでの主に補完機能の強化を行うことを目標にします。
Steepは型違反のチェック機能なども持っていますが、ここでは無視します。また、自作コードに型定義を追加することはせず、既存のgem周りの型の定義だけ取得することにします。これらは対応するのにそれなりの工数がかかるであろうためです。

冒頭で挙げたsinsokuさんのスライドと、そちらで紹介されていたRailsプロジェクトへの「頑張らない型導入」のすすめ - メドピア開発者ブログを存分に参考にします。

Steepをセットアップ

まずはSteepをセットアップします。

Steepはこのようなものです(引用):

Steep は、Ruby の静的型検査器です。RBS を使って、伝統的な漸進的型付けによる型検査を行うことができます。 単に型エラーを検出できるだけではなく、LSP を実装しているので、エディタ上での型エラー表示、補完、ドキュメント表示なども実装されています。

Ruby 3の静的解析機能のRBS、TypeProf、Steep、Sorbetの関係についてのノート - クックパッド開発者ブログ より。

Gemfileに以下のように :development グループに追記します:

group :development do
  gem "steep", require: false
end

bundle install を実行します。
bundle exec steep init を実行します。
Steepfile がプロジェクトトップディレクトリに出来るので、以下のように書き換えます:

D = Steep::Diagnostic

target :app do
  signature "sig"
  check "app"

  configure_code_diagnostics(D::Ruby.silent)
end

configure_code_diagnostics(D::Ruby.silent) が重要で、型のエラーチェックをしないようにします。何もしない状態だと既存のコードに対してエラーが溢れるので、それを防止します。 本当はエラーチェックもできると良いのですが(型はそのためにあるとも言えそうです)、今回は痛みなくという方針なので頑張らずに無視します。

rbs_rails gemをセットアップ

rbs_railsとはRuby on Rails用のRBSファイルジェネレーターです。

Gemfileに以下のように :development グループに追記します:

group :development do
  gem 'rbs_rails', require: false
end

bundle install を実行します。
bin/rails g rbs_rails:install を実行します。lib/tasks/rbs.rake が作成されます。
いったんここまでにしておきます。

rbs collectionのセットアップ

rbs collectionはサードパーティgemのRBSを生成してくれる便利なツールです。

上記リポジトリのドキュメントには

In short, it is bundler for RBS.

と書かれていますね。

bundle exec rbs collection init を実行します。
rbs_collection.yaml が作成されます。これは書き換えずにこのまま利用します。

rbs.rakeの調整

bin/rails g rbs_rails:install で作られた rbs.rake をこのように書き換えます:

return unless Rails.env.development?

require "rbs_rails/rake_task"

namespace :rbs do
  task setup: %i(clean collection rbs_rails:all)

  task clean: :environment do
    sh "rm", "-rf", "sig/rbs_rails"
    sh "rm", "-rf", ".gem_rbs_collection"
  end

  task collection: :environment do
    sh "rbs", "collection", "install"
  end

  task validate: :environment do
    sh "rbs", "-Isig", "validate", "--silent"
  end
end

RbsRails::RakeTask.new do |task|
  # If you want to avoid generating RBS for some classes, comment in it.
  # default: nil
  #
  # task.ignore_model_if = -> (klass) { klass == MyClass }

  # If you want to change the rake task namespace, comment in it.
  # default: :rbs_rails
  # task.name = :cool_rbs_rails

  # If you want to change where RBS Rails writes RBSs into, comment in it.
  # default: Rails.root / 'sig/rbs_rails'
  # task.signature_root_dir = Rails.root / 'my_sig/rbs_rails'
end

これでRBSファイル生成の準備ができました!

RBSファイルの生成

さて、ここまででセットアップが終わったので、いよいよRBSファイルの生成を行います。
その前に、.gitignore に無視対象になるRBSファイルなどを追加しておきます。
RBSファイルやrbs collectionのロックファイルの差分がメンバー間で出てしまうことを防ぐと同時に、型情報なんて必要ないという人は生成セットアップをせずに済む優しい作りにしておきます。

これらを .gitignore に追加します:

.gem_rbs_collection
rbs_collection.lock.yaml
/sig

準備が整いました!RBSファイルの生成を行いましょう。

用意したRakeコマンドを実行するだけです:
bundle exec rake rbs:setup

.gem_rbs_collection 配下と sig 配下にRBSファイルが生成されたはずです。

これで痛みのないRBSの導入は完了です。 手で書いたコードは0に近いですが、果たしてこれだけで型情報の恩恵にあずかれるのでしょうか。

Visual Studio Codeの設定をしてみましょう。

Visual Studio Codeの設定

公式Steep拡張をインストールしてSteepの機能を有効にしましょう。
プロジェクトを開いていた場合は有効化後にもう一度開き直すと良いようです。

さてどうなっているでしょうか。

この拡張を有効化する前は Date.current が補完できてなかったのが…
Date.current補完ビフォー

できるようになった!
Date.current補完アフター

さらにクラスにカーソルを当てるとこういうドキュメントも出るようになった!
Dateクラスのドキュメントのポップアップ

これだけでもRBSを導入した甲斐があるというものではないでしょうか。

ちなみにSteep拡張の利用の際は、以下が必須なので気をつけましょう(特に1行目):

You have to have Steepfile in the root of the folder.
You have to use Bundler.

これでRuby on RailsプロジェクトへのRBSの傷みのない導入はひとまず完了です。

あとがき

RBSの概要の紹介と実際のRuby on Railsプロジェクトへの導入について書いてみました。

ここから自分で型を定義してガチガチに固めるとなると、それはそれで違った痛みが生まれるのではないかと想像していますが、試してみる価値もありそうです。
また、Visual Studio Code以外のエディターで恩恵に預かれるかまでは調べられなかったので、知っている方がいたら教えていただきたいです。

Rubyの世界で今後の型との付き合い方がどうなっていくのか、楽しみです。


Takayuki Nagatomi

Takayuki Nagatomi (永冨 隆之)

1985年に生まれて以来ずっと福岡在住。高校を1年で中退後、引きこもって海外ゲームばかり遊んでいたら英語が得意になり、TOEICスコア960を獲得。
英語が得意なら言語を扱うプログラミングも向いているのではないかという恩師の勧めにより26歳の時に情報系の専門学校に入学。
30歳でソフトウェアエンジニアとしてのキャリアを開始したweird programmerです。
家族を何よりも大事にしたい、最高の妻の夫であり2児の父でもあります。

現職: 株式会社SmartHR