Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUGFIX] ensure destroy-sync cleanup is correct #5438

Merged
merged 3 commits into from
Apr 19, 2018

Conversation

runspired
Copy link
Contributor

@runspired runspired commented Apr 18, 2018

resolves #5424

Bug
Unloading the record from a hasMany relationship immediately prior to pushing that same record with that same relationship state back into the store results in a disconnected internal-model being left behind in the has-many relationship.

Root Cause
When the unload and push are in the same run loop, we were calling destroySync to complete the destroy and before giving the user a new InternalModel instead of cancelDestroy. Because a deletion was not signaled, destroySync did not remove the InternalModel from relationships. This is semantically what we expect from destroy, minus the return of a brand new internal-model.

Background
The destroySync logic branch was added to support unloadRecord being immediately followed by createRecord for a record with the same ID. This case is not one we wish to support long-term, once there is a clear ability for users to signal to the store that a record has been remotely deleted.

Fix
The createRecord codepath utilizes _existingInternalModelForId() to check for existing internal models. This method is where we've added the destroySync logic previously.

The _internalModelForId() also implemented a check and destroySync call, but did so unnecessarily (poorly constructed test scenarios led us to believe this was correct when it wasn't). This meant that on push we also called destroySync when what we wanted was cancelDestroy. We now call cancelDestroy.

@runspired
Copy link
Contributor Author

also cc @efx we had the mechanism "almost" correct in our previous attempt at testing for this, we were missing a key detail though: the push of new data must occur in the same run-loop as the unload.

@runspired
Copy link
Contributor Author

runspired commented Apr 18, 2018

Ironically this is roughly what I think we will need (was reverted) #5058

EDIT they were previously un-reverted and then repurposed. However they turn out to be not what we need here.

@ghost
Copy link

ghost commented Apr 18, 2018

Ah, nice find! Do you recall the reason those changes were reverted? e.g. caused other issues?

@runspired runspired changed the title [WIP][BUGFIX] ensure destroy-sync cleanup is correct [BUGFIX] ensure destroy-sync cleanup is correct Apr 18, 2018
@runspired runspired requested a review from hjdivad April 18, 2018 21:38
Copy link
Member

@hjdivad hjdivad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good.

I have a minor cleanup comment but i'd be happy to merge without it.

I would like to know why the relationship payload changes from data: null to data: [] and want to confirm that this is just aesthetic and either one would work. If the relationship payloads are not equivalent i think it would be a regression.

// The createRecord path will take _existingInternalModelForId()
// which will call `destroySync` instead for this unload + then
// sync createRecord scenario. Once we have true client-side
// delete signaling, we should never call destroySync
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These comments are good but this logic relies on _internalModelForId and _existingInternalModelForId being called from separate code paths which is nonobvious and brittle.

I'm 👍 on merging this to get the fix in, but we'll want to clean this up.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My hope is that once we have remove operations we will simply stop supporting unloadRecord + createRecord which is the only reason why this divergence exists. Instead you would be required to remove before the add to ensure the store fully forgets about the previous record by that ID.

let knownBoats = store._internalModelsFor('boat');

// ensure we loaded the people and boats
assert.equal(knownPeople.models.length, 1, 'one person record is loaded');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are fine, but better assertions would match against eg an array of ids rather than just the length as that would catch more regressions.

eg

assert.deepEqual(knownPeople.models.map(m => m.id), ['1'], 'one person record is loaded');

i don't think it matters enough to not merge an important bug fix, but is an improvement we can make to the tests.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(the above comment applies to the rest of the tests that assert against size)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

cars: { data: null }
cars: {
data: []
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this payload change? these are both equivalent relationship payloads no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"cosmetic".

[] for empty collections, null for empty resource identifiers.

The spec is specific on this although we have been lax historically: http://jsonapi.org/format/#document-resource-object-linkage

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(the tests passed either way, just thought it was good to continue working us little by little closer to full spec)

@runspired
Copy link
Contributor Author

Test failure is against ember-canary for the changes there that break our use of the ENV flag (see #5437)

@runspired runspired merged commit 95c6d5d into emberjs:master Apr 19, 2018
@runspired runspired deleted the fix-destroy-sync branch April 19, 2018 03:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

unloadRecord doesn't always clean up relationships
2 participants