Существующие данные, сериализованные в массив, не сохраняются при обновлении до RoR 5 - PullRequest
0 голосов
/ 07 января 2019

Я почти обновил свое приложение 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.

На основании этого ответа Я могу успешно перенести данные attribute_one_names (Hash), выполнив следующую миграцию:

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 (Array).

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

Фактически, запустив эту миграцию, значения --- [] сохраняются в базе данных независимо от существующих данных, сериализованных как массив. То есть значение --- [] сохраняется для каждой записи независимо от ранее данных, присутствующих в базе данных.

Как мне решить проблему?

Примечание

Перед запуском 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 голосов
/ 12 января 2019

Вы можете попробовать распаковать 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 поддерживает его поддержку вашей базы данных).

...