3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claude Code に「RBS を生成するコード」を書かせたら便利だった

Last updated at Posted at 2025-10-31

最近個人では Ruby で開発する際は、 rbs-inline と Steep を使って書いてます。型で色々事前にエラーを見つけてくれるのはありがたいのですが、Type Error を避けるために冗長な書き方をしなくていけなくて面倒なこともまあまああります。

例えば、↓ のようなメモ化代入は Ruby ではよく書きますが Steep にチェックさせるには、「インスタンスの型定義」も書く必要があります。面倒ですね。

def profile #: Profile
  @profile ||= Profile.find(id)
end

# Steep で Type Error にならないようにするには以下の定義も必要
# @rbs @name: Profile?

こういう、Steep とかがうまく解析できるように色々 RBS を用意するのは結構求められるのですが、手で書くのは面倒です。
ということで生成するスクリプトを書きたいのですが、ゼロから書くのは大変なので Claude Code にやらせてみたところ結構うまくいったので、個人的に試したこととかを書いていこうと思います。

ちなみにサンプルとして、自分が最近作ってもらったスクリプトのサンプルも置いておきます。

実際にこれらに生成させている RBS ファイルはこんな感じです。

結論

こんな感じの指示を与えれば OK

以下のようなメモ化代入を含むコードを検出して、インスタンス変数の RBS 定義を作成してくれるような script を作成してほしい。
解析には prism や rbs gem を使ってほしい。

def profile #: Profile
  @profile ||= Profile.find(id)
end

Ruby コード, RBS の解析は prism, rbs gem を使えば OK

Claude Code に与える指示としては、こんな感じのことを与えればまあやってくれます。

以下のようなメモ化代入を含むコードを検出して、インスタンス変数の RBS 定義を作成してくれるような script を作成してほしい。

def profile #: Profile
  @profile ||= Profile.find(id)
end

ただ、使う gem を指定しないとコードを正規表現で解析したりする(保守性が)ヤバいコードを生み出すことがまあまああります…。
ある程度保守性が高い実装になるように、静的解析向けのライブラリを指定して指示しておいた方が良いです。

ここでは prism, rbs gem をおすすめしておきます。これらは Ruby に含まれる gem (default gem, bundled gem) なので、追加のインストールが不要なことが多いです。
Claude Code が生成するコードのイメージを掴むためにも、それぞれ軽く説明します。

prism: Ruby コードのパーサー

prism は Ruby コードのパーサーで、Ruby コードの解析を行えます。
例えば、こういう実装で、「コード中で定義されたメソッドの名前の一覧」の出力が行えたりします。

script/ruby_methods_visitor.rb
require "prism"

class MethodPrintVisitor < Prism::Visitor
  def initialize
    @visited_methods = []
    super
  end

  def visit_def_node(node)
    method_name = node.name
    @visited_methods << method_name
    super
  end

  attr_reader :visited_methods
end

visitor = MethodPrintVisitor.new
Prism.parse_file(ARGV[0]).value.accept(visitor)
visitor.visited_methods.each do |method_name|
  puts method_name
end
$ ruby script/ruby_methods_visitor.rb lib/ruboty/adapters/slack_events/slack_events_handler.rb
initialize
handle_event
on_generic_event
on_message
on_event_callback
on_events_api

詳しい使い方は↓のリファレンスを見ると良いです。

Ruby のコードを解析して何かしてもらう、みたいなケースだと prism を使ってもらうようにすれば基本的には良いと思います。

rbs: Ruby の型やクラス構造の解析

rbs は言わずと知れた Ruby の型定義用の言語ですが、 rbs gem を使うとこれらを使った解析が行えます。
例えば、Class の継承関係などは rbs gem に解析させると早いです。

script/print_ancestors.rb
# 指定したクラス名の ancestor を列挙するスクリプト
require 'rbs'
require 'pathname'

# Load RBS environment
loader = RBS::EnvironmentLoader.new
loader.add(path: Pathname('sig'))
loader.add(path: Pathname('.gem_rbs_collection'))
environment = RBS::Environment.from_loader(loader).resolve_type_names

*type_namespaces, type_name = ARGV[0].split('::').reject(&:empty?).map(&:to_sym)
namespace = RBS::Namespace.new(path: type_namespaces, absolute: true)
typename = RBS::TypeName.new(name: type_name, namespace: namespace)

decl = environment.class_decls[typename]

if decl.nil?
  puts "Type not found: #{typename}"
  exit 1
end

puts "Ancestors of #{typename}:"
puts

builder = RBS::DefinitionBuilder.new(env: environment)
definition = builder.build_instance(typename)

definition.ancestors.ancestors.each do |ancestor|
  puts "  #{ancestor.name}"
end
$ ruby script/print_ancestors.rb "::Ruboty::AiAgent::ChatThreadMessages"
Ancestors of ::Ruboty::AiAgent::ChatThreadMessages:

  ::Ruboty::AiAgent::ChatThreadMessages
  ::Ruboty::AiAgent::ChatThreadAssociations
  ::Ruboty::AiAgent::RecordSet
  ::Object
  ::ActiveSupport::Tryable
  ::Kernel
  ::BasicObject

こういった継承関係等の解析は prism ではなく rbs gem にやらせた方が効率的です。
ただ、rbs gem は Ruby コードを直接解析できないので、一度 rbs-inline に ruby コードを元に rbs ファイルを生成させ、それを rbs gem に解析させるのが良いです。

注意したほうが良いこととしては、スクリプト自身が解析する RBS ファイルに、自身が出力する RBS ファイルを含めない ということです。自身が出力した RBS ファイルを、次回の解析に利用してしまうと、実行結果が変わってしまう (冪等ではなくなる) 可能性があるからです。

こういった感じで除外するようにしましょう。

loader = RBS::EnvironmentLoader.new

# スクリプトが出力した RBS ファイル (ここでは generated-by-scripts 内) を除外する
Pathname('sig').children.select(&:directory?).each do |dir|
  next if dir.basename.to_s == 'generated-by-scripts'

  loader.add(path: dir)
end

生成に使うスクリプトを Rake task 化しておく

スクリプトを色々作っていくと、それぞれ叩くのが大変なので Rake Task とかでまとめておくと良いです。
自分が最近書いた https://github.com/tomoasleep/ruboty-ai_agent とかではこういう感じで書いてます。

また、スクリプトに RBS ファイルを出力させる際、特定のディレクトリに全て生成させるようにしておくと良いです。自分は sig/generated-by-scripts 的な名前で用意しています。

Rakefile
namespace :rbs do
  desc 'Clean generated RBS files'
  task :clean do
    sh('rm -rf sig/generate')
    sh('rm -rf sig/generated-by-scripts')
  end

  desc 'Install rbs collection'
  task :collection do
    sh('bundle exec rbs collection install')
  end

  desc 'Run rbs-inline to generate RBS files'
  task :inline do
    sh('script/clean-orphaned-rbs.rb')
    sh('bundle exec rbs-inline --opt-out --output lib')
  end

  desc 'Generate RBS definitions by script/generate-rbs.rb'
  task :script do
    sh('script/generate-data-rbs.rb')
    sh('script/generate-concern-rbs.rb')
    sh('script/generate-memorized-ivar-rbs.rb')
  end
end

# rake rbs で生成をまとめて行えるようにしておく
task rbs: %i[rbs:inline rbs:script]
3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?