data icon indicating copy to clipboard operation
data copied to clipboard

Assertion after saving a new relational record and tried to forget it

Open Jopie01 opened this issue 1 year ago • 4 comments

Today I upgraded to the bleeding edge 5.4.0-alpha.22 version and after a small change (replacing the __storeWrapper with _capabilities) in my code everything seemed still to work.

Creating a new relational record (hasMany) in the store works perfectly well and I can save it successfully to the backend. Because the new record is a relational one (one or more levels deeper then the main record) the backend doesn't return an id, just an empty array to let know that everything was successful.

So after the successful save action, I walk over the saved records and records with a negative id (all new records I create will get a negative id) I will forget them by doing this.identifierCache.forgetRecordIdentifier(identifier) which worked well in 5.5.0-alpha.11. Then I reloaded the main record with a force reload from the backend so the cache got refreshed.

However, somehow this doesn't work anymore and the record with the negative id stays in the store. When I reload the main record with it's relational fields I get Uncaught (in promise) Error: Assertion Failed: Expected to receive a stable Identifier to subscribe to. Placing a debugger shows that the list of records are loaded from the backend including the new one with the right id but the record with the negative id is also in the list which should have been removed.

Jopie01 avatar Feb 20 '24 23:02 Jopie01

might need to pair with you to debug this one. There's things happening here we don't particularly support but the general flow is something we do support.

runspired avatar Feb 21 '24 01:02 runspired

I want to add some more context to understand the issue a bit better.

I'm using my own CRUD functions inside the store to request data from the backend. I also use my own tracker to track relationship changes (I hope it will be the Ember Data one day). When creating or saving a record I loop over all the changed fields in the model. When a field is a relational field, I go to that model and loop over all those fields as well and continue to do so until we are at the 'bottom'. During this process I build the data structure for the backend.

As an example, I have a product model which has a supplier field which is a hasMany. The supplier also have a hasMany field for prices for certain quantities.

product
  |
  |-> name
  |-> code
  |-> supplier
        |
        |-> supplier_name
        |-> product_code
        |-> product
        |-> price
             |
             |-> product_supplier
             |-> unit_price
             |-> quantity

I have an existing product with an existing supplier which I want to change. I also want to add a new supplier. When everything is done, I save the product, hence save because the product already exists. The save function starts at the product model and goes down until the price model and get all the changes and put them in the data structure.

{
  "method": "model.product.write",
  "params": [
    [4950],                   <-- actual product id in the backend
    {
      "supplier": [           <-- hasMany on the product model
        ["write",             <-- changes to an existing record in the backend
          [1094],             <-- the id of that record
          {
            "code": "3422",
            "price": [        <-- hasMany on the supplier model
              ["create",      <-- create a new record for the price 
                [
                  {
                    "product_supplier": 1094,
                    "quantity": 10,
                    "unit_price": 23
                  }
                ]
              ]
            ]
          }
        ],
        ["create",            <-- create a new record for the supplier
          [
            {
              "code": "Code for supplier",
              "name": "Name of product of supplier",
              "price": [
                ["create",    <-- create two new records for the new supplier
                  [
                    {
                      "product_supplier": -3,
                      "quantity": 5,
                      "unit_price": 10,
                    },
                    {
                      "product_supplier": -3,
                      "quantity": 10,
                      "unit_price": 4
                    }
                  ]
                ]
              ],
              "product": 4950
            }
          ]
        ]
      ]
    },
  ]
}

The structure seems a bit weird, but it just works perfectly and the backend return null when everything is successfully stored.

Because I know which records are new, I want to forget them and fetch them again from the backend. So after everything is stored I walk over them and do this.identifierCache.forgetRecordIdentifier(identifier). Then I return back from my save record function (resolve the promise) and do a this.store.findRecord from the product model with the right id and a force reload to refresh the cache. This step errors now because the identifier with negative id still exists.

~~I don't know it it helps but it seems it is something somewhere between commit bfc69def926a41ad92deb32d0777eaaab6c4a0b8 and commit e5c79aaaea423648b2773d64c013275aa2969e83~~ (my bad, it isn't) I will see if I can narrow it down to one particular commit.

Jopie01 avatar Feb 21 '24 10:02 Jopie01

Ok, I was thinking that I messed up my installation, but as it turned out, the issue existed a bit longer than I expected. I'm not going into the messy details about it, it already took me too much time.

I cloned this repo and linked the different packages:

    "@ember-data/debug": "link:../data/packages/debug",
    "@ember-data/graph": "link:../data/packages/graph",
    "@ember-data/json-api": "link:../data/packages/json-api",
    "@ember-data/request": "link:../data/packages/request",
    "@ember-data/store": "link:../data/packages/store",
    "@ember-data/tracking": "5.4.0-alpha.22",
    "@warp-drive/core-types": "0.0.0-alpha.8",

I wasn't able to link the tracking package because then something about private_infrastucture pops up and errors. I also needed the last one to get a successful build. Doing this didn't influence the result.

After some rough testing of several commits, I came across commit be9f3a8e5a5c3ec2386b639e66e80f5c830e1f6e which didn't had the error. However the commit above it (347359671e6baf15be7a5d34c296879337c96a57) did have the error and is linked to PR #8807. I didn't dig further.

Jopie01 avatar Feb 21 '24 15:02 Jopie01

I was able to narrow it down to the resetOnRemoteUpdate key in the field options. Because when not set (default is true), I got a deprecation warning linked to https://deprecations.emberjs.com/ember-data/v5.x#toc_ember-data-deprecate-relationship-remote-update-clearing-local-state (BTW the link in the deprecation itself is missing the #toc_ part). Setting it to false, removes the deprecation, but gives me the assertion error. Keeping true shows the deprecation but the rest is loaded correctly.

For now, I will keep resetOnRemoteUpdate as true and ignore the deprecation warning.

Jopie01 avatar Feb 21 '24 15:02 Jopie01