Rails ActiveRecord字符串字段编码与Ruby字符串编码对比

5

背景:将外部来源的字符串进行转码以保存到数据库中

我从一个 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

我猜这两个函数都只返回动态生成的集合或底层集合的副本,而且有很好的理由。
1个回答

3
这个问题最简单且最干净的解决方案是不直接调用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 相反,后者对进行编码的代码的读者不可见)。基于这样的“查找”方法,您可以进一步实现一种方法,该方法使用 YourRecord.connection.encoding 自动重新编码字符串以使用数据库的字符串编码。
我知道让 Encoding.find 完全符合您的要求会更加令人兴奋,但我认为这种“更简单”的方法实际上是更好的。 :-)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接