Rails6+attr_encryptedの環境をRails7にアップデートするときにやったこと

## はじめに

Rails6.0からRails7.0（正確には7.0.3）にアップグレードするにあたり、データの暗号化処理をを `attr_encrypted` からRails7の標準機能に置き換えた話を以下にまとめます。

## Rails7にアップブレードする際に発生した問題点

アップグレードの際に以下の諸問題が発生しました。

graphqlのpreloader
https://michiomochi.com/blog/rails-v6047-to-v7023

sessionが見つからない問題
https://ta-watanabe.hatenablog.com/entry/2022/03/14/140128
https://blog.dnpp.org/api_only_rails7_with_devise

Enumerable.sumがRails7から非推奨
https://www.bigbinary.com/blog/rails-7-deprecates-enumerable-sum-and-array-sum

graphql-fragment-cacheが最新だと動かない問題
https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache/

strip_tagsの仕様変更に伴う修正
https://techracho.bpsinc.jp/hachi8833/2022_06_06/118470#1-1

stylesheetのmedia属性
https://qiita.com/SoarTec-lab/items/9832bb89402e452b20bb#railsapplicationconfigaction_viewapply_stylesheet_media_default--false

これらは大体対処法がありましたが、一点非常に難儀したのは `attr_encrypted` の内容とRails7の標準機能がバッティングしたことです。

## attr_ecnryptedとは

https://github.com/attr-encrypted/attr_encrypted

要約すると、指定したカラムに対してデータを入力する際に、自動的に暗号化して保存、取り出すときは暗号をデコードしてくれます。
例として、`user` モデルに `email` の情報を暗号化して保存するとします。
userモデルには以下のように定義します。

```
  t.string "encrypted_email", null: true
```

user.rbには

```
attr_encrypted :email, key: your_encrypted_key, algorithm: 'aes-256-cbc', mode: :single_iv_and_salt
```

を設定します。（オプション等はマニュアル参照ですが、既に非推奨）
上記によって、`user.email` を参照すると、実態は `user.encrypted_email` にある暗号化されたデータを透過的に扱うことが出来ます。

## Rails7での標準暗号化機能

Railsはver7から `attr_encrypted` で提供している物と大体同等のカラム暗号化機能を提供するようになりました。問題は `attr_encrypted` で提供しているメソッドと同名のメソッドが含まれるようになり、Rails7上で `attr_encrypted` が提供するメソッドを実行するとエラーが発生するようになります。
そこで、暫定的に以下のブランチに切り替えます。
https://github.com/InvestIMBY/attr_encrypted/tree/rails-7-0-support

```
gem "attr_encrypted", github: 'InvestIMBY/attr_encrypted', branch: 'ruby-3-rails-7-0-support'
```

また、今まで `encrypted` というメソッドで暗号化していた物がRails7の標準機能とバッティングしてしまったため、以下のようにリネームします。

```
- encrypted_email = User.encrypt(:email, email)
+ encrypted_email = User.attr_encrypted_encrypt(:email, email)
```

元々 `attr_encrypted` 自体更新が停滞していたこともあり、この機会にRails7の標準暗号化機能に移行することにしました。
移行を行った事例について調べてみると、1件該当しました。
https://pagertree.com/blog/migrate-attr_encrypted-to-rails-7-active-record-encrypts

概ねこの内容に従いますが、こちらの環境ではスキーマ管理を `Ridgepole` で行っているので、細かい点では読み替えが必要です。

## 移行手順

以下の3つの段階に分けて行います。

1. 既存の encrypted_email の設定を oldemail のメソッドで取得するように変更
2. 標準encrypt用のemailカラムの email を追加
3. バッチで oldemail から email にデータコピー

3.で行うバッチファイルの作成とテストまでは事前に行っておきます。

### 暗号化キーの作成
 
```
bin/rails db:encryption:init
```

によって以下の様なキーが出力されます

```
active_record_encryption:
  primary_key: EGY8WhulUOXixybod7ZWwMIL68R9o5kC
  deterministic_key: aPA5XyALhf75NNnMzaspW7akTfZp0lPY
  key_derivation_salt: xEY0dt6TZcAMg52K7O84wYzkjvbA62Hz
```

```
EDITOR=vim bin/rails credentials:edit
```
などでcredentialファイルを編集して追記しておきます。

### 新暗号化用カラム

```
  email           :text(65535)      not null #今回追加した物
  encrypted_email :string(255)
```
 
今までは `encrypted_email` によって透過的に `email` を扱っていましたが、新暗号化方式からそのまま `email` カラムを扱います。
同時に旧暗号化方式を以下のように修正します。

```
-  attr_encrypted :email, key: your_attr_encrypted_key, algorithm: 'aes-256-cbc', mode: :single_iv_and_salt
+  attr_encrypted :oldemail, key: your_attr_encrypted_key, algorithm: 'aes-256-cbc', mode: :single_iv_and_salt, attribute: 'encrypted_email'
+  encrypts :email, deterministic: true
```

上記の設定により、`user.oldemail` が旧暗号化方式を指すようになります。

### データ移行バッチ

抜粋すると以下のような形になります。
```
      User.all.find_each do |user|
        if user.email.blank?
          user.email = user.oldemail
          user.save!
        end
      end
```

サービスをメンテインの後に、暗号化処理を新方式に切り替えたコードをマージし、バッチを実行します。
emailのデータが問題なく移行済みになっていることを確認し、 `attr_encrypted` gemと `encrypted_email` カラムを削除します。