背景:将外部来源的字符串进行转码以保存到数据库中
我从一个 gem 中获取了一个字符串 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>
。
就差一点点了。
问题:是否有一种干净和/或推荐的方法来避免硬编码目标编码,而是动态确定并使用数据库的编码呢?
丢弃的想法
我认为对 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
我猜这两个函数都只返回动态生成的集合或底层集合的副本,而且有很好的理由。