PostgreSQL アダプターのUUID正規表現で末尾ダッシュを拒否するよう修正
PostgreSQL アダプターの ACCEPTABLE_UUID 正規表現に存在したバグを修正し、末尾にダッシュを持つ不正なUUID文字列を正しく拒否するようになりました。これにより、不正なUUIDで find を呼び出した際に ActiveRecord::RecordNotFound が適切に発生します。
背景
#49934 は、PostgreSQL が受け入れる多様なUUIDフォーマット(波括弧付き・大文字小文字・ダッシュの有無)を #changed? が正しく扱えるよう、ACCEPTABLE_UUID 正規表現を拡張した変更です。しかし、この変更が意図せず末尾ダッシュを持つUUIDを受け入れてしまう挙動を引き起こしていました。
その結果、次のようなコードが ActiveRecord::RecordNotFound を発生させずに動作していました。
User.find('a0ee-bc99-9c0b-4ef8-bb6d-6bb9-bd38-0a11-') # 末尾に余分なダッシュ
Active Record は内部でUUIDを正規化する際に末尾ダッシュをサイレントに除去しており、不正なUUID文字列が有効なUUIDとして扱われていました。PostgreSQL の公式ドキュメントが定義するUUID形式とは異なる入力が透過的に受け入れられていたことが問題の核心です。
技術的な変更
ACCEPTABLE_UUID 正規表現を1行修正し、UUIDの各4文字グループがダッシュを「前置できる」構造に変えることで末尾ダッシュを排除しました。
変更前:
ACCEPTABLE_UUID = %r{\A(\{)?([a-fA-F0-9]{4}-?){8}(?(1)\}|)\z}
変更後:
ACCEPTABLE_UUID = %r{\A(\{)?[a-fA-F0-9]{4}(-?[a-fA-F0-9]{4}){7}(?(1)\}|)\z}
変更前の正規表現は ([a-fA-F0-9]{4}-?){8} というパターンで「4桁英数字、その後にオプションのダッシュ」を8回繰り返す構造でした。この構造では最後のグループもダッシュを後置できるため、32桁の英数字の後にダッシュが来るケース(...0a11-)が許容されていました。変更後は最初の4桁グループを固定し、残り7グループを (-?[a-fA-F0-9]{4}) で「オプションのダッシュを前置した4桁英数字」として繰り返すことで、ダッシュが必ず英数字の前にしか置けない構造になっています。
テストには末尾ダッシュのケースが追加されました。
"a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11-"].each do |invalid_uuid|
uuid = UUIDType.new guid: invalid_uuid
assert_nil uuid.guid
end
設計判断
既存の正規表現パターンを再構築する方式が採用されました。末尾ダッシュを禁止する方法としては末尾に [^-] の先読み否定を加える手もありますが、本PRでは繰り返しグループの構造そのものを見直し、「ダッシュは英数字グループの前置のみ許容」という意味的に正確なモデルで表現しています。これにより、パターンが受け入れるUUID形式の構造が正規表現から直接読み取れるようになっています。
CANONICAL_UUID(標準的なハイフン区切り形式のみを受け入れる正規表現)は変更されていません。ACCEPTABLE_UUID はPostgreSQLが許容する柔軟な入力形式に対応するためのものであり、その柔軟さを維持しつつ明示的に不正な形式だけを排除する最小限の修正となっています。
まとめ
本PRは、#49934 で導入されたUUID形式の柔軟な受け入れ処理が引き起こした末尾ダッシュの見逃しを、正規表現の構造的な修正で解消しました。不正な入力のサイレントな正規化を防ぐことで、find の引数バリデーションが仕様通りに機能するようになります。