【问题标题】:Is ActiveRecord changing the encoding on my serialized hashActiveRecord 是否更改了我的序列化哈希的编码
【发布时间】:2016-09-17 17:00:45
【问题描述】:

我有一个 Rails 应用程序,它接受来自第三方来源的 JSON 数据,我相信我遇到了一些 ActiveRecord 幕后魔术,它可以识别从 JSON 中提取的哈希中的 ASCII-8BIT 字符并保存它们无论我做什么,我的数据库都是如此。

这里是类的简化描述...

class MyClass < ActiveRecord::Base
 serialize :data
end

以及如何创建对象...

a = MyClass.new 
a.data = {
  "a" =>
    {
      "b" => "bb",  
      "c" => "GIF89a\x01\x00\x01\x00\x00\x00\x00\x00!\vNETSCAPE2.0\x03\x01\x00\x00!\x04\t\x00\x00\x01\x00,\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02L\x01\x00;"
    }
}

我相信这些是 ASCII-8BIT 字符,如果它们被保存为这样就足够公平了(尽管我尝试在任何地方都使用 UTF8)。但我需要这些字符是 UTF-8,因为当我去查看它们时,我得到:

ActionView::Template::Error ("\xEF" from ASCII-8BIT to UTF-8):
    64:       <div>
    65:         <pre><%= mc.prettify %></pre>
    66:       </div>
app/models/my_class.rb:28:in `prettify'

prettify 中的第 28 行是:

JSON.pretty_generate(self.data)

所以我试图重新编码哈希中的任何字符串。我构建了执行此操作的功能(使用anonymous classesrefinementsHashArrayString),但无论如何,ASCII-8BIT 都会返回给我。用最简单的话来说,这就是正在发生的事情:

mc = MyClass.find(123)
mc.data['a']['c'].encode!(Encoding.find('UTF-8'), {invalid: :replace, undef: :replace, replace: ''})
mc.data['a']['c'].encoding #=> #<Encoding:UTF-8>
mc.data['a']['c'].valid_encoding? #=> true
mc.save!
mc.reload
mc.data['a']['c'].encoding #=> #<Encoding:ASCII-8BIT> <-- !!!!!

ActiveRecord 在保存此哈希时对其做了什么?我该怎么做才能在serialized MySQL(v5.6,通过mysql2 gem)mediumtext列(使用Ruby 2.2.4和Rails 4.1.4)中永久存储所有编码为UTF-8的字符串的哈希?


我的.cnf

[client]
default-character-set=utf8mb4

[mysql]
default-character-set=utf8mb4

[mysqld]
# ...
skip-character-set-client-handshake
collation_server=utf8_unicode_ci
init-connect='SET NAMES utf8mb4'
character-set-server=utf8

【问题讨论】:

  • 我怀疑 ActiveRecord 正在这样做。我怀疑你的数据库是。您的表/列的排序规则设置为什么?
  • 或者我错了。这里真正的问题是您的 JSON 中有二进制数据。你真的,真的不应该,因为你最终会得到不是有效 UTF-8 的字节。这在某处引起问题:在您的数据库、Ruby 或 JSON 解析器中。即使\xNN 表示的字节也必须是有效的 UTF-8 字节——UTF-8 是文本编码,而不是二进制数据。真正的解决方案是首先使用例如以安全的方式对二进制数据进行编码。 Base64。这既常见又必要。
  • @Jordan 排序规则是 utf8_unicode_ci
  • 见我的第二条评论。您不会成功地将原始二进制数据存储为 UTF-8 文本。
  • @Jordan 因为我从不将这些显示为图像(仅文本),我仍然会将之前的字符串解码为 JSON.pretty_generate(并得到错误)或在屏幕上显示无意义的 base64。 (解析pretty_generate 输出寻找base64 会非常昂贵。)我更倾向于清空有问题的字符串。真的没有办法把它锤入 UTF-8 吗?

标签: mysql ruby-on-rails ruby activerecord utf-8


【解决方案1】:

因此,实际上并没有“ASCII-8BIT”字符这样的东西。 ASCII-8BIT 对 ruby​​ 来说本质上意味着“根本没有编码”——只是字节,而不假设任何编码。它是“二进制”的同义词。

但是,如果您的字节不是有效的 UTF-8,它们就不能真正编码为 UTF-8。即使字符串上的编码是 UTF-8,当你尝试对它做一些事情时,充其量你会得到很多 InvalidEncoding 错误。

字符串最终会被标记为什么编码取决于 ActiveRecord 和您的数据库本身之间的复杂关系——此外,数据库本身有时实际上可以更改您的字节,具体取决于数据库和如何它已经设置好了,你正在做什么。我们可以尝试准确调试您在做什么。

但实际上,答案是——如果您希望它是 UTF-8,它就不能包含二进制非 UTF8 数据。 “ASCII-8BIT”实际上二进制数据的正确编码。你实际上想做什么,那些奇怪的字节来自哪里,你为什么想要它们?一般来说,我不确定在 JSON 中放置任意非 UTF8 字节是否合法?它对 JSON 来说可能是合法的,但它可能会让你头疼(比如你正在处理的那个),因为它取决于 rails 和你的底层数据库将如何处理它们。

只是为了解决显示错误,您可以让您的 prettify 方法使用 scrub,在 ruby​​ 2.1.0 中添加以消除当前编码的坏字节。 value.force_encoding("UTF-8").scrub。这可能会消除你的错误,并且可能会做正确的事情,但最好弄清楚到底发生了什么,为什么你首先想要那些奇怪的字节,它们应该是什么意思是为了什么目的。

【讨论】:

  • 谢谢。这是一个有用的帖子,但我认为它不能回答这个问题。我认为这样做的重点是:如果我想将\x01 保存为序列化哈希中的任意值,然后完全按照您在此处看到的那样显示它,我该怎么做?如果你有兴趣追踪它,我已经添加了我的 mysql 配置文件的相关部分。
  • 我不确定你是否可以。但如果你想尝试,关键是确保序列化的哈希在数据库中被视为 UTF-8 或在检索它时被 Rails 处理。我不确定该怎么做。如果事情以 ASCII-8BIT 的形式返回,那是正确。您不能将任意数据视为 UTF8。您还可以告诉 AR serialize 序列化为 JSON 或 YAML,可能其中一个效果更好。 AR 中可能存在阻止这种情况的错误。编码处理起来很混乱。这可能是可能的,但我个人只会使用带有 UTF8 数据的 AR 序列化。
  • 如果您的数据以 ASCII-8BIT 编码的字符串形式返回,并且包含您希望它包含的字节 - 这是正确,您不需要再搞乱数据库编码。那么我猜你的问题是你如何获取 ASCII-8BIT 编码的字符串,并在浏览器中显示为"GIF89a\x01\x00...?请记住,这是字符串的转义表示,而不是“实际字符串”。我不确定如何将二进制字符串转换为该表示形式,但它是可行的(尽管是一个不寻常的请求),但一个单独的问题不再与 ActiveRecord 或数据库有关。
  • 在输出或prettify 方法中试试这个:binary_str.codepoints.pack("C*")
猜你喜欢
  • 2013-09-08
  • 2012-08-02
  • 1970-01-01
  • 2011-11-24
  • 2023-03-21
  • 2019-10-28
  • 1970-01-01
  • 2016-11-16
  • 1970-01-01
相关资源
最近更新 更多