Skip to content

Hashdiff does not see change if nested object is updated in place due to same reference value in resource object and original_state #1331

Open
@kaarelss

Description

Issue summary

HashDiff does not see change if nested resource object is updated in place. When resource object is created with #create_instance method, it adds value to both resource and to the original_state. If you update the object in place, the change will be reflected in original_state aswell which is wrong.

Expected behavior

All values in original_state should be duplicates, not references.

Steps to reproduce the problem

  1. Install gems gem install shopify_api rspec
  2. Create ruby script with the content below
  3. Execute script with rspec bundle exec rspec ${script_file_name}
it "should reproduce issue with reference value in original_state" do
    resource_hash = {
        name: "#111",
        line_items: [
          { title: "One", price: 100, quantity: 1 },
        ],
        shipping_address: {
          first_name: "John",
          last_name: "Doe",
          address_1: "Freedom street",
        },
    }.values_as_hash

    draft_order = ShopifyAPI::DraftOrder.new(from_hash: resource_hash)
    draft_order.save!
    expect(draft_order.shipping_address["first_name"]).to eq("John")

    # This changes original_state which is wrong
    draft_order.shipping_address["first_name"] = "Tom"
    draft_order.save!

    expect(draft_order.shipping_address["first_name"]).to eq("Tom")

    # It is only possible to update it by setting whole object like this
    updated_address = draft_order.shipping_address.values_as_hash
    updated_address[:first_name] = "Tom"
    draft_order.shipping_address = updated_address
    draft_order.save!

    expect(draft_order.shipping_address["first_name"]).to eq("Tom")
end

This is our current workaround to fix this issue.

module ShopifyAPI
  module Rest
    module BaseExtension

      def create_instance(data:, session:, instance: nil)
        result = super
        result.original_state = result.original_state.deep_dup

        result
      end

    end
  end
end

ShopifyAPI::Rest::Base.singleton_class.prepend(ShopifyAPI::Rest::BaseExtension)

This is where the actual issue is happening:

else
instance.public_send("#{attribute}=", value)
instance.original_state[attr_sym] = value
end

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions