【问题标题】:Rails ActiveRecord string field encoding vs Ruby String encodingRails ActiveRecord 字符串字段编码与 Ruby 字符串编码
【发布时间】: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 对象,但它会返回一个包含编码名称的字符串。幸运的是,Encodingcan 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',如果我们希望它返回#&lt;Encoding:UTF-8&gt;。)

太接近了。

问题:有没有干净和/或推荐的方法

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

放弃的想法

我不觉得对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


    【解决方案1】:

    对于这个问题,最简单、可以说是最干净的解决方案是不直接调用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 完全按照您的意愿去做会更令人兴奋,但我认为这种“笨拙”的方法实际上会更好。 :-)

    【讨论】:

      猜你喜欢
      • 2013-12-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-08-19
      相关资源
      最近更新 更多