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

时间:2018-02-13 18:59:00

标签: ruby-on-rails serialization hash ruby-on-rails-5

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

class Club
  serialize :social_media, Hash
end

在创建新俱乐部和输入社交媒体时,一切正常,但对于我现有的社交媒体数据:

  

ActiveRecord :: SerializationTypeMismatch:属性应该是一个哈希,但是一个ActionController :: Parameters。

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

5 个答案:

答案 0 :(得分:3)

来自fine manual

  

序列化(attr_name,class_name_or_coder = Object)

     

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

所以当你这样说时:

serialize :social_media, Hash

ActiveRecord将要求反序列化的social_mediaHash。但是,如vnbrs所述,ActionController::Parameters不再像以前那样使用Hash子类,并且您有一个充满序列化ActionController::Parameters个实例的表。如果您查看social_media列中的原始YAML数据,您会看到一堆字符串,如:

--- !ruby/object:ActionController::Parameters...

而不是像这样的哈希:

---\n:key: value...

您应该修复所有现有数据,以便在social_media而不是ActionController::Parameters中使用YAMLized哈希,以及其他任何内容。这个过程会有些不愉快:

  1. 将每个social_media拉出表格作为字符串。
  2. 将YAML字符串解压缩到Ruby对象中:obj = YAML.load(str)
  3. 将该对象转换为哈希:h = obj.to_unsafe_h
  4. 将该哈希值写回YAML字符串:str = h.to_yaml
  5. 将该字符串放回数据库以替换(1)中的旧字符串。
  6. 请注意(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_ofall代替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)

在Rails 4上运行迁移以为Rails 5准备数据。

我们正在经历完全相同的事情,除了我们序列化为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。