在 Ruby/Rails 開發中,常需根據字串動態獲取常數 (類別/模組)。本文說明 String#constantize (Rails 擴充) 與 Object.const_get (Ruby 核心) 的用法、差異、巢狀命名空間處理與安全性考量。
ActiveSupport 擴充 String 提供 constantize,支援巢狀命名空間:
'ChinaMag'.constantize # => ChinaMag
'Test::Unit'.constantize # => Test::Unit
'Admin::UsersController'.constantize # => Admin::UsersController
特點:自動處理 :: 分隔、忽略前導 ::、拋出 NameError 若常數不存在。
不依賴 Rails,純 Ruby 可用:
Object.const_get('ChinaMag') # => ChinaMag
Object.const_get('Test::Unit') # => Test::Unit
# 巢狀命名空間需自行分割遞迴
'Foo::Bar'.split('::').inject(Object) { |mod, name| mod.const_get(name) }
# => Foo::Bar
| 特性 | String#constantize | Object.const_get |
|---|---|---|
| 依賴 | Rails (ActiveSupport) | Ruby 核心 |
| 巢狀命名空間 | 內建支援 | 需自行分割遞迴 |
| 前導 :: | 自動忽略 | 需自行處理 |
| 錯誤類型 | NameError | NameError |
| 效能 | 稍慢 (額外解析) | 極快 |
module ConstantResolver
module_function
# 安全獲取,不存在回傳 nil 而非拋出異常
def safe_get(name, default = nil)
return default if name.blank?
name.split('::').inject(Object) do |mod, const|
mod.const_defined?(const, false) ? mod.const_get(const) : (return default)
end
rescue NameError
default
end
# 僅允許白名單命名空間 (安全性關鍵)
def safe_get_whitelisted(name, *allowed_namespaces)
return nil if name.blank?
parts = name.split('::')
return nil unless allowed_namespaces.any? { |ns| name.start_with?(ns + '::') || name == ns }
safe_get(name)
end
end
# 使用範例
ConstantResolver.safe_get('User') # => User
ConstantResolver.safe_get('NonExistent') # => nil
ConstantResolver.safe_get_whitelisted('Admin::User', 'Admin', 'Api') # => Admin::User
ConstantResolver.safe_get_whitelisted('Evil::Code', 'Admin', 'Api') # => nil (阻擋)
type 欄位,動態實例化對應類別絕不可直接對使用者輸入呼叫 constantize/const_get。攻擊者可注入任意常數名稱 (如
Kernel,Process,FileUtils) 執行任意程式碼、讀取檔案、執行系統指令。
const_defined?(name, false) 先檢查 (false = 不查祖先)NameError 統一處理若類別尚未被自動載入 (Zeitwerk/Classic),constantize 會觸發自動載入機制。確保:
app/services/admin/user_service.rb → Admin::UserService)autoload_paths 中原文標題錯誤提示:原標題為「Java中List與Map初始化的一些寫法分享」,但實際內容為 Ruby/Rails
constantize與const_get。本文已依實際內容重寫。