Существующие данные, сериализованные как массив, не сохраняются при обновлении до RoR 5.

Я почти обновил свое приложение RoR с версии 4 до версии 5, и осталось сделать еще несколько вещей, чтобы завершить процесс.

В моем приложении RoR v4 я использовал атрибуты, которые были serialized как Hash и Array:

class ModelOne < ApplicationRecord
  serialize :attribute_one_names, Hash
end

class ModelTwo < ApplicationRecord
  serialize :attribute_two_names, Array
end

Теперь мне нужно обновить записи в базе данных, чтобы они соответствовали новым требованиям RoR v5.

На основе этого ответа Я могу успешно перенести данные 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

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

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

Примечание.

Перед запуском 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

person Backo    schedule 07.01.2019    source источник
comment
Это похоже на ту же проблему, что и дубликат, но, возможно, не на 100% дубликат. Однако решение должно применяться: вручную распаковать все с помощью YAML.load, преобразовать вещи по мере необходимости, to_yaml их вручную и поместить обратно. Все это с использованием низкоуровневого доступа к базе данных. И убедитесь, что вы всегда пропускаете простые массивы и хэши через serialize (или лучше забудьте, что serialize существует, и используйте что-то разумное, например столбец JSON или отдельные таблицы). Дайте мне знать, если я далеко от базы dup-hammer.   -  person mu is too short    schedule 07.01.2019
comment
Кроме того, поскольку у вас есть массив экземпляров ActionController::Parameters и ActiveSupport::HashWithIndifferentAccess, вам нужно будет распаковать внешний массив, а затем преобразовать каждый элемент внутри массива в простой старый хэш. Возможно, в array_of_names << name.to_unsafe_h.to_h что-то идет не так. Тьфу, serialize это дьявол.   -  person mu is too short    schedule 07.01.2019
comment
@muistooshort ОК, это ВЫГЛЯДИТ на ту же проблему, что и дубликат, но, возможно, не на 100% дубликат. Можете ли вы привести пример того, что вы пишете? Я немного смущен.   -  person Backo    schedule 07.01.2019
comment
Кажется, они изменили принцип работы дублирующего молотка, поэтому я не могу открыть его самостоятельно. В любом случае, попробуйте заменить m.attribute_two_names.each ... на array_of_names = m.attribute_two_names.map(&:as_json) (т.е. используйте #as_json для преобразования всего в простые массивы и хэши, а не пытайтесь сделать это с #to_unsafe_h и #to_h). И почему вы используете базу данных?   -  person mu is too short    schedule 07.01.2019
comment
@muistooshort Я пытался использовать array_of_names = m.attribute_two_names.map(&:as_json), как вы написали, но я все еще получаю базу данных, заполненную всеми значениями --- [].   -  person Backo    schedule 07.01.2019
comment
@muistooshort Я обновил вопрос, добавив выведенную информацию во время миграции, если это может помочь.   -  person Backo    schedule 07.01.2019
comment
@muistooshort Я использую MySQL   -  person Backo    schedule 08.01.2019
comment
Что произойдет, если вы удалите serialize :attribute_two_names из ModelTwo, а затем скажете m.attribute_two_names = array_of_names.to_yaml (т. е. сами разберетесь с YAML-изацией)? Поддерживает ли ваша версия MySQL столбцы JSON? Возможно, самое время заменить serialize чем-то вменяемым.   -  person mu is too short    schedule 08.01.2019
comment
В этом случае вы будете делать что-то вроде m.attribute_two_names = YAML.load(m.attribute_two_names).as_json.to_yaml; m.save!.   -  person mu is too short    schedule 08.01.2019
comment
@muistooshort Если я запускаю m.attribute_two_names = array_of_names.to_yaml, я получаю неожиданный токен ошибки 751 в '--- [].   -  person Backo    schedule 12.01.2019
comment
Вы удалили часть serialize :attribute_two_names? Я предлагаю вам обрабатывать столбец как текст при миграции и вручную обрабатывать YAML.   -  person mu is too short    schedule 12.01.2019
comment
Опс! Теперь я получаю сообщение об ошибке, не могу сериализовать attribute_two_names: должен был быть массивом, но был строкой. -- --- []\n   -  person Backo    schedule 12.01.2019
comment
Это говорит о том, что он использует реальную версию ModelTwo из app/models/model_two.rb, а не ту, которую вы определяете в своей миграции.   -  person mu is too short    schedule 12.01.2019


Ответы (1)


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

person mu is too short    schedule 12.01.2019
comment
Я получаю сообщение об ошибке без неявного преобразования массива в строку в YAML.load(m.attribute_two_names). - person Backo; 12.01.2019
comment
Это происходит, когда вы YAML.load(['some','array']), поэтому вы, вероятно, не используете ModelTwoForMigrationChicanery хитрость и serialize проскальзывает из основного app/models/ файла. - person mu is too short; 12.01.2019