data
data copied to clipboard
Assertion after saving a new relational record and tried to forget it
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.
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.
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.
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.
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.