Rails ActiveRecord 字符串字段编码 vs Ruby 字符串编码

Rails ActiveRecord string field encoding vs Ruby String encoding

上下文:对来自外部源的字符串进行转码以保存在数据库中

从 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.encodingreturns#<Encoding:ASCII-8BIT>而不是#<Encoding:ISO-8859-1> ,我是 passing the source encoding as the second argument。生成 s 的 gem 可能不知道它从中读取字符串的文件是 latin1 编码的。)

挑战:避免对目标编码进行硬编码

我突然想到,关于数据库字符串编码的知识不属于我执行此持久化和转码的代码部分。

我可以向模型的 class 询问数据库的编码:

MyRecord.connection.encoding

这不会 return Ruby Encoding object though, it returns a string containing the encoding's name. Fortunately, the Encoding class can be queried with names (and some aliases) 查找编码:

Encoding.find 'UTF-8' # returns #<Encoding:UTF-8>, the value of Encoding::UTF_8

不幸的是,使用了不同的命名约定: MyRecord.connection.encoding returns 'UTF8' (no 减号)而 Encoding.find(...) 需要传递 'UTF-8' 减号)或者 'CP65001' 如果我们想要它 return #<Encoding:UTF-8>.)

太接近了。

问题:有没有干净的and/or推荐方式

避免目标编码的硬编码,而是动态确定和使用数据库的编码?

废弃的想法

我觉得对 MyRecord.connection.encoding 的结果或 Encoding.aliases() 的内容进行字符串操作或模式匹配不会比在代码中保留硬编码值更好.

修改Encoding.aliases()的return值没有任何效果:

Encoding.aliases['UTF8'] = 'UTF-8'
Encoding.find 'UTF8' # ArgumentError: unknown encoding name - UTF8

(无论如何感觉也不对),修改 #names 的 return 值也不行:

Encoding::UTF_8.names.push('UTF8')
Encoding.find 'UTF8'# ArgumentError: unknown encoding name - UTF8

我想这两者都只是 return 动态生成的集合或基础集合的副本,并且有充分的理由。

解决此问题的最简单且可以说是最干净的解决方案是不直接调用 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 不同,后者对进行编码的代码的 reader 不可见)。基于这样的 find 方法,您可以进一步实现一种方法,该方法使用 YourRecord.connection.encoding.

自动将字符串重新编码为数据库的字符串编码。

我知道让 Encoding.find 完全按照您的意愿去做会更令人兴奋,但我认为这种 "dumber" 方法实际上会更好。 :-)