我目前正在将Ruby on Rails应用程序从4.2升级到5.0,并且遇到了关于将数据存储为序列化哈希的字段的障碍。例如,我有
class Club
serialize :social_media, Hash
end
在创建新俱乐部和输入社交媒体时,一切正常,但对于我现有的社交媒体数据:
ActiveRecord :: SerializationTypeMismatch:属性应该是一个哈希,但是一个ActionController :: Parameters。
如何将ActionController::Parameter
个对象的所有现有数据转换为简单的哈希值?数据库是mysql。
答案 0 :(得分:3)
来自fine manual:
序列化(attr_name,class_name_or_coder = Object)
[...]如果指定了
class_name
,则序列化对象必须在赋值和检索时属于该类。否则将引发SerializationTypeMismatch
。
所以当你这样说时:
serialize :social_media, Hash
ActiveRecord将要求反序列化的social_media
为Hash
。但是,如vnbrs所述,ActionController::Parameters
不再像以前那样使用Hash
子类,并且您有一个充满序列化ActionController::Parameters
个实例的表。如果您查看social_media
列中的原始YAML数据,您会看到一堆字符串,如:
--- !ruby/object:ActionController::Parameters...
而不是像这样的哈希:
---\n:key: value...
您应该修复所有现有数据,以便在social_media
而不是ActionController::Parameters
中使用YAMLized哈希,以及其他任何内容。这个过程会有些不愉快:
social_media
拉出表格作为字符串。obj = YAML.load(str)
。h = obj.to_unsafe_h
。str = h.to_yaml
。请注意(3)中的to_unsafe_h
来电。只需在ActionController::Parameters
实例上调用to_h
(或to_hash
)就会在Rails5中出现异常,您必须先包含permit
调用才能过滤参数:
h = params.to_h # Exception!
h = params.permit(:whatever).to_h # Indifferent access hash with one entry
如果您使用to_unsafe_h
(或to_unsafe_hash
),那么您将获得HashWithIndifferentAccess
中的所有内容。当然,如果你真的想要一个普通的哈希,那你就说:
h = obj.to_unsafe_h.to_h
打开无关的访问包装器。这也假设您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
如果您当然有很多find_in_batches
,那么您想要使用in_batches_of
或all
代替Club
。
如果您的MySQL支持json
列,而ActiveRecord支持MySQL json
列(对不起,PostgreSQL家伙),那么这可能是将列更改为{{}的好时机1}}并远离json
。
答案 1 :(得分:2)
简短地回答-一种不需要数据库迁移的解决方案:
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
现在club.social_media
可以在Rails 4或Rails 5上创建。
答案 2 :(得分:1)
@schor的回复是救生员,但是在执行YAML.load(value)时,我始终遇到no implicit conversion of nil into String
错误。
对我有用的是:
class Foo < ApplicationRecord
class NewSerializer
def self.load(value)
return {} if !value #### THIS NEW LINE
obj = YAML.load(value)
if obj.respond_to?(:to_unsafe_h)
obj.to_unsafe_h
else
obj
end
end
def self.dump(value)
if value.respond_to?(:to_unsafe_h)
YAML.dump(value.to_unsafe_h)
else
YAML.dump(value)
end
end
end
serialize :some_hash_field, NewSerializer
end
我必须让Rails团队完全忽略了我,这是一个非常不受欢迎的重大更改,甚至不允许应用程序获取“旧”数据。
答案 3 :(得分:0)
official Ruby on Rails documentation有一个关于在Rails版本之间升级的部分,它解释了您有关错误的更多信息:
ActionController::Parameters
不再继承HashWithIndifferentAccess
在您的应用程序中调用params现在将返回一个对象而不是一个哈希。如果您的参数已被允许,则无需进行任何更改。如果您不管permitted?
,您需要将应用程序升级到第一个许可,然后转换为哈希。
params.permit([:proceed_to, :return_to]).to_h
答案 4 :(得分:0)
我们正在经历完全相同的事情,除了我们序列化为ActiveSupport::HashWithIndifferentAccess
而不是序列号Hash
,我建议这样做,但是我将在这里提供答案简单的Hash
。
如果您尚未升级到Rails 5(我希望您还没有升级,并且您的测试已发现此问题),则可以在Rails 4分支上运行迁移,这将为Rails 5准备数据。 >
在Rails 4中,它实际上将所有记录从ActionController::Parameters
重新序列化为Hash
,而ActionController::Parameters
仍从HashWithIndifferentAccess
继承。
class ConvertSerializedActionControllerParametersToHashInClubs < ActiveRecord::Migration
disable_ddl_transaction! # This prevents the locking of the table (e.g. in production).
def up
clubs = Club.where.not( social_media: nil )
total_records = clubs.count
say "Updating #{ total_records } records."
clubs.each.with_index( 1 ) do |club, index|
say "Updating #{ index } of #{ total_records }...", true
club.social_media = club.social_media.to_h
club.social_media_will_change!
club.save
end
end
def down
puts "Cannot be reverse! See backup table."
end
end
如果您有多个需要转换的列,则很容易修改此迁移以转换所有必要的表和列。
取决于执行此操作的时间,您的数据应已准备好用于Rails 5。