😊

新卒エンジニアがRubyを最新版にした時に躓いた話

2023/09/27に公開

今回の作業内容

Docker上で開発しているアプリケーションのRuby3.0.4をRuby3.2.2に上げていきます。
記事にはしていませんが8月にRuby on Rails(以降Rails)を7.0.7上げたばかりなので、この作業でRailsもRubyも2023年9月現在でのほぼ最新版になりました🎉

上げる手順

  1. (テストを充実させる)
  2. マイナーバージョンを1つあげる
  3. テストを回す
  4. テストが全て通るようになるまで修正する
  5. 1に戻る

というのが基本的な手順になります。
0に関しては今回の手順には含めません。日頃からテストを充実させておくことは、新たに作成した機能が動いていることを担保するだけでなく、今回のようにアプリケーションの基盤に関わるようなアップデートで機能が動いているかを担保することができます。

やっていく

マイナーバージョンとは?

手順1にいきなり出てきたマイナーバージョンの説明を先にします。
RubyやRailsなどのソフトウェアでは、セマンティックバージョニングというバージョンの管理方法が採用されています。
セマンティックバージョニングは、メジャーバージョン マイナーバージョン パッチバージョンの3層で構成されています。
メジャーバージョンは、後方互換性の無いAPIの破壊的変更があった場合に上げられます。
マイナーバージョンは、後方互換性のあるAPIの変更があった時に上げられます。
パッチバージョンは、バグなどの修正があった時に上げられます。
Ruby3.0.4だと3がメジャーバージョン、0がマイナーバージョン、4がパッチパージョンです。
メジャーバージョンを変更すると破壊的変更によるアプリケーションへの影響範囲が大きすぎてしまい、パッチバージョンの変更ではバグの修正のみなので変更点が小さすぎてしまう。その間を取ったちょうど良い変更範囲がマイナーバージョンであると考えてください。

3.0.4→3.1.4にあげる

弊社が使用していたRubyのバージョンは3.0.4だったので、1つ上のマイナーバージョンの安定版である3.1.4に上げていきます。
多くの場合「Rubyをあげる前にgemのバージョンを最新にしましょう」と言われています。しかし私個人の考えでは、gemを最新まで上げてしまうと、Rubyを上げたことによるバグなのか、gemを上げたことによるバグなのかの判断が難しいので、gemのバージョンアップは強制ではないです。なので今回はRubyのバージョンのみを上げていきます。
まず初めに、3つのファイルを書き換えていきます。

.ruby_version
- 3.0.4
+ 3.1.4
Dockerfile
- FROM ruby:3.0.4
+ FROM ruby:3.1.4
Gemfile
- ruby '3.0.4'
+ ruby '3.1.4'

と書き換えたらshellで

shell
docker-compose build --no-cache
docker-compose run --rm web bundle install

と実行しましょう。
コマンドの意味は以下の通りです。
docker-compose build --no-cache: cacheを使わずにイメージをビルドする。cacheが有効になっているとrubyのバージョンを上げた変更が反映されない場合があります。--no-cacheというオプションをつけることでキャッシュを使わずにコンテナを立ち上げてくれます。
docker-compose run --rm web bundle install: Rubyのバージョンが上がったことでgemもバージョンアップする可能性があるので確認します。
これでもRubyのバージョンが上がらない場合は、Dockerのイメージを削除してみましょう。
既存のイメージが新しくビルドするイメージに影響を与えていることもあり得るので、古いイメージは削除してしまいます。

uri_data gemでのエラー

早速エラーが出てきました。

NameError: uninitialized class variable @@schemes in URI
Did you mean?  scheme_list
/usr/local/bundle/gems/data_uri-0.1.0/lib/data_uri/uri.rb

gemのdata_uriがなんかなってる。。。。
全く同じエラーに陥っている方がいらっしゃいました。
https://zenn.dev/leaner_dev/articles/20230426-ruby-3-2
data_uriがRuby3.1の変更に対応していないとのこと。
それもそのはず。このgemは10年ほどリリースされていませんでした。
この先Ruby3.1以降に対応するとはあまり考えられないので、gemを削除して、先の記事を参考に以下のような独自クラスを実装しました。

lib/uri_data.rb
class UriData
  REGEXP = %r{\Adata:([-\w]+/[-\w+.]+)?;base64,(.*)}m
  attr_reader :data, :content_type

  def initialize(uri)
    data_uri_parts = uri.match(REGEXP) || []

    @content_type = data_uri_parts[1]
    @data = Base64.decode64(data_uri_parts[2])
  end
end

uri_data gemと同様に::URI::Dataのまま参照してくれれば楽だったのですが、

lib/uri/data.rb
module URI
  class Data
    hogehoge
  end
end

と実装しても::URI::Dataがうまく参照されなかったので、代替案としてUriDataというクラスを作成しました。

DidYouMean:という警告文

こちらはエラーでは無いのですがbundle installなどをすると

Calling DidYouMean::SPELL_CHECKERS.merge!(error_name => spell_checker)' has been deprecated. Please call DidYouMean.correct_error(error_name, spell_checker) instead.

という警告文が出てきます。
RubyGemsの下記issueにある通り、
https://github.com/rubygems/rubygems/issues/5234

sell
bundle update --bundler

解消できました。
bundlerのバージョンを上げた後、bundle installでエラーになるかもしれません。
その時は、DockerfileとGemfile.lockに書かれているbundlerのバージョンを合わせてください。

Dockerfile
- RUN gem install bundler -v '2.2.27'
+ RUN gem install bundler -v '2.4.19'
Gemfile.lock
BUNDLED WITH
-   2.2.33
+   2.4.19

これでRuby3.1.4で動くようになりました!
RSpecも全て通ったので、Ruby3.1.4にあげる作業はこれでおしまいです。

CIのRubyのバージョンもあげる

手元の環境はこれで良いのですが、最後にCIでのRubyのバージョンも上げておきましょう。

.github/workflows/test.yml
省略
   steps:
      - uses: actions/checkout@v2
      - name: set up Ruby
        uses: ruby/setup-ruby@v1
        with:
-          ruby-version: 3.0.4
+          ruby-version: 3.1.4
省略

ウキウキでPR出したのに、CIでテストが落ちるなんてのは悲しいですよね。
私は悲しかったです。

3.1.4→3.2.2にあげる

やることは先ほどと変わりません。
3つのファイルを書き換えていきます。

.ruby_version
- 3.1.4
+ 3.2.2
Dockerfile
- FROM ruby:3.1.4
+ FROM ruby:3.2.2
Gemfile
- ruby '3.1.4'
+ ruby '3.2.2'

と書き換えたらshellで

shell
docker-compose build --no-cache
docker-compose run --rm web bundle install

と実行しましょう。
今回は特に問題なくコンテナが立ち上がりました。
RSpecでも特に問題は見つかりませんでした。
CIでのRubyのバージョンを3.2.2にして、終わりです。

と思っていたのに。。。。

リリース後、何気なく開発をしていた時に不思議なことが起こりました。
rails consoleでモデル名.last.など2回以上メソッドをチェーンするとコンソールが落ちる
こちらの原因は私の先輩が突き止めてくれました。
gemの
dry-struct
dry-types
dry-validation
が最新のバージョンになっていないことが原因だったようです。
gemも最新バージョンにした方が良いですね〜

まとめ

入社半年の社員には、初めての経験だったのでエラーの原因追求などに時間がかかりました。
特にgemなんてよくわからない依存関係もあるので、最新のはずが依存関係かなんかで最新になっていなかったなんてこともありました。
しかしこれも良い経験になったことには変わりありません。
次回以降のバージョンアップはもう少し素早くできそうです!
今回の身に染みてわかったのは、
gemは定期的にバージョンアップしましょう!!
ということですね

参考資料

セマンティックバージョニング(ほぼ)理解した
https://qiita.com/yaskitie/items/75eebf335d4fbb96551f
Docker + Rails(Ruby)をバージョンアップ(アップグレード)したお話
https://qiita.com/t0sh1/items/5e7ecdc8f47505118a1f
Rubyリリースノート
https://www.ruby-lang.org/ja/
RubyKaigi 2023参戦のためにRuby 3.2にバージョンアップしました
https://zenn.dev/leaner_dev/articles/20230426-ruby-3-2#nameerror%3A-uninitialized-class-variable-%40%40schemes-in-uri-did-you-mean%3F-scheme_list

COUNTERWORKS テックブログ

Discussion