Rails `auto_link` 輔助方法在 UTF-8 環境下將 URL 後緊跟的中文誤判為 URL 一部分,根因是 ActiveSupport 修改了 Ruby 字符串編碼行為,導致正則 `\w` 與 `[A-Za-z0-9_]` 行為不一致。本文記錄排查過程與修復方案。
輸入: "https://www.javaeye.com網站很不錯" 預期: <a href="https://www.javaeye.com">https://www.javaeye.com</a>網站很不錯 實際: <a href="https://www.javaeye.com網站很不錯">https://www.javaeye.com網站很不錯</a>
NetBeans Ctrl+O 打開 `text_helper.rb`,核心正則 `AUTO_LINK_RE` 中使用 `\w` 匹配域名/路徑字符。
純 Ruby 環境(Unicode 內部編碼):
irb> name = "范凱" irb> name.match /\w+/ # => nil irb> name.match /[A-Za-z0-9_]+/ # => nil
載入 Rails 環境(`script/console`,UTF-8 內部編碼):
>> name = "范凱" >> name.match /\w+/ # => #<MatchData "范凱"> 匹配上了! >> name.match /[A-Za-z0-9_]+/ # => nil
結論:ActiveSupport 將字符串按 UTF-8 操作,導致 `\w` 能匹配中文字符,而字符類 `[A-Za-z0-9_]` 仍僅匹配 ASCII。兩者在 Ruby 標準語義下本應等價, Rails 打破了這一約定。
在 `application_helper.rb` 中重新定義 `AUTO_LINK_RE` 和 `auto_link_urls`,將所有 `\w` 替換為显式字符類 `0-9A-Za-z_`:
AUTO_LINK_RE = %r{
( <\w+.*?> | [^=!:'"/] | ^ )
( (?:https?://) | (?:www\.) )
( [-0-9A-Za-z_]+ (?:\.[-0-9A-Za-z_]+)* (?::\d+)?
(?:/(?:(?:[~0-9A-Za-z_\+%-]|(?:[,.;:][^\s$]))+)?)*
(?:\?[0-9A-Za-z_\+%&=.;-]+)? (?:\#[0-9A-Za-z_\-]*)? )
} x unless const_defined?(:AUTO_LINK_RE)
def auto_link_urls(text, href_options = {})
# ... 同源碼邏輯,使用修正後的 AUTO_LINK_RE ...
end
專案啟動時自動覆蓋 Rails 內建定義,`auto_link` 恢復正確截斷中文行為。
框架層修改核心類語義(String/Regexp 行為)極具隱蔽破壞力。遇到正則異常優先檢查 `$KCODE`、`ActiveSupport::Multibyte` 等全局修改。显式字符類比 `\w` 等簡寫更安全、可移植。