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

[CHORE]: Extract internalModel access to identifiers in RecordArray modules #6715

Merged
merged 1 commit into from
Jul 30, 2020

Conversation

snewcomer
Copy link
Contributor

@snewcomer snewcomer commented Nov 11, 2019

This is to reduce the need of InternalModel throughout the codebase. This is possible because we have an identifier cache available to lookup records that were populated locally. This doesn't encompass all the spots where an identifier can be swapped for an InternalModel, but is a start.

Modifications include Record-Array, Record-Array-Manager, Adapter-Populated-Record-Array and Core-Store.

  1. Method calls on RecordArray give identifiers instead of internalModels
  2. Calls to Record-Array-Manager pass identifiers and expects to receive identifiers.

ref #6166

@@ -3679,75 +3679,70 @@ module('integration/relationships/has_many - Has-Many Relationships', function(h
user: belongsTo('user', { inverse: null, async: false }),
});

run(() => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

just reminding to revisit this...

@@ -122,7 +122,6 @@ export default class InternalModel {
__recordData: RecordData | null;
_isDestroyed: boolean;
isError: boolean;
_pendingRecordArrayManagerFlush: boolean;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this state was only used in record-array-manager, so put it there with a Set of pending identifiers.


function recordArraysForIdentifier(store, identifier) {
let internalModel = internalModelFactoryFor(store).peek(identifier);
if (internalModel) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

There was only one failing test that required this guard. disconnectRecord in store-wrapper-test. It is pushing with data: [] and included: [houseHash]. Is this possible?

Copy link
Contributor

Choose a reason for hiding this comment

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

Turns out M3 hit this case so I had to make some adjustments, roughly speaking it is possible in the current state of things to try to sync-destroy during push via this API. I'm going to bring this up in a meeting as EmberData does not do a sync-destroy here itself and we likely should not allow it going forward as the "syncLiveArray" code is undesirable complexity that remains from our need to handle observers greedily firing when we begin changes.

We may be able to refactor overtime to avoid delivering changes to the UI until a single "ui flush" which would help us avoid this problem.

@runspired runspired added 🏷️ chore This PR primarily refactors code or updates dependencies 🎯 canary PR is targeting canary (default) 🌲 Project Trim 🌲 PRs related to https://github.com/emberjs/data/issues/6166 labels Nov 11, 2019
@runspired
Copy link
Contributor

The M3 failure is because M3 registers query-arrays (result of M3's queryURL method) with the record-array-manager. There's a few paths forward here:

  • Intercept Approach
    • detect internalModels during creation and convert them
    • implement legacy _pushIM/_setIM methods and convert to identifiers within
  • Refactor M3
    • for the non-custom-model-class branch, move M3 to using identifiers or off of RecordArrayManager
  • Wait for M3
    • once the custom-model-class FF is on, M3 no longer uses the RecordArrayManager

@runspired runspired force-pushed the sn/record-arrays-next branch 2 times, most recently from 5ceb06e to 7d8e6de Compare November 14, 2019 20:30
@runspired runspired removed the blocked label Nov 14, 2019
@@ -100,7 +100,7 @@ module('integration/record_array_manager', function(hooks) {
assert.equal(allSummary.called.length, 0, 'initial: no calls to all.willDestroy');
assert.equal(adapterPopulatedSummary.called.length, 0, 'initial: no calls to adapterPopulated.willDestroy');
assert.equal(
internalPersonModel._recordArrays.size,
manager.getRecordArrayForIdentifier(internalPersonModel.identifier).size,
Copy link
Member

Choose a reason for hiding this comment

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

this method should be getRecordArraysForIdentifier

@@ -100,7 +100,7 @@ module('integration/record_array_manager', function(hooks) {
assert.equal(allSummary.called.length, 0, 'initial: no calls to all.willDestroy');
assert.equal(adapterPopulatedSummary.called.length, 0, 'initial: no calls to adapterPopulated.willDestroy');
assert.equal(
internalPersonModel._recordArrays.size,
manager.getRecordArrayForIdentifier(internalPersonModel.identifier).size,
Copy link
Member

Choose a reason for hiding this comment

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

me and @runspired talked about adding a test to confirm that getRecordArrayForIdentifier is === _recordArrays


const emberRun = emberRunloop.backburner;
const pendingForIdentifier = new Set([]);
const IMFallback = new WeakMap();
const RecordArraysCache = new WeakMap(); // store StableIdentifier => Set[RecordArray[]]
Copy link
Member

Choose a reason for hiding this comment

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

Can we move the comment above the line please

@runspired
Copy link
Contributor

After reviewing with @igorT we decided we will

  • introduce a feature flag for RECORD_ARRAY_IDENTIFIERS, when off this PR should in effect have "no change"
  • introduce a feature flag for DEPRECATED_PRIVATE_RECORD_ARRAY_SUPPORT, which when RECORD_ARRAY_IDENTIFIERS is on and DEPRECATED_PRIVATE_RECORD_ARRAY_SUPPORT is off has the effect of removing our legacy support for private API hacks used to add custom record arrays (such as what M3 does).
  • update this PR to make use of these flags
  • add some tests for private API hacks that use the record array manager
  • land this PR then land a PR turning both the new flags on.
  • at a later time (likely around the time that CUSTOM_MODEL_CLASS flag is activated) turn the DEPRECATED_PRIVATE_RECORD_ARRAY_SUPPORT off.

@@ -157,6 +157,7 @@ export default class InternalModel {
_scheduledDestroy: any;
_modelClass: any;
__deferredTriggers: any;
__recordArrays: any;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I brought this back b/c we use __recordArrays in recordArray._dissociateFromOwnRecords.

Since internal-model has an identifier regardless of the flag, I think we would be safe to refactor.

@@ -18,7 +19,7 @@ const Person = Model.extend({
},
});

module('integration/record-arrays/adapter_populated_record_array - AdapterPopulatedRecordArray', function(hooks) {
module('scott integration/record-arrays/adapter_populated_record_array - AdapterPopulatedRecordArray', function(hooks) {
Copy link
Contributor

Choose a reason for hiding this comment

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

don't forget the Scott removal

@snewcomer snewcomer force-pushed the sn/record-arrays-next branch from 62c38ab to 2020758 Compare February 22, 2020 19:25
@snewcomer
Copy link
Contributor Author

@runspired I believe this PR is ready to review. All tests passed with both feature flags on and off!!

@snewcomer snewcomer force-pushed the sn/record-arrays-next branch from 975f75c to eb97486 Compare March 3, 2020 04:13
@snewcomer snewcomer force-pushed the sn/record-arrays-next branch from eb97486 to 44772f2 Compare April 22, 2020 21:03
@runspired runspired removed the 🎯 canary PR is targeting canary (default) label Apr 25, 2020
@snewcomer snewcomer force-pushed the sn/record-arrays-next branch from 44772f2 to 6b8e07b Compare April 27, 2020 02:46
@@ -177,4 +177,6 @@ export default {
REQUEST_SERVICE: null,
CUSTOM_MODEL_CLASS: null,
FULL_LINKS_ON_RELATIONSHIPS: true,
RECORD_ARRAY_MANAGER_IDENTIFIERS: false,
Copy link
Member

Choose a reason for hiding this comment

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

These should be null

array._removeInternalModels(modelsToRemove);
}
}
const pushIdentifiers = function(recordArray, identifiers, cache) {
Copy link
Member

Choose a reason for hiding this comment

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

Can we name this function please

const emberRun = emberRunloop.backburner;
let RecordArrayManager;

export let associateWithRecordArray;
Copy link
Member

Choose a reason for hiding this comment

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

Lets export this after it's been assigned

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since we have this assigned in a conditional block, it only allows us to import/export at the top level.

}
};

const destroy = function(entry) {
Copy link
Member

Choose a reason for hiding this comment

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

Lets bring these back into the named style, and see if we run into issues

@snewcomer snewcomer added the code-review Tracks PRs that require a code-review label Jul 21, 2020
@@ -2657,7 +2658,12 @@ abstract class CoreStore extends Service {
internalModel.setupData(data);

if (!isUpdate) {
this.recordArrayManager.recordDidChange(internalModel);
// TODO: do we want to maintain old API with this flag?
Copy link
Member

Choose a reason for hiding this comment

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

This seems like a leftover comment

this.recordArrayManager.recordDidChange(internalModel);
// TODO: do we want to maintain old API with this flag?
if (RECORD_ARRAY_MANAGER_IDENTIFIERS) {
this.recordArrayManager.recordDidChange(internalModel.identifier);
Copy link
Member

Choose a reason for hiding this comment

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

Given that we use the identifier on line 2644, we could extract the let identifier

@@ -42,6 +44,10 @@ export default class InternalModelMap {
return this._models.length;
}

get modelIdentifiers(): StableRecordIdentifier[] {
Copy link
Member

Choose a reason for hiding this comment

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

we should probably call this recordIdentifiers. We also only use it in a single place, maybe it's not worth creating a method for it?

@@ -1363,8 +1359,14 @@ export default class InternalModel {
@private
*/
updateRecordArrays() {
// @ts-ignore: Store is untyped and typescript does not detect instance props set in `init`
this.store.recordArrayManager.recordDidChange(this);
// TODO: do we want to maintain old API with this flag?
Copy link
Member

Choose a reason for hiding this comment

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

Leftover comment

this.store.recordArrayManager.recordDidChange(this);
// TODO: do we want to maintain old API with this flag?
if (RECORD_ARRAY_MANAGER_IDENTIFIERS) {
// @ts-ignore: Store is untyped and typescript does not detect instance props set in `init`
Copy link
Member

Choose a reason for hiding this comment

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

This ts-ignore is slightly incorrect, it seems we just need to expose recordArrayManager from CoreStore

REMOVE_RECORD_ARRAY_MANAGER_LEGACY_COMPAT,
} from '@ember-data/canary-features';

// only used by RECORD_ARRAY_MANAGER_IDENTIFIERS
Copy link
Member

Choose a reason for hiding this comment

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

Lets remove this comment


/**
* @method recordDidChange
* @public
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this is meant to be public, no?

@param {String} modelName
@param {Object} query
@param {Array} internalModels
@param {Object} payload
Copy link
Member

Choose a reason for hiding this comment

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

❤️

}
};

const destroy = function destroyEntry(entry) {
Copy link
Member

@igorT igorT Jul 23, 2020

Choose a reason for hiding this comment

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

should make the names consistent

entry.destroy();
};

const remove = function removeEntry(array, item) {
Copy link
Member

@igorT igorT Jul 23, 2020

Choose a reason for hiding this comment

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

should make the names consistent

@param {Object} payload normalized payload
@internal
*/
AdapterPopulatedRecordArray.prototype._setIdentifiers = function _setIdentifiers(identifiers, payload) {
Copy link
Member

Choose a reason for hiding this comment

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

Seems safer to extend here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done! I think the original intention was to work on the object directly instead of subclassing but this works too! Aligns closer to the semantics of defining objects in this file as well.

One question though - When it comes to switching this file over to TS though, would we modify the prototype or subclass?

this.manager._associateWithRecordArray(identifiers, this);

if (DEPRECATE_EVENTED_API_USAGE) {
const _hasDidLoad = DEBUG ? this._has('didLoad') : this.has('didLoad');
Copy link
Member

Choose a reason for hiding this comment

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

Switch with let

@param {Object} payload normalized payload
@internal
*/
AdapterPopulatedRecordArray.prototype._setIdentifiers = function _setIdentifiers(identifiers, payload) {
Copy link
Member

@igorT igorT Jul 23, 2020

Choose a reason for hiding this comment

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

function setObjects(identifierOrInternalModel) {
    // TODO: initial load should not cause change events at all, only
    // subsequent. This requires changing the public api of adapter.query, but
    // hopefully we can do that soon.
    this.get('content').setObjects(identifiers);

    this.setProperties({
      isLoaded: true,
      isUpdating: false,
      meta: assign({}, payload.meta),
      links: assign({}, payload.links),
    });

    this.manager._associateWithRecordArray(identifiers, this);

    if (DEPRECATE_EVENTED_API_USAGE) {
      const _hasDidLoad = DEBUG ? this._has('didLoad') : this.has('didLoad');
      if (_hasDidLoad) {
        // TODO: should triggering didLoad event be the last action of the runLoop?
        once(this, 'trigger', 'didLoad');
      }
    }
  };
}

if (RECORD_ARRAY_MANAGER_IDENTIFIERS) {
  AdapterPopulatedRecordArray.prototype._setIdentifiers = function _setIdentifiers(identifiers, payload) {
    setObjects(identifiers, payload)
  } 
} else {
  AdapterPopulatedRecordArray.prototype._setInternalModels = function _setInternalModels(internalModels, payload) {
   setObjects(identifiers, payload)

}
}

return internalModelForIdentifier(store, identifier).getRecord();
}

function internalModelForIdentifier(store, identifier) {
Copy link
Member

Choose a reason for hiding this comment

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

We probably don't need this helper function

@@ -113,8 +124,13 @@ export default ArrayProxy.extend(DeprecatedEvented, {
@return {Model} record
*/
objectAtContent(index) {
let internalModel = get(this, 'content').objectAt(index);
return internalModel && internalModel.getRecord();
let identifier = get(this, 'content').objectAt(index);
Copy link
Member

@igorT igorT Jul 23, 2020

Choose a reason for hiding this comment

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

this might read nicer if

    if (RECORD_ARRAY_MANAGER_IDENTIFIERS) {
        let identifier = get(this, 'content').objectAt(index);
      return identifier ? recordForIdentifier(this.store, identifier) : undefined;
    } else {
      let internalModel = get(this, 'content').objectAt(index);
      return internalModel ? internalModel.getRecord() : undefined;
    }


/*
if (RECORD_ARRAY_MANAGER_IDENTIFIERS) {
Copy link
Member

Choose a reason for hiding this comment

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

Should probably be extending here

@@ -2,4 +2,4 @@

{{outlet}}

<a href="/tests">Tests</a>
<a href={{"/tests"}}>Tests</a>
Copy link
Member

Choose a reason for hiding this comment

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

Lets try removing this

const emberRun = emberRunloop.backburner;
let RecordArrayManager;

export let associateWithRecordArray;
Copy link
Member

Choose a reason for hiding this comment

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

Lets leave a comment that we should remove this after the FF land

@snewcomer snewcomer requested a review from igorT July 24, 2020 22:08
@snewcomer snewcomer force-pushed the sn/record-arrays-next branch from 6a1f0f7 to 940efc7 Compare July 29, 2020 22:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
code-review Tracks PRs that require a code-review 🏷️ chore This PR primarily refactors code or updates dependencies 🌲 Project Trim 🌲 PRs related to https://github.com/emberjs/data/issues/6166
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants