[英]Rails ActiveRecord string field encoding vs Ruby String encoding
从宝石,我得到一个字符串s
有latin-1
编码过的内容和我想在Rails模型来存储。
r = MyRecord.new(mystring: s)
# ...
r.save
因为我的PostgreSQL数据库使用UTF-8
编码,所以在将字符串字段设置为字符串后保存模型会在该字符串包含某些非ASCII字符时导致错误:
ActiveRecord::StatementInvalid: PG::CharacterNotInRepertoire: ERROR: invalid byte sequence for encoding "UTF8": 0xdf 0x65
...
我可以通过转码字符串轻松解决这个问题:
r = MyRecord.new(mystring: s.encode(Encoding::UTF_8, Encoding::ISO_8859_1))
# ...
r.save
(因为r.encoding
返回#<Encoding:ASCII-8BIT>
而不是#<Encoding:ISO-8859-1>
,我将源编码作为第二个参数传递 。生成s
的gem可能不知道它从字符串读取的文件是latin1
编码。)
在我看来,关于数据库的字符串编码的知识不属于我持久化的代码部分,因此也不属于代码转换。
我可以向模型的类询问数据库的编码:
MyRecord.connection.encoding
但这不会返回Ruby Encoding
对象,它返回一个包含编码名称的字符串。 幸运的是, 可以使用名称 (和一些别名 ) 查询 Encoding
类以查找编码:
Encoding.find 'UTF-8' # returns #<Encoding:UTF-8>, the value of Encoding::UTF_8
不幸的是,使用了不同的命名约定: MyRecord.connection.encoding
返回'UTF8'
( 没有减号),而Encoding.find(...)
需要传递'UTF-8'
( 带减号)或'CP65001'
如果我们希望它返回#<Encoding:UTF-8>
。)
Sooooo关闭。
避免目标编码的硬编码,而是动态地确定和使用数据库的编码?
我不觉得对MyRecord.connection.encoding
的结果进行字符串操作或模式匹配,或者对Encoding.aliases()
的内容进行模式匹配比将代码中的硬编码值保留更好。
修改Encoding.aliases()
的返回值没有任何影响:
Encoding.aliases['UTF8'] = 'UTF-8'
Encoding.find 'UTF8' # ArgumentError: unknown encoding name - UTF8
(无论如何也感觉不对),也没有修改#names
的返回值:
Encoding::UTF_8.names.push('UTF8')
Encoding.find 'UTF8'# ArgumentError: unknown encoding name - UTF8
我猜两者都只返回动态生成的集合或底层集合的副本,这是有充分理由的。
解决这个问题的最简单,也可以说是最干净的解决方案是不直接调用Encoding.find
,但是有一个实用工具方法(可能位于lib/yourapp
的模块中),它知道你关心的编码名称差异并退回到所有其他输入的Encoding.find
:
module YourApp
module DatabaseStringEncoding
def find(name)
case name
when 'UTF8'
Encoding::UTF_8
...
else
Encoding.find(name)
end
end
end
这很容易理解和发现(而不是直接修改Encoding
,这对编码的代码的读者来说是不可见的)。 基于这样的find
方法,您可以进一步实现一个方法,该方法使用YourRecord.connection.encoding
自动将字符串重新编码为数据库的字符串编码。
我知道让Encoding.find
做你想要的更令人兴奋,但我认为这种“笨拙”的方法实际上会更好。 :-)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.