Simplify PRE_CONTENT_STRING hash
Rails 7 removes the default empty‑string proc from PRE_CONTENT_STRINGS and relies on Ruby 3.1’s optimisation that "#{nil}" no longer allocates a new String. The change shortens the constant definition while preserving the existing tag‑rendering behaviour.
背景
PRE_CONTENT_STRINGS was built with a shareable proc that returned a frozen empty string to avoid object allocations when interpolating nil values in content_tag_string. This defensive default was introduced because, prior to Ruby 3.1, the expression "#{nil}" created a fresh empty String, inflating per‑request allocations.
The framework needed the proc to keep memory usage low while remaining Ractor‑safe, so the hash was created via Hash.new(&ActiveSupport::Ractors.shareable_proc { "" }) and then frozen.
Since Ruby 3.1 optimises nil interpolation to reuse the empty string literal, the explicit default value no longer provides a memory benefit, allowing it to be dropped without functional impact.
技術的な変更
The constant is now defined as a literal frozen hash, eliminating the shareable‑proc indirection.
@@ -41,10 +41,10 @@ module TagHelper
TAG_TYPES.merge! CLASS_PREFIXES.index_with(:class)
TAG_TYPES.freeze
- PRE_CONTENT_STRINGS = Hash.new(&ActiveSupport::Ractors.shareable_proc { "" })
- PRE_CONTENT_STRINGS[:textarea] = "\n"
- PRE_CONTENT_STRINGS["textarea"] = "\n"
- PRE_CONTENT_STRINGS.freeze
+ PRE_CONTENT_STRINGS = {
+ textarea: "\n",
+ "textarea" => "\n",
+ }.freeze
# = Action View Tag Builder
#
When an unknown key is looked up, the hash now returns nil; the surrounding interpolation "#{PRE_CONTENT_STRINGS[name]}" yields an empty string automatically because Ruby 3.1 does not allocate a new String for nil.
The PR’s IRB benchmark confirms the behaviour: after the first call, both "#{a}" (where a is a frozen empty string) and "#{nil}" allocate only a single object, showing no regression after the default is removed.
Thus the modification simplifies the source while keeping tag rendering identical to the previous implementation.
設計判断
The refactor favours simplicity by discarding a custom proc that is now redundant, letting the language runtime handle the edge case. This reduces indirection and the amount of code that must be kept Ractor‑compatible.
Because the hash is still frozen, it remains shareable across Ractors, and the only keys used by the framework (:textarea and "textarea") are explicitly defined, so behaviour for all supported tags is unchanged.
The design choice lowers maintenance overhead and aligns Rails with modern Ruby semantics without sacrificing safety or performance.
まとめ
By removing an obsolete default‑value proc from PRE_CONTENT_STRINGS, Rails leverages Ruby 3.1’s nil‑interpolation optimisation, resulting in a cleaner, Ractor‑safe constant definition while keeping the rendered output unchanged.