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

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

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

@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 

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

4 ответов


Я подозреваю

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

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

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

и поскольку он заморожен, вы не можете его изменить.

Вы можете

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

или

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

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


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


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

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

попробовать PersonItem.where(:person_id=>@person.id).each do |x|


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

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

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