Почему я получаю эту ошибку "не могу модифицировать замороженную хэш"?

У меня есть модель Person и модель Item. У человека много предметов, и предмет принадлежит человеку.

В этом коде мне нужно удалить существующие элементы для человека и создать новые из параметра (который представляет собой массив хэшей). Затем мне нужно обновить одно из полей элемента, основанное на одном из его других полей.

@person = Person.find(params["id"])

@person.person_items.each do |q|
    q.destroy
end

person_items_from_param = ActiveSupport::JSON.decode(params["person_items"])

person_items_from_param.each do |pi|
    @person.person_items.create(pi) if pi.is_a?(Hash)
end

@person.person_items.each do |x|
    if x.item_type == "Type1"
        x.item_amount = "5"
    elsif x.item_type == "Type2"
        x.item_amount = "10"
    end
    x.save
end

В строках x.item_amount = "5" и x.item_amount = "10" я получаю эту ошибку:

RuntimeError in PersonsController#submit_items
can't modify frozen hash 

Как я могу это исправить? Спасибо за чтение.

Ответ 1

Я бы заподозрил

ActiveSupport::JSON.decode(params["person_items"])

возвращает замороженный хеш, который затем используется для создания объектов

@person.person_items.create(pi) if pi.is_a?(Hash)

И так как он заблокирован, вы не можете его изменить.

Вы могли

а Сделайте глубокую копию объекта JSON

или

В Перезагрузите экземпляр модели, который должен восстановить объект, делающий поля незамеренными.

Вариант A является "лучшим" решением, но сложным, потому что единственный способ, которым я знаю глубокое копирование, - сериализация, десериализация и создание объекта и назначение возвращаемого значения.

Ответ 2

Если вы используете q.destroy перед сохранением элемента, вы получите ошибку. лучше сначала сохраните элемент, а затем используйте destroy.

Ответ 3

Вы можете обойти это, если вы снова прочитаете person_items из базы данных, а не используете ассоциацию. Связь устарела и указывает на уничтоженные строки.

Вместо @person.person_items.each do |x|

Try PersonItem.where(:person_id=>@person.id).each do |x|

Ответ 4

Вы можете сделать глубокую копию любого объекта в рельсах, включая JSON, так что просто сделайте это.  Помните, что clone сохраняет замороженное состояние, а dup - нет.

Самый простой способ исправить ошибку can't modify frozen Array - это dup этот замороженный массив;)

person_items_from_param = ActiveSupport::JSON.decode(params["person_items"]).dup