升级到RoR 5时,不存储序列化为Array的现有数据

时间:2019-01-07 17:53:10

标签: ruby-on-rails serialization activerecord migration ruby-on-rails-5.2

我几乎将我的RoR应用程序从v4升级到了v5,还有许多工作要做以完成该过程。

在我的RoR v4应用程序中,我使用的是serialize d作为哈希和数组的属性:

class ModelOne < ApplicationRecord
  serialize :attribute_one_names, Hash
end

class ModelTwo < ApplicationRecord
  serialize :attribute_two_names, Array
end

现在,我需要更新数据库中的记录,以符合新的RoR v5要求。

基于this answer,我可以通过运行以下迁移成功迁移attribute_one_names(哈希)数据:

class MigrationOneFromRor4ToRor5 < ActiveRecord::Migration[5.2]
  class ModelOne < ApplicationRecord
    self.table_name = 'model_one'
    serialize :attribute_one_names
  end

  def up
    ModelOne.all.each do |m|
      h = m.attribute_one_names.to_unsafe_h.to_h
      m.attribute_one_names = h

      m.save!
    end
  end
end

问题出在attribute_two_names(数组)数据上。

class MigrationTwoFromRor4ToRor5 < ActiveRecord::Migration[5.2]
  class ModelTwo < ApplicationRecord
    self.table_name = 'model_two'
    serialize :attribute_two_names
  end

  def up
    ModelTwo.all.each do |m|
      array_of_names = []
      m.attribute_two_names.each do |name|
        array_of_names << name.to_unsafe_h.to_h
      end

      # Output 1:
      puts array_of_names.inspect 
      # => [{"name"=>"Website1Name", "url"=>"http://www.website1.com"}, {"name"=>"Website2Name", "url"=>"http://www.website2.com"}]
      puts m.attribute_two_names.inspect 
      # => [<ActionController::Parameters {"name"=>"Website1Name", "url"=>"http://www.website1.com"} permitted: false>, <ActionController::Parameters {"name"=>"Website2Name", "url"=>"http://www.website2.com"} permitted: false>]

      m.attribute_two_names = array_of_names

      # Output 2:
      puts m.attribute_two_names.inspect
      # => [{"name"=>"Website1Name", "url"=>"http://www.website1.com"}, {"name"=>"Website2Name", "url"=>"http://www.website2.com"}]

      m.save!

      # Output 3:
      puts m.attribute_two_names.inspect 
      # => []
    end

  end
end

实际上,通过运行此迁移,无论序列化为Array的现有数据如何,--- []值都存储在数据库中。也就是说,无论数据库中是否存在先前的数据,都会为每个记录存储一个--- []值。

我该如何解决问题?

注意

在运行MigrationTwoFromRor4ToRor5之前,attribute_two_names数据库列中的值如下:

---
- !ruby/hash:ActionController::Parameters
  name: Website1Name
  url: http://www.website1.com
- !ruby/hash:ActionController::Parameters
  name: Website2Name
  url: http://www.website2.com

---
- !map:ActiveSupport::HashWithIndifferentAccess 
  name: Website1Name
  url: http://www.website1.com
- !map:ActiveSupport::HashWithIndifferentAccess 
  name: Website2Name
  url: http://www.website2.com

1 个答案:

答案 0 :(得分:0)

您可以尝试手动解压缩YAML以获得原始的旧哈希和数组,然后手动重新对该数据进行YAML。像这样:

class MigrationTwoFromRor4ToRor5 < ActiveRecord::Migration[5.2]
  # Make sure this name is not used by any real models
  # as we want our own completely separate interface to
  # the `model_two` table.
  class ModelTwoForMigrationChicanery < ApplicationRecord
    self.table_name = 'model_two'
    # We'll be treating `attribute_two_names` as just
    # a blob of text in here so no `serialize`.
  end

  def up
    ModelTwoForMigrationChicanery.all.each do |m|
      # Unpack the YAML that `serialize` uses.
      a = YAML.load(m.attribute_two_names)

      # Using `as_json` is a convenient way to unwrap 
      # all the `ActionController::Parameters` and
      # `ActiveSupport::HashWithIndifferentAccess`
      # noise that go into the database. `#as_json`
      # on both of those give you plain old hashes.
      a = a.as_json

      # Manually YAMLize the cleaned up data.
      m.attribute_two_names = a.to_yaml

      # And put it back in the database.
      m.save!
    end
  end
end

一旦所有这些问题都解决了,您应该在模型和控制器中添加一些内容,以确保只有简单的数组和散列能够传递到serialize。我还建议您从serialize移到类似JSON列类型(如果您的数据库支持它,而ActiveRecord支持您的数据库支持)之类的东西。