ActiveStorage::Blobのコンテンツタイプ判定メソッドでnilを安全に処理
ActiveStorage::Blobのコンテンツタイプ判定メソッド(image?、audio?、video?、text?)が、content_typeがnilの場合にNoMethodErrorを発生させる問題が修正されました。これにより、コンテンツタイプが未設定のBlobに対しても安全にクリーンアップ処理を実行できるようになります。
背景
未添付のActive Storage Blobを削除するpurge_later処理において、content_typeがnilのレコードが存在するとNoMethodErrorが発生していました。具体的には、image?メソッド内でundefined method 'start_with?' for nilというエラーが発生し、クリーンアップ処理全体が失敗する問題がありました。
この問題は、脆弱性スキャンツールが/rails/active_storage/direct_uploadsエンドポイントに対してコンテンツタイプを指定せずに直接リクエストを送信することで、content_type: nilのレコードがデータベースに作成されたことが原因です。Active Storageの仕様上、content_typeがnilになる可能性があるため、コードはこの状況を適切にハンドリングする必要がありました。
技術的な変更
activestorage/app/models/active_storage/blob.rbの4つのコンテンツタイプ判定メソッドが、安全なナビゲーション演算子(&.) を使用するように変更されました。
変更前:
def image?
content_type.start_with?("image")
end
def audio?
content_type.start_with?("audio")
end
def video?
content_type.start_with?("video")
end
def text?
content_type.start_with?("text")
end
変更後:
def image?
content_type&.start_with?("image")
end
def audio?
content_type&.start_with?("audio")
end
def video?
content_type&.start_with?("video")
end
def text?
content_type&.start_with?("text")
end
content_type&.start_with?の形式に変更することで、content_typeがnilの場合はstart_with?メソッドが呼び出されずにnilを返します。Rubyの論理評価において、nilはfalsyな値として扱われるため、各判定メソッドは期待通りfalseを返します。
テストコードでは、content_type: nilのBlobを作成し、すべてのコンテンツタイプ判定メソッドがfalseを返すことを検証しています。
test "blob type methods return false for nil content type" do
blob = create_blob_before_direct_upload(
filename: "unknown_file",
byte_size: 100,
checksum: "test_checksum",
content_type: nil
)
assert_nil blob.content_type
assert_not_predicate blob, :image?
assert_not_predicate blob, :video?
assert_not_predicate blob, :audio?
assert_not_predicate blob, :text?
end
設計判断
安全なナビゲーション演算子による最小限の変更 という方針が採用されました。
この修正では、各メソッドにcontent_type.present? && content_type.start_with?(...)のような明示的なnilチェックを追加するのではなく、&.演算子を使用しています。この選択により、コードの可読性を維持しながら、nilセーフな実装を実現しています。&.演算子を使用することで、レシーバーがnilの場合は後続のメソッド呼び出しをスキップし、nilを返すというRubyの慣用的なパターンに従っています。
変更の影響範囲は4つのメソッドの4行のみに限定されており、既存のコンテンツタイプが設定されているBlobの動作には一切影響を与えません。content_typeが存在する場合の振る舞いは変更前と完全に同じであり、後方互換性が保たれています。
本PRは、予期しないデータ状態に対する防御的プログラミングの好例です。外部からの不正なリクエストやデータ移行時の不整合など、実運用環境では仕様上想定されていないデータ状態が発生する可能性があります。最小限のコード変更で堅牢性を向上させ、クリーンアップ処理の継続性を確保した判断といえます。