`number_to_phone`が複数文字デリミタを先頭から正しく除去するよう修正
7桁以下の電話番号に複数文字の:delimiterを指定すると、先頭に不正な断片が残るバグが修正されました。slice!(0, 1) という1文字固定の除去コードを slice!(0, delimiter.length) に変更するだけの1行修正です。
背景
number_to_phone は :delimiter オプションに任意の文字列を受け付けますが、「先頭のデリミタを除去する」処理が1文字固定になっていたため、複数文字のデリミタで誤動作していました。具体的には、- や , のようなデリミタを7桁の電話番号(エリアコードなし)に適用すると、以下のような誤った出力が生成されていました。
number_to_phone(5551234, delimiter: " - ")
# => "- 555 - 1234" (期待値: "555 - 1234")
number_to_phone(5551234, delimiter: ", ")
# => " 555, 1234" (期待値: "555, 1234")
このバグは2011年の #3314 で導入された修正に由来します。#3314 はエリアコードなしの場合に先頭デリミタが残る問題を修正したPRでしたが、その実装が slice!(0, 1) という1文字固定の除去に留まっていたため、複数文字デリミタのケースが見落とされていました。テストスイートが1文字のデリミタと10桁番号のみを対象にしていたことで、13年間この問題が眠り続けていました。
技術的な変更
convert_without_area_code メソッド内の1行を修正することで、デリミタ長に応じた正確な除去が実現されています。
変更前:
number.slice!(0, 1) if start_with_delimiter?(number)
変更後:
number.slice!(0, delimiter.length) if start_with_delimiter?(number)
デリミタが先頭に付くのは、正規表現パターン /(\d{0,3})(\d{3})(\d{4})$/ の第1キャプチャグループ \d{0,3} が空(7桁以下でエリアコードなし)になるケースのみです。この場合、gsub後の文字列は "\1#{delimiter}\2#{delimiter}\3" の展開により - 555 - 1234 のようにデリミタが先頭に現れます。start_with_delimiter? がその状態を検出してから slice! で除去する仕組みですが、除去サイズが1文字固定だったため複数文字デリミタに対応できていませんでした。
start_with_delimiter? は delimiter.present? を保証しているため、delimiter.length は常に1以上になります。また、String#slice! は文字単位で動作するため、マルチバイト文字のデリミタ(例: "—" U+2014 EMダッシュ)も正しく処理されます。追加されたテストケースは以下の3つです。
assert_equal("555 - 1234", number_helper.number_to_phone(5551234, delimiter: " - "))
assert_equal("800 - 555 - 1212", number_helper.number_to_phone(8005551212, delimiter: " - "))
assert_equal("555—1234", number_helper.number_to_phone(5551234, delimiter: "—"))
10桁番号のテストケース(8005551212 + -)も追加されており、エリアコードあり・なし両方でデリミタが正常に機能することが確認されています。
設計判断
修正範囲を最小限に絞る方針 が採用されており、コードパスの分岐追加や正規表現の変更は一切行っていません。
変更箇所は slice!(0, 1) から slice!(0, delimiter.length) への1文字の変更のみです。start_with_delimiter? による条件分岐や gsub のパターンはそのまま維持されており、既存の動作を保証するすべての条件(10桁番号、エリアコードあり、カントリーコード、内線番号、空デリミタ)は影響を受けません。PR本文によると、変更後も number_helper_test.rb の全22テスト・819アサーションが通過しています。
マルチバイト対応が自動的に得られる点も設計上の利点です。String#slice! が文字単位で動作するRubyの仕様により、追加の特殊処理なしにUnicode文字のデリミタを正しく扱えます。
まとめ
2011年の修正で誤って残された 1 というマジックナンバーを delimiter.length に置き換えるだけで、ドキュメントに記載された「任意の文字列を:delimiterに指定可能」という仕様が初めて完全に実現されました。複数文字デリミタやマルチバイトデリミタを利用していたアプリケーションは、このバグフィックスにより期待通りの出力を得られるようになります。