【发布时间】:2015-08-25 09:39:29
【问题描述】:
上下文:从外部源转码字符串以保存在数据库中
从 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>,所以我是passing the source encoding as the second argument。产生s 的宝石可能不是知道它从中读取字符串的文件是latin1 编码的。)
挑战:避免对目标编码进行硬编码
我突然想到,关于数据库字符串编码的知识不属于我执行此操作的代码部分,因此也不属于转码。
我可以向模型的类询问数据库的编码:
MyRecord.connection.encoding
虽然这不会返回 Ruby Encoding 对象,但它会返回一个包含编码名称的字符串。幸运的是,Encoding 类 can be queried with names(和 some aliases)可以查找编码:
Encoding.find 'UTF-8' # returns #<Encoding:UTF-8>, the value of Encoding::UTF_8
不幸的是,使用了不同的命名约定: MyRecord.connection.encoding 返回 'UTF8'(no 减号)而 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
我猜两者都只返回动态生成的集合或底层集合的副本,这是有充分理由的。
【问题讨论】:
标签: ruby-on-rails ruby postgresql character-encoding rails-activerecord