升级为Rails 5时,序列化为哈希的现有数据会产生错误

Mik*_*nce 2 hash serialization ruby-on-rails ruby-on-rails-5

我目前正在将Ruby on Rails应用程序从4.2升级到5.0,并且遇到了涉及将数据存储为序列化哈希的字段的障碍。例如,我有

class Club
  serialize :social_media, Hash
end
Run Code Online (Sandbox Code Playgroud)

当创建新俱乐部并输入社交媒体时,一切正常,但是对于现有的社交媒体数据,我会得到:

ActiveRecord :: SerializationTypeMismatch:该属性应为哈希,但应为ActionController :: Parameters。

如何将所有现有数据从ActionController::Parameter对象转换为简单哈希?数据库是mysql。

mu *_*ort 6

精美的手册中

序列化(attr_name,class_name_or_coder =对象)

[...]如果class_name指定,则序列化对象在分配和检索时必须属于该类。否则SerializationTypeMismatch会引发。

所以当你这样说:

serialize :social_media, Hash
Run Code Online (Sandbox Code Playgroud)

ActiveRecord要求将未序列化的内容social_mediaHash。但是,正如vnbrs所指出的那样ActionController::Parameters不再Hash像以前那样使用子类,而是拥有一个充满序列化ActionController::Parameters实例的表。如果查看列中的原始YAML数据social_media,则会看到一堆字符串,例如:

--- !ruby/object:ActionController::Parameters...
Run Code Online (Sandbox Code Playgroud)

而不是像这样的哈希:

---\n:key: value...
Run Code Online (Sandbox Code Playgroud)

您应该修复所有现有数据,以在其中使用YAML化哈希,social_media而不要使用ActionController::Parameters其中的任何其他哈希。这个过程会有些不愉快:

  1. 将每个social_media字符串从表中拉出。
  2. 将该YAML字符串解压缩为Ruby对象:obj = YAML.load(str)
  3. 将该对象转换为Hash :h = obj.to_unsafe_h
  4. 将该哈希回写到YAML字符串:str = h.to_yaml
  5. 将该字符串放回数据库中,以替换(1)中的旧字符串。

注意(3)中to_unsafe_h调用。仅在实例上调用(或为此)会在Rails5中给您一个异常,您必须包含一个调用来首先过滤参数:to_hto_hashActionController::Parameterspermit

h = params.to_h                   # Exception!
h = params.permit(:whatever).to_h # Indifferent access hash with one entry
Run Code Online (Sandbox Code Playgroud)

如果您使用to_unsafe_h(或to_unsafe_hash),则可以将整个内容放入一个HashWithIndifferentAccess。当然,如果您真的想要一个普通的旧哈希,那么您会说:

h = obj.to_unsafe_h.to_h
Run Code Online (Sandbox Code Playgroud)

也可以解开无关紧要的访问包装器。这也假设您只有这种情况ActionController::Parameterssocial_media因此您可能需要包括一项obj.respond_to?(:to_unsafe_hash)检查以查看如何解压social_media值。

您可以通过在Rails迁移中直接访问数据库来进行上述数据迁移。这可能非常麻烦,具体取决于低级MySQL接口的美观程度。另外,您可以在迁移中创建简化的模型类,如下所示:

class YourMigration < ...
  class ModelHack < ApplicationRecord
    self.table_name = 'clubs'
    serialize :social_media
  end

  def up
    ModelHack.all.each do |m|
      # Update this to match your real data and what you want `h` to be.
      h = m.social_media.to_unsafe_h.to_h
      m.social_media = h
      m.save!
    end
  end

  def down
    raise ActiveRecord::IrreversibleMigration
  end
end
Run Code Online (Sandbox Code Playgroud)

当然,如果您有很多需求,可以使用find_in_batchesin_batches_of代替。allClub


如果您的MySQL支持json列,并且ActiveRecord可与MySQL的json列配合使用(对不起,这里的PostgreSQL家伙),那么这可能是将列更改为json并远离的好时机serialize


sch*_*hor 6

扩展简短的回复 - 不需要数据库迁移的解决方案:

class Serializer
  def self.load(value)
    obj = YAML.load(value || "{}")
    if obj.respond_to?(:to_unsafe_h)
      obj.to_unsafe_h
    else
      obj
    end
  end
  def self.dump(value)
    value = if value.respond_to?(:to_unsafe_h)
      value.to_unsafe_h
    else
      value
    end
    YAML.dump(value)
  end
end

serialize :social_media, Serializer
Run Code Online (Sandbox Code Playgroud)

现在,club.social_media无论它是在 Rails 4 还是 Rails 5 上创建的,都可以工作。