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。
从精美的手册中:
序列化(attr_name,class_name_or_coder =对象)
[...]如果
class_name指定,则序列化对象在分配和检索时必须属于该类。否则SerializationTypeMismatch会引发。
所以当你这样说:
serialize :social_media, Hash
Run Code Online (Sandbox Code Playgroud)
ActiveRecord要求将未序列化的内容social_media为Hash。但是,正如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其中的任何其他哈希。这个过程会有些不愉快:
social_media字符串从表中拉出。obj = YAML.load(str)。h = obj.to_unsafe_h。str = h.to_yaml。注意(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::Parameters,social_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_batches或in_batches_of代替。allClub
如果您的MySQL支持json列,并且ActiveRecord可与MySQL的json列配合使用(对不起,这里的PostgreSQL家伙),那么这可能是将列更改为json并远离的好时机serialize。
扩展简短的回复 - 不需要数据库迁移的解决方案:
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 上创建的,都可以工作。