はじめに
本日 2025/12/25 (木) に Ruby 4 がリリースされました 🎉
Ruby::Box というクラスの定義を隔離できる新機能が実装されています。
今回はこの Ruby::Box を使って、複数のバージョンの Gem を同じ Ruby プロセスで動かしてみようと思います。
前提
Ruby 4 をインストールしておきましょう。以下は rbenv を使用して Ruby 4.0.0 インストールする例です。
$ git -C ~/.rbenv pull
$ git -C ~/.rbenv/plugins/ruby-build pull
$ rbenv install 4.0.0
$ rbenv local 4.0.0
例: 異なるバージョンの Rack を irb で動かす
Rack (rack) の Rack::Utils.byte_ranges メソッドの挙動が v3.0.9 から v3.0.9.1 で変わっているようです。それを確かめてみます。
事前に rack の 3.0.9 と 3.0.9.1 を両方インストールしておきます。
$ gem install rack -v 3.0.9
$ gem install rack -v 3.0.9.1
Ruby::Box を有効にするには RUBY_BOX=1 という環境変数を設定する必要があります。
$ RUBY_BOX=1 irb
/Users/quanon/.rbenv/versions/4.0.0/bin/ruby: warning: Ruby::Box is experimental, and the behavior may change in the future!
See https://docs.ruby-lang.org/en/4.0/Ruby/Box.html for known issues, etc.
irb(main):001>
まず、それぞれのバージョン用の Ruby::Box を用意します。
Ruby::Box はそれぞれ独立した $LOAD_PATH を持っており Ruby::Box#load_path でアクセスできます。そこに各バージョンの Gem の require すべきパスを追加します。
# Ruby::Box はそれぞれ独立した $LOAD_PATH を持っているので、
# それぞれに Gem のパスを追加する。
v3_0_9_0_box = Ruby::Box.new
full_require_paths = Gem::Specification.find_by_name('rack', '3.0.9').full_require_paths
#=> ["/Users/quanon/.rbenv/versions/4.0.0/lib/ruby/gems/4.0.0/gems/rack-3.0.9/lib"]
full_require_paths.each { v3_0_9_0_box.load_path << it }
v3_0_9_1_box = Ruby::Box.new
full_require_paths = Gem::Specification.find_by_name('rack', '3.0.9.1').full_require_paths
#=> ["/Users/quanon/.rbenv/versions/4.0.0/lib/ruby/gems/4.0.0/gems/rack-3.0.9.1/lib"]
full_require_paths.each { v3_0_9_1_box.load_path << it }
次に、それぞれの Ruby::Box で rack を require します。
# 各 Ruby::Box で rack を require する。
# Ruby::Box ごとに Rack のバージョンが異なっている。
v3_0_9_0_box.require('rack')
v3_0_9_0_box::Rack::RELEASE
##=> "3.0.9"
v3_0_9_1_box.require('rack')
v3_0_9_1_box::Rack::RELEASE
#=> "3.0.9.1"
最後に、それぞれの Ruby::Box で Rack::Utils.byte_ranges を呼び出してみます。
env = { 'HTTP_RANGE' => 'bytes=0-20,0-500' }
size = 500
# Ruby::Box ごと、つまり Rack のバージョンごとに
# Rack::Utils.byte_ranges の挙動が異なることを確認できた 🙆
v3_0_9_0_box::Rack::Utils.byte_ranges(env, size)
#=> [0..20, 0..499]
v3_0_9_1_box::Rack::Utils.byte_ranges(env, size)
#=> []
同じ irb (同じ Ruby プロセス上) で複数のバージョンの rack の挙動を確認することができました 😉
余談
- まだ RubyGems や Bundler には
Ruby::Boxを使って異なるバージョンの Gem を読み込んだり Gem のクラスを隔離したりするための専用の機能はないようです。これからの進化に期待です! - csv や bigdecimal といった Gem でも同様のことを試しましたが、irb がクラッシュしたり思った挙動にならなかったり、まだまだ動作が不安定でした。