ActiveRecord::Type::JsonのJSON Encoderキャッシュを遅延評価に変更
ActiveRecordのType::Jsonが起動時にJSON Encoderをキャッシュする実装が削除され、ActiveSupport::JSON::Encodingの遅延評価されたEncoderを使用するようになりました。これにより、アプリケーション初期化後にJSON Encoderをカスタマイズできるようになります。
背景
クラスロード時のEncoderキャッシュにより、ActiveRecordが最初に参照された時点でJSON Encoderが固定されてしまう不具合が発生していました。PR Descriptionによると、「If we cache the encoder when the class is first referenced, it prevents us from being able to proper override the encoder later」という問題が存在していました。
この実装では、一部のGemがActiveRecordを早期にロードした場合、その後にアプリケーションがActiveSupport::JSON::Encoding.json_encoderをカスタマイズしても、ActiveRecord::Type::Jsonは古いEncoderを使い続けてしまいます。#56766で報告されたように、カスタムEncoderを設定してもType::Jsonが標準のjson gemベースのEncoderを使用し続ける問題が発生していました。
技術的な変更
activerecord/lib/active_record/type/json.rbからJSON_ENCODER定数が削除され、serializeメソッドがActiveSupport::JSON::Encodingのメソッドを直接呼び出すように変更されました。
変更前:
JSON_ENCODER = ActiveSupport::JSON::Encoding.json_encoder.new(escape: false)
def serialize(value)
JSON_ENCODER.encode(value) unless value.nil?
end
変更後:
def serialize(value)
ActiveSupport::JSON::Encoding.encode_without_escape(value) unless value.nil?
end
encode_without_escapeメソッドは、ActiveSupport::JSON::Encodingが提供するescape: falseオプションを事前適用したメソッドです。このメソッド内部では既にEncoderインスタンスがキャッシュされているため、Type::Json側で独自にキャッシュする必要はありません。
この変更により、Encoderの参照が実行時に解決されるようになり、アプリケーション初期化フェーズでの設定変更が正しく反映されます。
設計判断
既存のキャッシュ機構を活用する方式が採用されました。
PR Descriptionによると、「just make use of ActiveSupport::JSON's cached encoder and prebuilt without_escaping option」という方針が示されています。ActiveSupport::JSON::Encodingは既にEncoderインスタンスをキャッシュする実装を持っており、Type::Json独自のJSON_ENCODER定数は、このキャッシュ層の上にさらに別のキャッシュ層を追加する形になっていました。
本PRでは、Type::Json独自のキャッシュを削除し、ActiveSupport::JSON::Encodingのキャッシュ機構に一本化することで、設定変更の反映タイミングを統一しています。パフォーマンス面では、ActiveSupport::JSON::Encoding内部でEncoderインスタンスがキャッシュされているため、メソッド呼び出しのオーバーヘッドのみが追加されますが、JSON Encoderの初期化コストと比較すると無視できる程度の影響です。
本PRではテストの追加が保留されていますが、既存のテストが通過していることから、機能的な互換性は維持されていると判断されています。
まとめ
本PRは、クラスロード時のEncoderキャッシュが引き起こした設定タイミングの問題を、キャッシュ層を統合することで解決しています。Type::Json独自のキャッシュを削除し、ActiveSupport::JSON::Encodingの遅延評価されたEncoderを使用することで、アプリケーション初期化の柔軟性を回復しながら、既存のキャッシュ機構によるパフォーマンス特性を維持しています。