safe_join のデフォルト区切り文字から $, を除去して nil に変更
Rails の safe_join がデフォルトで使用していた $,(Ruby のグローバル変数)を廃止し、デフォルトを nil に変更しました。これにより、Ractor 環境での安全性が向上し、未文書化の挙動がなくなります。
背景
safe_join は文字列配列を HTML 安全に結合するユーティリティで、従来は第2引数 sep のデフォルト値に $, を採用していました。実装上は sep = ERB::Util.unwrapped_html_escape(sep) でエスケープし、join(sep) で結合しているため、デフォルトの $, が区切り文字として出力されていました。
$, は Ruby 本体で 10 年以上前に非推奨化され、Ractor では完全に非安全とされています。さらに Rails の公式ドキュメントでも safe_join のデフォルトセパレータは言及されておらず、実質的に暗黙の仕様に過ぎませんでした。
このような未文書化かつ非推奨の振る舞いを残すことは、開発者に予期せぬ警告やランタイムエラーを招くリスクがあります。したがって、デフォルトから $, を除去し、Ruby が返す既定値 nil をそのまま利用する方針が取られました。
技術的な変更
safe_join のメソッドシグネチャが def safe_join(array, sep = $,) から def safe_join(array, sep = nil) へ変更され、デフォルト引数が nil となりました。これにより、呼び出し側がセパレータを省略した場合は区切り文字が付与されません。
@@
- def safe_join(array, sep = $,)
+ def safe_join(array, sep = nil)
sep = ERB::Util.unwrapped_html_escape(sep)
array.flatten.map! { |i| ERB::Util.unwrapped_html_escape(i) }.join(sep).html_safe
テストコードでも $, に依存したケースが削除され、safe_join の既定動作が nil になることを検証するテストが残されています。加えて、to_sentence メソッドが内部で safe_join を呼び出す際に $, の影響を受けないことを確認するテストが追加されました。
この変更は ActionView の公開 API に対する互換性を保ちつつ、デフォルト引数の振る舞いだけを明示的に変更しています。既存コードで明示的にセパレータを渡している箇所は影響を受けません。
設計判断
$, の非推奨化は Ruby 側で長らく行われているため、Rails が独自にデプリケーションサイクルを設ける必要はなく、即時にデフォルトを削除する方針が採用されました。未文書化の挙動を残すことは API の一貫性を損ねるため、ドキュメントと実装を合わせて整理する判断が下されました。
デフォルトを nil としたことで、safe_join の呼び出し側は「区切り文字が不要」か「明示的に指定」かをはっきり表現できます。これは Ruby の標準的な Array#join の振る舞いと一致し、学習コストの低減にも寄与します。
結果として、Ractor 安全性の確保と API の明瞭化が同時に実現され、将来的なメンテナンス負荷が減少します。
まとめ
この PR は safe_join のデフォルトセパレータから $, を除去し、Ruby の既定値 nil を採用した変更です。未文書化かつ非推奨の挙動を即時に廃止することで、Ractor 環境での安全性と API の一貫性が向上します。