Railsã¢ããªãRBS + Steepã§åãã§ãã¯ããã¾ã§ã®æé
rails new
ãã¦ãã steep check
ãéãã¾ã§ã«ãã£ã¦ã¿ãä½æ¥ãã¾ã¨ãã¦ããã
GitHub
ã³ã¼ãã¯GitHubã§å ¬éãã¦ããã®ã§ã詳細ãªæé ãç¥ãããæ¹ã¯åç §ãã¦ãã ããã
æé
gemãå ¥ãã
steepã¨rbs_railsãGemfileã«è¿½å ããã
# Gemfile group :development do gem 'rbs_rails', require: false gem 'steep', require: false end
bundle install
ãå®è¡ããã
$ bundle install
rbs_rails ã®READMEã®æé ã«å¾ã£ã¦ lib/tasks/rbs.rake
ãä½æããã
# lib/tasks/rbs.rake require 'rbs_rails/rake_task' RbsRails::RakeTask.new
gemã®rbsãåå¾ãã
ããã¤ãã®gemã®åå®ç¾©ã¯gem_rbs_collectionããåå¾ãã¦å©ç¨ã§ããã
$ bundle exec rbs collection init $ bundle exec rbs collection install
åå¾ããrbsãã¡ã¤ã«ã¯Gitã§ç®¡çããå¿ è¦ã¯ãªãã®ã§ .gitignore ã«è¿½å ãã¦ããã
/.gem_rbs_collection
rbs_railsã§rbsãçæãã
Railsã®æä¾ããã¡ã½ããå®ç¾©ãããã¤ãèªåçæãã¦ãããã
bin/rails rbs_rails:all
èªåçæããããã¡ã¤ã«ãªã®ã§ .gitattributes ã«è¿½å ãã¦ããã¨è¯ãã*1
sig/rbs_rails/** linguist-generated
Steepfileãä½ã
bundle exec steep init
ã§éå½¢ãä½æããã¾ããæå°ã®è¨å®ã ã¨ä»¥ä¸ã®éãã
target :app do signature "sig" check "app" end
æ¢åãã¡ã¤ã«ã®rbsãç¨æãã
ディレクトリ構造にあわせてrbsのプロトタイプを生成するシェル - アジャイルSEの憂鬱 ã§ç´¹ä»ããã¯ã³ã©ã¤ãã¼ã使ãã
$ find app/ -name '*.rb' | xargs -I{} bash -c 'mkdir -p sig/$(dirname {}); bundle exec rbs prototype rb {} > sig/{}s;'
app/models/application_record.rb
ã®ããã«æåããåå¨ãããã¡ã¤ã«ã«å¯¾ãã¦ãrbsã®ãããã¿ã¤ããçæããã
ä¸è¶³ãã¦ãrbsãç¨æãã
gem_rbs_collectionã«ãªãgemã®rbsã¯èªåã§æ¸ãå¿ è¦ãããã®ã§æ¸ãã
rails new
ããç´å¾ã®ç¶æ
ã§ä¸è¶³ãã¦ããåå®ç¾©ã¯ä»¥ä¸ã®éããï¼ãªãã·ã§ã³ã«ãã£ã¦å¤ãããããããªãï¼
# sig/patch.rbs module ActiveRecord class Base def self.primary_abstract_class: () -> void end end module ActionMailer class Base def self.default: (untyped) -> void def self.layout: (untyped) -> void end end module ActionCable module Channel class Base end end module Connection class Base end end end
åãã§ãã¯ãå®è¡ãã
ããã¾ã§è¨å®ããããã°ã steep check
ãå®è¡ã§ããã
$ bundle exec steep check # Type checking files: ..................................................................................................................................................... No type error detected. ð«
ã¸ã§ãã¬ã¼ã¿ã¼å¾ã«rbsãèªåçæãã
ã¸ã§ãã¬ã¼ã¿ã¼ã使ã£ãã¨ãã«rbsã®ãããã¿ã¤ããä½æãããã rbs_rails:all
ãèªåã§å®è¡ããããã«ãã¦ããã
å°ã楽ã«ãªãã
# config/environments/development.rb Rails.application.configure do config.generators.after_generate do |files| files.each do |f| next unless f.match?(%r{^app/.+\.rb$}) rb_path = Rails.root.join(f) rbs_path = Rails.root.join('sig', f.sub(/\.rb$/, '.rbs')) rbs_path.dirname.mkpath unless rbs_path.dirname.exist? system("bundle exec rbs prototype rb #{rb_path} > #{rbs_path}", exception: true) end if files.any? { |f| f.match?(%r{^app/.+\.rb$}) } system("bin/rails rbs_rails:all", exception: true) end end end
scaffold ã試ã
ããã¾ã§ã®è¨å®ãæ¸ã¾ããç¶æ ã§ãscaffoldã試ãã¦ã¿ãã
$ bin/rails g scaffold book title author
åãã§ãã¯ãå®è¡ããã
$ bundle exec steep check # Type checking files: ......................................................................................................................................F...................... app/controllers/books_controller.rb:28:34: [error] Type `::BooksController` does not have method `book_url` â Diagnostic ID: Ruby::NoMethod â â format.html { redirect_to book_url(@book), notice: "Book was successfully created." } ~~~~~~~~ app/controllers/books_controller.rb:41:34: [error] Type `::BooksController` does not have method `book_url` â Diagnostic ID: Ruby::NoMethod â â format.html { redirect_to book_url(@book), notice: "Book was successfully updated." } ~~~~~~~~ app/controllers/books_controller.rb:55:32: [error] Type `::BooksController` does not have method `books_url` â Diagnostic ID: Ruby::NoMethod â â format.html { redirect_to books_url, notice: "Book was successfully destroyed." } ~~~~~~~~~ Detected 3 problems from 1 file
ã³ã³ããã¼ã©ã§å¼ã³åºãã¦ããã¡ã½ããã§ãã¨ã©ã¼ãåºãã
ã¨ã©ã¼ã«ã¤ãã¦
webä¸ã®è¨äºã 㨠check "app/models"
ã ãåãã§ãã¯ãã¦ãè¨äºãå¤ãã®ã§ãç¾ç¶ã ã¨ã³ã³ããã¼ã©ã®åãã§ãã¯ã¯é£ããã®ããï¼
ç´ãæ¹ãåãã£ãããã¾ãããã°ã«æ¸ããããããªãã
ãã¾ã
GitHub Actionsã§åãã§ãã¯ãåãã
# .github/workflows/check.yml name: check on: push: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: run: runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 with: bundler-cache: true - run: bundle exec rbs collection install - run: bundle exec steep check
Steep VSCode Integration
steep-vscode ã使ãã¨ãVSCodeä¸ã§åãã§ãã¯ã®çµæãè¦ããã¨ãã§ããã
注æ: devcontainerã ã¨rbsã®å¤æ´ãåæ ãããªãï¼
devcontainerã§steep-vscodeã使ã£ã¦ãrbsã®ãã¡ã¤ã«å¤æ´ãæ¤ç¥ãããªãåé¡ãèµ·ããã
Command + Shift + P
=> Steep: Restart all
ã§åèµ·åããã°ç´ãã
åå ã¯ããåãããªãã§ããããã¡ã¤ã«IOé¢é£ãªã®ã§Docker for Macéå®ã®åé¡ããããã¾ããã
*1:ãã«ãªã¯ã®diffãéããç¶æ ã«ãªã