クラウドテックをRails6.1からRails7.0へアップグレードしたので、やったことをまとめてみました

この記事は クラウドワークス[Advent Calendar 2023](https://qiita.com/advent-calendar/2023/crowdworks)の9日目の記事です。

# はじめに
株式会社クラウドワークスの@nisyuuです。和食作りに日々励んでいます。
エンジニアとして[クラウドテック](https://crowdtech.jp/)というフリーランスと企業をマッチングするエージェントサービスを開発しています。

クラウドテックは2023年11月に、Rails6.1からRails7.0へとアップグレードしました。アップグレード時点でRails7.1がリリースされていましたが、既にRails7.0アップグレードのための変更対応を一通り行いレビュー状態になっていたことと、7.1に対応するためには新たな変更が必要になるため7.0までとしました。

本記事では、Rails6.1からRails7.0へ移行するために行なったことをまとめます。

# Rails7.0に依存するgemのアップグレード
`Gemfile`のRailsバージョンを指定している箇所を`gem 'rails', '~> 7.0.5.1’`に変更します。
次に`bundle update`をすることでRails7.0と依存するgemのアップデートが可能となりますが、1時間以上経過しても処理が一向に終わらなかったため、手動で一つ一つ依存gemを確認してアップグレードしました。

## 手動でアップデートした方法
Railsバージョンを`gem 'rails', '~> 7.0.5.1’`にした状態で、`bundle install`をすると、処理が中断し依存するgemをチェックすることができます。

**例**
```bash
Bundler could not find compatible versions for gem "activemodel":
  In snapshot (Gemfile.lock):
    activemodel (= 6.1.7.3)

  In Gemfile:
    public_activity was resolved to 2.0.2, which depends on
      activerecord (>= 5.0) was resolved to 6.1.7.3, which depends on
        activemodel (= 6.1.7.3)

    rails (~> 7.0.5.1) was resolved to 7.0.5.1, which depends on
      activemodel (= 7.0.5.1)

Running `bundle update` will rebuild your snapshot from scratch, using only
the gems in your Gemfile, which may resolve the conflict.
```

`activemodel`のバージョンが依存関係にあるとわかったので、`bundle update --conservative activemodel`を実行します。
再び`bundle install`実行し、`Bundler could not find compatible versions for gem`が表示されなくなるまで繰り返していきます。

## Sprocketsへの依存がオプショナルになった
Rails7.0から`sprockets-rails`への依存関係がなくなったため、引き続きSprocketを使用したい場合は別途インストールする必要があります。

# アップデートタスクの実行
gemのアップグレードが終わった後に、`rails app:update`を実行します。`app:update`コマンドは、新しいRailsバージョンで必要となるファイルの新規作成や既存ファイルの変更などが行われます。

# ソースコードの変更
手始めは[Rails 6.1からRails 7.0へのアップグレード](https://railsguides.jp/v7.0/upgrading_ruby_on_rails.html#rails-6-1%E3%81%8B%E3%82%89rails-7-0%E3%81%B8%E3%81%AE%E3%82%A2%E3%83%83%E3%83%97%E3%82%B0%E3%83%AC%E3%83%BC%E3%83%89https://railsguides.jp/v7.0/upgrading_ruby_on_rails.html#rails-6-1%E3%81%8B%E3%82%89rails-7-0%E3%81%B8%E3%81%AE%E3%82%A2%E3%83%83%E3%83%97%E3%82%B0%E3%83%AC%E3%83%BC%E3%83%89)を参考にチェック作業を行い、変更対応を入れました。

アップグレードガイド以外の変更必要箇所もあったため、下記にまとめていきます。

## ActiveRecord::Base.configurations[Rails.env]が使用できない
`ActiveRecord::Base.configurations`がカプセル化されたようで、`ActiveRecord::Base.configurations[Rails.env]`を`ActiveRecord::Base.connection_db_config.configuration_hash`へと変更する必要がありました。

## Tasks::DatabaseTasks.schema_file が削除された
元々、非推奨になっていた`Tasks::DatabaseTasks.schema_file`がRails7.0で削除されました。代わりに下記コードへと変更しました。

```ruby
file = ActiveRecord::Tasks::DatabaseTasks.schema_file_type(:sql)
file_path = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, file)
```

ただし、`Tasks::DatabaseTasks.schema_file_type`がRails7.0で非推奨化されてしまったため、他の回避方法があればそちらを選択するのが良さそうです。

## to_sがto_fsへと変更
`to_s`がRails7.0から非推奨となり、`to_fs`もしくは`to_formatted_s`が推奨されるようになりました。Rails7.1から`to_s`は削除となっています。

> 参考
[to_s 👉 to_fsに変更](https://zenn.dev/ryosk7/articles/ae547ad6415a44#to_s-%F0%9F%91%89-to_fs%E3%81%AB%E5%A4%89%E6%9B%B4)

## テストでセッションの値をセットする時はActionController::TestSessionを使用する
Rails7.0にアップグレードしてから`allow_any_instance_of(ActionDispatch::Request).to receive(:session).and_return({xxx: yyy})`という書き方ができなくなりました。
実際に実行すると、このようなエラーが表示されます。

```bash
NoMethodError: undefined method `enabled?' for {xxx: yyy}:Hash
```

`and_return`にハッシュ値の`{xxx: yyy}`をそのまま入れるのではなく、`ActionController::TestSession`でセットした値を`and_return`に渡すようにしなければなりません。

**例**

```ruby
mock_session = ActionController::TestSession.new(test_ids: [1, 2, 3])
allow_any_instance_of(ActionDispatch::Request).to receive(:session).and_return(mock_session)
```

## ActiveModel::Errorsがハッシュとして使えなくなった

ActiveModel::Errorsはエラーオブジェクトの配列となりました。

**エラーメッセージ**

```bash
NoMethodError: undefined method `to_sym' for #<ActiveModel::Error attribute=others, type=too_long, options={:count=>4096}>
```

変更はこのようにする必要があります。

```diff_ruby
- record.errors.each { |k, v| errors.add(k, v) }
+ record.errors.each { |error| errors.add(error.attribute, error.message) }
```

## ActiveModel::Errors#clearが使えなくなる

非推奨であったActiveModel::Errors#clearが使えなくなり、代わりにActiveModel::Errors#deleteが代替の機能として提供されました。

コードはこのような変更が必要になります。

```diff_ruby
- errors[:base].clear
+ errors.delete(:base)
```

## mergeを使ってのBETWEEN条件を評価してくれなくなる

同じカラム上で条件を複数マージした時に、マージした条件が維持されず常に後者の条件によって置き換わるようになりました。
複数条件を維持させるには、以下のように一つのmergeで完結させるか、`rewhere`オプションを使うことで可能なようです。

```diff_ruby
relation = Author.all
- relation = relation.joins(:book).merge(Book.where(published_at: start_published_at..)) if start_published_at.present?
- relation = relation.joins(:book).merge(Book.where(published_at: ..end_published_at)) if end_published_at.present?
+ relation.joins(:book).merge(Book.where(published_at: start_published_at.presence..end_published_at.presence))
```

`rewhere`については[Ruby on Rails 7.0 リリースノート](https://railsguides.jp/v7.0/7_0_release_notes.html#active-record-%E4%B8%BB%E3%81%AA%E5%A4%89%E6%9B%B4%E7%82%B9)を参照ください。

# 検証作業
Rails7.0の変更対応を終え、CIも無事に通るようになったところで、通常のコードレビューに加えドッグフーディングやウォークスルーを実施しました。
ドッグフーディングとは、開発したものを実際に自社組織内で使ってみることを意味します。今回の場合だと、Rails7.0環境の含まれるブランチを各エンジニアに取り込んでもらい、実際にRails7.0上で作業してもらうことでドッグフーディングを実施しました。
ウォークスルーは、評価対象のプロセスを実際に体験をすることを指します。評価対象は影響範囲のある箇所がメインになります。Rails7.0アップグレードはシステム全体に影響があるものなので、システム上のユーザーがよく使用する機能を中心に幅広くウォークスルーを行いました。

# おわりに
チームのサポートと入念にチェックを行っていたおかげで、リリースは何事もなく終えられました。