diff --git a/packages/-ember-data/node-tests/fixtures/expected.js b/packages/-ember-data/node-tests/fixtures/expected.js index 71ba3e206aa..c5ca307a5a1 100644 --- a/packages/-ember-data/node-tests/fixtures/expected.js +++ b/packages/-ember-data/node-tests/fixtures/expected.js @@ -71,8 +71,6 @@ module.exports = { '(private) @ember-data/store RecordArray#objectAtContent', '(private) @ember-data/store RecordArray#store', '(private) @ember-data/store RecordArrayManager#_associateWithRecordArray', - '(private) @ember-data/store RecordArray#_pushInternalModels', - '(private) @ember-data/store RecordArray#_removeInternalModels', '(private) @ember-data/store RecordArray#_unregisterFromManager', '(private) @ember-data/store SnapshotRecordArray#_recordArray', '(private) @ember-data/store SnapshotRecordArray#_snapshots', diff --git a/packages/-ember-data/tests/integration/record-array-manager-test.js b/packages/-ember-data/tests/integration/record-array-manager-test.js index 6a14f3c06a1..e73a57c0316 100644 --- a/packages/-ember-data/tests/integration/record-array-manager-test.js +++ b/packages/-ember-data/tests/integration/record-array-manager-test.js @@ -1,5 +1,4 @@ import { A } from '@ember/array'; -import OrderedSet from '@ember/ordered-set'; import { settled } from '@ember/test-helpers'; import { module, test } from 'qunit'; @@ -7,7 +6,6 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import RESTAdapter from '@ember-data/adapter/rest'; -import { RECORD_ARRAY_MANAGER_IDENTIFIERS } from '@ember-data/canary-features'; import Model, { attr, belongsTo, hasMany } from '@ember-data/model'; import { recordIdentifierFor } from '@ember-data/store'; @@ -102,48 +100,32 @@ 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'); - if (RECORD_ARRAY_MANAGER_IDENTIFIERS) { - assert.equal( - manager.getRecordArraysForIdentifier(internalPersonModel.identifier).size, - 1, - 'initial: expected the person to be a member of 1 recordArrays' - ); - } else { - assert.equal( - internalPersonModel._recordArrays.size, - 1, - 'initial: expected the person to be a member of 1 recordArrays' - ); - } + assert.equal( + manager.getRecordArraysForIdentifier(internalPersonModel.identifier).size, + 1, + 'initial: expected the person to be a member of 1 recordArrays' + ); assert.equal('person' in manager._liveRecordArrays, true, 'initial: we have a live array for person'); all.destroy(); await settled(); - if (RECORD_ARRAY_MANAGER_IDENTIFIERS) { - assert.equal( - manager.getRecordArraysForIdentifier(internalPersonModel.identifier).size, - 0, - 'expected the person to be a member of no recordArrays' - ); - } else { - assert.equal(internalPersonModel._recordArrays.size, 0, 'expected the person to be a member of no recordArrays'); - } + assert.equal( + manager.getRecordArraysForIdentifier(internalPersonModel.identifier).size, + 0, + 'expected the person to be a member of no recordArrays' + ); assert.equal(allSummary.called.length, 1, 'all.willDestroy called once'); assert.equal('person' in manager._liveRecordArrays, false, 'no longer have a live array for person'); manager.destroy(); await settled(); - if (RECORD_ARRAY_MANAGER_IDENTIFIERS) { - assert.equal( - manager.getRecordArraysForIdentifier(internalPersonModel.identifier).size, - 0, - 'expected the person to be a member of no recordArrays' - ); - } else { - assert.equal(internalPersonModel._recordArrays.size, 0, 'expected the person to be a member of no recordArrays'); - } + assert.equal( + manager.getRecordArraysForIdentifier(internalPersonModel.identifier).size, + 0, + 'expected the person to be a member of no recordArrays' + ); assert.equal(allSummary.called.length, 1, 'all.willDestroy still only called once'); assert.equal(adapterPopulatedSummary.called.length, 1, 'adapterPopulated.willDestroy called once'); }); @@ -269,41 +251,24 @@ module('integration/record_array_manager', function(hooks) { }); test('createRecordArray with optional content', function(assert) { - let content; - let record; - if (RECORD_ARRAY_MANAGER_IDENTIFIERS) { - record = store.push({ - data: { - type: 'car', - id: '1', - attributes: { - make: 'BMC', - model: 'Mini Cooper', - }, - }, - }); - content = A([recordIdentifierFor(record)]); - } else { - let internalModel = { - _recordArrays: new OrderedSet(), - getRecord() { - return record; + let record = store.push({ + data: { + type: 'car', + id: '1', + attributes: { + make: 'BMC', + model: 'Mini Cooper', }, - }; - - content = A([internalModel]); - } + }, + }); + let content = A([recordIdentifierFor(record)]); let recordArray = manager.createRecordArray('foo', content); assert.equal(recordArray.modelName, 'foo', 'has modelName'); assert.equal(recordArray.isLoaded, true, 'isLoaded is true'); assert.equal(recordArray.manager, manager, 'recordArray has manager'); - if (RECORD_ARRAY_MANAGER_IDENTIFIERS) { - assert.deepEqual(recordArray.get('content'), [recordIdentifierFor(record)], 'recordArray has content'); - } else { - assert.equal(recordArray.get('content'), content); - } + assert.deepEqual(recordArray.get('content'), [recordIdentifierFor(record)], 'recordArray has content'); assert.deepEqual(recordArray.toArray(), [record], 'toArray works'); }); diff --git a/packages/-ember-data/tests/integration/record-arrays/adapter-populated-record-array-test.js b/packages/-ember-data/tests/integration/record-arrays/adapter-populated-record-array-test.js index c73d469c364..29f511d3bd9 100644 --- a/packages/-ember-data/tests/integration/record-arrays/adapter-populated-record-array-test.js +++ b/packages/-ember-data/tests/integration/record-arrays/adapter-populated-record-array-test.js @@ -7,7 +7,6 @@ import { setupTest } from 'ember-qunit'; import Adapter from '@ember-data/adapter'; import JSONAPIAdapter from '@ember-data/adapter/json-api'; -import { RECORD_ARRAY_MANAGER_IDENTIFIERS } from '@ember-data/canary-features'; import Model, { attr } from '@ember-data/model'; import JSONAPISerializer from '@ember-data/serializer/json-api'; import { recordIdentifierFor } from '@ember-data/store'; @@ -68,17 +67,10 @@ module('integration/record-arrays/adapter_populated_record_array - AdapterPopula let results = store.push(payload); - if (RECORD_ARRAY_MANAGER_IDENTIFIERS) { - recordArray._setIdentifiers( - results.map(r => recordIdentifierFor(r)), - payload - ); - } else { - recordArray._setInternalModels( - results.map(r => r._internalModel), - payload - ); - } + recordArray._setIdentifiers( + results.map(r => recordIdentifierFor(r)), + payload + ); assert.equal(recordArray.get('length'), 3, 'expected recordArray to contain exactly 3 records'); @@ -123,17 +115,10 @@ module('integration/record-arrays/adapter_populated_record_array - AdapterPopula }; let results = store.push(payload); - if (RECORD_ARRAY_MANAGER_IDENTIFIERS) { - recordArray._setIdentifiers( - results.map(r => recordIdentifierFor(r)), - payload - ); - } else { - recordArray._setInternalModels( - results.map(r => r._internalModel), - payload - ); - } + recordArray._setIdentifiers( + results.map(r => recordIdentifierFor(r)), + payload + ); assert.equal(recordArray.get('meta.foo'), 'bar', 'expected meta.foo to be bar from payload'); }); @@ -171,17 +156,10 @@ module('integration/record-arrays/adapter_populated_record_array - AdapterPopula }; let results = store.push(payload); - if (RECORD_ARRAY_MANAGER_IDENTIFIERS) { - recordArray._setIdentifiers( - results.map(r => recordIdentifierFor(r)), - payload - ); - } else { - recordArray._setInternalModels( - results.map(r => r._internalModel), - payload - ); - } + recordArray._setIdentifiers( + results.map(r => recordIdentifierFor(r)), + payload + ); assert.equal( recordArray.get('links.first'), diff --git a/packages/-ember-data/tests/unit/model/relationships/record-array-test.js b/packages/-ember-data/tests/unit/model/relationships/record-array-test.js index 6b961c5a346..80933f30a00 100644 --- a/packages/-ember-data/tests/unit/model/relationships/record-array-test.js +++ b/packages/-ember-data/tests/unit/model/relationships/record-array-test.js @@ -1,228 +1,117 @@ import { A } from '@ember/array'; import { get, set } from '@ember/object'; -import { run } from '@ember/runloop'; import { module, test } from 'qunit'; import DS from 'ember-data'; import { setupTest } from 'ember-qunit'; -import { RECORD_ARRAY_MANAGER_IDENTIFIERS } from '@ember-data/canary-features'; import { recordIdentifierFor } from '@ember-data/store'; -if (RECORD_ARRAY_MANAGER_IDENTIFIERS) { - module('unit/model/relationships - RecordArray', function(hooks) { - setupTest(hooks); +module('unit/model/relationships - RecordArray', function(hooks) { + setupTest(hooks); - test('updating the content of a RecordArray updates its content', async function(assert) { - let Tag = DS.Model.extend({ - name: DS.attr('string'), - }); + test('updating the content of a RecordArray updates its content', async function(assert) { + let Tag = DS.Model.extend({ + name: DS.attr('string'), + }); - this.owner.register('model:tag', Tag); + this.owner.register('model:tag', Tag); - let store = this.owner.lookup('service:store'); - let tags; + let store = this.owner.lookup('service:store'); + let tags; - let records = store.push({ - data: [ - { - type: 'tag', - id: '5', - attributes: { - name: 'friendly', - }, - }, - { - type: 'tag', - id: '2', - attributes: { - name: 'smarmy', - }, + let records = store.push({ + data: [ + { + type: 'tag', + id: '5', + attributes: { + name: 'friendly', }, - { - type: 'tag', - id: '12', - attributes: { - name: 'oohlala', - }, + }, + { + type: 'tag', + id: '2', + attributes: { + name: 'smarmy', }, - ], - }); - tags = DS.RecordArray.create({ - content: A(records.map(r => recordIdentifierFor(r)).slice(0, 2)), - store: store, - modelName: 'tag', - }); - - let tag = tags.objectAt(0); - assert.equal(get(tag, 'name'), 'friendly', `precond - we're working with the right tags`); - - set( - tags, - 'content', - A( - records - .map(r => r._internalModel) - .slice(1, 3) - .map(im => im.identifier) - ) - ); - - tag = tags.objectAt(0); - assert.equal(get(tag, 'name'), 'smarmy', 'the lookup was updated'); - }); - - test('can create child record from a hasMany relationship', async function(assert) { - assert.expect(3); - - const Tag = DS.Model.extend({ - name: DS.attr('string'), - person: DS.belongsTo('person', { async: false }), - }); - - const Person = DS.Model.extend({ - name: DS.attr('string'), - tags: DS.hasMany('tag', { async: false }), - }); - - this.owner.register('model:tag', Tag); - this.owner.register('model:person', Person); - - let store = this.owner.lookup('service:store'); - let adapter = store.adapterFor('application'); - - adapter.shouldBackgroundReloadRecord = () => false; - - store.push({ - data: { - type: 'person', - id: '1', + }, + { + type: 'tag', + id: '12', attributes: { - name: 'Tom Dale', + name: 'oohlala', }, }, - }); - - let person = await store.findRecord('person', 1); - person.get('tags').createRecord({ name: 'cool' }); - - assert.equal(get(person, 'name'), 'Tom Dale', 'precond - retrieves person record from store'); - assert.equal(get(person, 'tags.length'), 1, 'tag is added to the parent record'); - assert.equal( - get(person, 'tags') - .objectAt(0) - .get('name'), - 'cool', - 'tag values are passed along' - ); + ], }); - }); -} else { - module('unit/model/relationships - RecordArray', function(hooks) { - setupTest(hooks); - - test('updating the content of a RecordArray updates its content', function(assert) { - let Tag = DS.Model.extend({ - name: DS.attr('string'), - }); - - this.owner.register('model:tag', Tag); - - let store = this.owner.lookup('service:store'); - let tags; - let internalModels; - - run(() => { - internalModels = store._push({ - data: [ - { - type: 'tag', - id: '5', - attributes: { - name: 'friendly', - }, - }, - { - type: 'tag', - id: '2', - attributes: { - name: 'smarmy', - }, - }, - { - type: 'tag', - id: '12', - attributes: { - name: 'oohlala', - }, - }, - ], - }); - tags = DS.RecordArray.create({ - content: A(internalModels.slice(0, 2)), - store: store, - modelName: 'tag', - }); - }); - - let tag = tags.objectAt(0); - assert.equal(get(tag, 'name'), 'friendly', `precond - we're working with the right tags`); - - run(() => set(tags, 'content', A(internalModels.slice(1, 3)))); - - tag = tags.objectAt(0); - assert.equal(get(tag, 'name'), 'smarmy', 'the lookup was updated'); + tags = DS.RecordArray.create({ + content: A(records.map(r => recordIdentifierFor(r)).slice(0, 2)), + store: store, + modelName: 'tag', }); - test('can create child record from a hasMany relationship', function(assert) { - assert.expect(3); + let tag = tags.objectAt(0); + assert.equal(get(tag, 'name'), 'friendly', `precond - we're working with the right tags`); + + set( + tags, + 'content', + A( + records + .map(r => r._internalModel) + .slice(1, 3) + .map(im => im.identifier) + ) + ); + + tag = tags.objectAt(0); + assert.equal(get(tag, 'name'), 'smarmy', 'the lookup was updated'); + }); - const Tag = DS.Model.extend({ - name: DS.attr('string'), - person: DS.belongsTo('person', { async: false }), - }); + test('can create child record from a hasMany relationship', async function(assert) { + assert.expect(3); - const Person = DS.Model.extend({ - name: DS.attr('string'), - tags: DS.hasMany('tag', { async: false }), - }); + const Tag = DS.Model.extend({ + name: DS.attr('string'), + person: DS.belongsTo('person', { async: false }), + }); - this.owner.register('model:tag', Tag); - this.owner.register('model:person', Person); + const Person = DS.Model.extend({ + name: DS.attr('string'), + tags: DS.hasMany('tag', { async: false }), + }); - let store = this.owner.lookup('service:store'); - let adapter = store.adapterFor('application'); + this.owner.register('model:tag', Tag); + this.owner.register('model:person', Person); - adapter.shouldBackgroundReloadRecord = () => false; + let store = this.owner.lookup('service:store'); + let adapter = store.adapterFor('application'); - run(() => { - store.push({ - data: { - type: 'person', - id: '1', - attributes: { - name: 'Tom Dale', - }, - }, - }); - }); - - return run(() => { - return store.findRecord('person', 1).then(person => { - person.get('tags').createRecord({ name: 'cool' }); - - assert.equal(get(person, 'name'), 'Tom Dale', 'precond - retrieves person record from store'); - assert.equal(get(person, 'tags.length'), 1, 'tag is added to the parent record'); - assert.equal( - get(person, 'tags') - .objectAt(0) - .get('name'), - 'cool', - 'tag values are passed along' - ); - }); - }); + adapter.shouldBackgroundReloadRecord = () => false; + + store.push({ + data: { + type: 'person', + id: '1', + attributes: { + name: 'Tom Dale', + }, + }, }); + + let person = await store.findRecord('person', 1); + person.get('tags').createRecord({ name: 'cool' }); + + assert.equal(get(person, 'name'), 'Tom Dale', 'precond - retrieves person record from store'); + assert.equal(get(person, 'tags.length'), 1, 'tag is added to the parent record'); + assert.equal( + get(person, 'tags') + .objectAt(0) + .get('name'), + 'cool', + 'tag values are passed along' + ); }); -} +}); diff --git a/packages/-ember-data/tests/unit/record-arrays/adapter-populated-record-array-test.js b/packages/-ember-data/tests/unit/record-arrays/adapter-populated-record-array-test.js index fcc022a1e07..b2ce5431cf7 100644 --- a/packages/-ember-data/tests/unit/record-arrays/adapter-populated-record-array-test.js +++ b/packages/-ember-data/tests/unit/record-arrays/adapter-populated-record-array-test.js @@ -1,6 +1,5 @@ import { A } from '@ember/array'; import Evented from '@ember/object/evented'; -import { run } from '@ember/runloop'; import { settled } from '@ember/test-helpers'; import { module, test } from 'qunit'; @@ -9,660 +8,356 @@ import RSVP from 'rsvp'; import DS from 'ember-data'; import { setupTest } from 'ember-qunit'; -import { RECORD_ARRAY_MANAGER_IDENTIFIERS } from '@ember-data/canary-features'; import Model, { attr } from '@ember-data/model'; import { DEPRECATE_EVENTED_API_USAGE } from '@ember-data/private-build-infra/deprecations'; import { recordIdentifierFor } from '@ember-data/store'; const { AdapterPopulatedRecordArray, RecordArrayManager } = DS; -if (RECORD_ARRAY_MANAGER_IDENTIFIERS) { - class Tag extends Model { - @attr() - name; - } - - module('unit/record-arrays/adapter-populated-record-array - DS.AdapterPopulatedRecordArray', function(hooks) { - setupTest(hooks); +class Tag extends Model { + @attr() + name; +} - test('default initial state', async function(assert) { - let recordArray = AdapterPopulatedRecordArray.create({ modelName: 'recordType' }); +module('unit/record-arrays/adapter-populated-record-array - DS.AdapterPopulatedRecordArray', function(hooks) { + setupTest(hooks); - assert.equal(recordArray.get('isLoaded'), false, 'expected isLoaded to be false'); - assert.equal(recordArray.get('modelName'), 'recordType', 'has modelName'); - assert.deepEqual(recordArray.get('content'), [], 'has no content'); - assert.strictEqual(recordArray.get('query'), null, 'no query'); - assert.strictEqual(recordArray.get('store'), null, 'no store'); - assert.strictEqual(recordArray.get('links'), null, 'no links'); - }); + test('default initial state', async function(assert) { + let recordArray = AdapterPopulatedRecordArray.create({ modelName: 'recordType' }); - test('custom initial state', async function(assert) { - let content = A([]); - let store = {}; - let recordArray = AdapterPopulatedRecordArray.create({ - modelName: 'apple', - isLoaded: true, - isUpdating: true, - content, - store, - query: 'some-query', - links: 'foo', - }); - assert.equal(recordArray.get('isLoaded'), true); - assert.equal(recordArray.get('isUpdating'), false); - assert.equal(recordArray.get('modelName'), 'apple'); - assert.deepEqual(recordArray.get('content'), content); - assert.equal(recordArray.get('store'), store); - assert.equal(recordArray.get('query'), 'some-query'); - assert.strictEqual(recordArray.get('links'), 'foo'); - }); - - test('#replace() throws error', function(assert) { - let recordArray = AdapterPopulatedRecordArray.create({ modelName: 'recordType' }); + assert.equal(recordArray.get('isLoaded'), false, 'expected isLoaded to be false'); + assert.equal(recordArray.get('modelName'), 'recordType', 'has modelName'); + assert.deepEqual(recordArray.get('content'), [], 'has no content'); + assert.strictEqual(recordArray.get('query'), null, 'no query'); + assert.strictEqual(recordArray.get('store'), null, 'no store'); + assert.strictEqual(recordArray.get('links'), null, 'no links'); + }); - assert.throws( - () => { - recordArray.replace(); - }, - Error('The result of a server query (on recordType) is immutable.'), - 'throws error' - ); + test('custom initial state', async function(assert) { + let content = A([]); + let store = {}; + let recordArray = AdapterPopulatedRecordArray.create({ + modelName: 'apple', + isLoaded: true, + isUpdating: true, + content, + store, + query: 'some-query', + links: 'foo', }); + assert.equal(recordArray.get('isLoaded'), true); + assert.equal(recordArray.get('isUpdating'), false); + assert.equal(recordArray.get('modelName'), 'apple'); + assert.deepEqual(recordArray.get('content'), content); + assert.equal(recordArray.get('store'), store); + assert.equal(recordArray.get('query'), 'some-query'); + assert.strictEqual(recordArray.get('links'), 'foo'); + }); - test('#update uses _update enabling query specific behavior', async function(assert) { - let queryCalled = 0; - let deferred = RSVP.defer(); - - const store = { - _query(modelName, query, array) { - queryCalled++; - assert.equal(modelName, 'recordType'); - assert.equal(query, 'some-query'); - assert.equal(array, recordArray); - - return deferred.promise; - }, - }; - - let recordArray = AdapterPopulatedRecordArray.create({ - modelName: 'recordType', - store, - query: 'some-query', - }); - - assert.equal(recordArray.get('isUpdating'), false, 'should not yet be updating'); - - assert.equal(queryCalled, 0); - - let updateResult = recordArray.update(); - - assert.equal(queryCalled, 1); - - deferred.resolve('return value'); + test('#replace() throws error', function(assert) { + let recordArray = AdapterPopulatedRecordArray.create({ modelName: 'recordType' }); - assert.equal(recordArray.get('isUpdating'), true, 'should be updating'); + assert.throws( + () => { + recordArray.replace(); + }, + Error('The result of a server query (on recordType) is immutable.'), + 'throws error' + ); + }); - return updateResult.then(result => { - assert.equal(result, 'return value'); - assert.equal(recordArray.get('isUpdating'), false, 'should no longer be updating'); - }); - }); + test('#update uses _update enabling query specific behavior', async function(assert) { + let queryCalled = 0; + let deferred = RSVP.defer(); - // TODO: is this method required, i suspect store._query should be refactor so this is not needed - test('#_setIdentifiers', async function(assert) { - let didAddRecord = 0; - function add(array) { - didAddRecord++; + const store = { + _query(modelName, query, array) { + queryCalled++; + assert.equal(modelName, 'recordType'); + assert.equal(query, 'some-query'); assert.equal(array, recordArray); - } - - this.owner.register('model:tag', Tag); - let store = this.owner.lookup('service:store'); - - const set = new Set(); - set.add = add; - let manager = new RecordArrayManager({ - store, - }); - manager.getRecordArraysForIdentifier = () => { - return set; - }; - - let recordArray = AdapterPopulatedRecordArray.create({ - query: 'some-query', - manager, - content: A(), - store, - }); - - let model1 = { - type: 'tag', - id: '1', - }; - let model2 = { - type: 'tag', - id: '2', - }; - - let [record1, record2] = store.push({ - data: [model1, model2], - }); - - let identifier1 = recordIdentifierFor(record1); - let identifier2 = recordIdentifierFor(record2); - - assert.equal(didAddRecord, 0, 'no records should have been added yet'); - - let didLoad = 0; - if (DEPRECATE_EVENTED_API_USAGE) { - recordArray.on('didLoad', function() { - didLoad++; - }); - } - - let links = { foo: 1 }; - let meta = { bar: 2 }; - - let result = recordArray._setIdentifiers([identifier1, identifier2], { - links, - meta, - }); - - assert.equal(result, undefined, '_setIdentifiers should have no return value'); - - assert.equal(didAddRecord, 2, 'two records should have been added'); - - assert.deepEqual( - recordArray.toArray(), - [record1, record2], - 'should now contain the loaded records by identifier' - ); - if (DEPRECATE_EVENTED_API_USAGE) { - assert.equal(didLoad, 0, 'didLoad event should not have fired'); - } - assert.equal(recordArray.get('links').foo, 1, 'has links'); - assert.equal(recordArray.get('meta').bar, 2, 'has meta'); + return deferred.promise; + }, + }; - await settled(); - - if (DEPRECATE_EVENTED_API_USAGE) { - assert.equal(didLoad, 1, 'didLoad event should have fired once'); - } - assert.expectDeprecation({ - id: 'ember-data:evented-api-usage', - }); + let recordArray = AdapterPopulatedRecordArray.create({ + modelName: 'recordType', + store, + query: 'some-query', }); - test('change events when receiving a new query payload', async function(assert) { - assert.expect(38); - - let arrayDidChange = 0; - let contentDidChange = 0; - let didAddRecord = 0; - - this.owner.register('model:tag', Tag); - let store = this.owner.lookup('service:store'); - - function add(array) { - didAddRecord++; - assert.equal(array, recordArray); - } - - function del(array) { - assert.equal(array, recordArray); - } - - const set = new Set(); - set.add = add; - set.delete = del; - let manager = new RecordArrayManager({ - store, - }); - manager.getRecordArraysForIdentifier = () => { - return set; - }; - let recordArray = AdapterPopulatedRecordArray.extend(Evented).create({ - query: 'some-query', - manager, - content: A(), - store, - }); - - let model1 = { - type: 'tag', - id: '1', - attributes: { - name: 'Scumbag Dale', - }, - }; - let model2 = { - type: 'tag', - id: '2', - attributes: { - name: 'Scumbag Katz', - }, - }; - - let [record1, record2] = store.push({ - data: [model1, model2], - }); - - recordArray._setIdentifiers([recordIdentifierFor(record1), recordIdentifierFor(record2)], {}); + assert.equal(recordArray.get('isUpdating'), false, 'should not yet be updating'); - assert.equal(didAddRecord, 2, 'expected 2 didAddRecords'); - assert.deepEqual( - recordArray.map(x => x.name), - ['Scumbag Dale', 'Scumbag Katz'] - ); - - assert.equal(arrayDidChange, 0, 'array should not yet have emitted a change event'); - assert.equal(contentDidChange, 0, 'recordArray.content should not have changed'); - - recordArray.addObserver('content', function() { - contentDidChange++; - }); + assert.equal(queryCalled, 0); - recordArray.one('@array:change', function(array, startIdx, removeAmt, addAmt) { - arrayDidChange++; + let updateResult = recordArray.update(); - // first time invoked - assert.equal(array, recordArray, 'should be same record array as above'); - assert.equal(startIdx, 0, 'expected startIdx'); - assert.equal(removeAmt, 2, 'expected removeAmt'); - assert.equal(addAmt, 2, 'expected addAmt'); - }); + assert.equal(queryCalled, 1); - assert.equal(recordArray.get('isLoaded'), true, 'should be considered loaded'); - assert.equal(recordArray.get('isUpdating'), false, 'should not yet be updating'); - - assert.equal(arrayDidChange, 0); - assert.equal(contentDidChange, 0, 'recordArray.content should not have changed'); - - arrayDidChange = 0; - contentDidChange = 0; - didAddRecord = 0; - - let model3 = { - type: 'tag', - id: '3', - attributes: { - name: 'Scumbag Penner', - }, - }; - let model4 = { - type: 'tag', - id: '4', - attributes: { - name: 'Scumbag Hamilton', - }, - }; - - let [record3, record4] = store.push({ - data: [model3, model4], - }); + deferred.resolve('return value'); - recordArray._setIdentifiers([recordIdentifierFor(record3), recordIdentifierFor(record4)], {}); + assert.equal(recordArray.get('isUpdating'), true, 'should be updating'); - assert.equal(didAddRecord, 2, 'expected 2 didAddRecords'); - assert.equal(recordArray.get('isLoaded'), true, 'should be considered loaded'); + return updateResult.then(result => { + assert.equal(result, 'return value'); assert.equal(recordArray.get('isUpdating'), false, 'should no longer be updating'); - - assert.equal(arrayDidChange, 1, 'record array should have omitted ONE change event'); - assert.equal(contentDidChange, 0, 'recordArray.content should not have changed'); - - assert.deepEqual( - recordArray.map(x => x.name), - ['Scumbag Penner', 'Scumbag Hamilton'] - ); - - arrayDidChange = 0; // reset change event counter - contentDidChange = 0; // reset change event counter - didAddRecord = 0; - - recordArray.one('@array:change', function(array, startIdx, removeAmt, addAmt) { - arrayDidChange++; - - assert.equal(array, recordArray, 'should be same recordArray as above'); - assert.equal(startIdx, 0, 'expected startIdx'); - assert.equal(removeAmt, 2, 'expected removeAmt'); - assert.equal(addAmt, 1, 'expected addAmt'); - }); - - // re-query - assert.equal(recordArray.get('isLoaded'), true, 'should be considered loaded'); - assert.equal(recordArray.get('isUpdating'), false, 'should not yet be updating'); - - assert.equal(arrayDidChange, 0, 'record array should not yet have omitted a change event'); - assert.equal(contentDidChange, 0, 'recordArray.content should not have changed'); - - let model5 = { - type: 'tag', - id: '5', - attributes: { - name: 'Scumbag Penner', - }, - }; - - let record5 = store.push({ - data: model5, - }); - - recordArray._setIdentifiers([recordIdentifierFor(record5)], {}); - - assert.equal(didAddRecord, 1, 'expected 0 didAddRecord'); - - assert.equal(recordArray.get('isLoaded'), true, 'should be considered loaded'); - assert.equal(recordArray.get('isUpdating'), false, 'should not longer be updating'); - - assert.equal(arrayDidChange, 1, 'record array should have emitted one change event'); - assert.equal(contentDidChange, 0, 'recordArray.content should not have changed'); - - assert.deepEqual( - recordArray.map(x => x.name), - ['Scumbag Penner'] - ); - assert.expectDeprecation({ - id: 'ember-data:evented-api-usage', - count: 1, - }); }); }); -} else { - module('unit/record-arrays/adapter-populated-record-array - DS.AdapterPopulatedRecordArray', function() { - function internalModelFor(record) { - let _internalModel = { - get id() { - return record.id; - }, - getRecord() { - return record; - }, - }; - - record._internalModel = _internalModel; - return _internalModel; + + // TODO: is this method required, i suspect store._query should be refactor so this is not needed + test('#_setIdentifiers', async function(assert) { + let didAddRecord = 0; + function add(array) { + didAddRecord++; + assert.equal(array, recordArray); } - test('default initial state', function(assert) { - let recordArray = AdapterPopulatedRecordArray.create({ modelName: 'recordType' }); + this.owner.register('model:tag', Tag); + let store = this.owner.lookup('service:store'); - assert.equal(recordArray.get('isLoaded'), false, 'expected isLoaded to be false'); - assert.equal(recordArray.get('modelName'), 'recordType'); - assert.deepEqual(recordArray.get('content'), []); - assert.strictEqual(recordArray.get('query'), null); - assert.strictEqual(recordArray.get('store'), null); - assert.strictEqual(recordArray.get('links'), null); + const set = new Set(); + set.add = add; + let manager = new RecordArrayManager({ + store, }); - - test('custom initial state', function(assert) { - let content = A([]); - let store = {}; - let recordArray = AdapterPopulatedRecordArray.create({ - modelName: 'apple', - isLoaded: true, - isUpdating: true, - content, - store, - query: 'some-query', - links: 'foo', - }); - assert.equal(recordArray.get('isLoaded'), true); - assert.equal(recordArray.get('isUpdating'), false); - assert.equal(recordArray.get('modelName'), 'apple'); - assert.equal(recordArray.get('content'), content); - assert.equal(recordArray.get('store'), store); - assert.equal(recordArray.get('query'), 'some-query'); - assert.strictEqual(recordArray.get('links'), 'foo'); + manager.getRecordArraysForIdentifier = () => { + return set; + }; + + let recordArray = AdapterPopulatedRecordArray.create({ + query: 'some-query', + manager, + content: A(), + store, }); - test('#replace() throws error', function(assert) { - let recordArray = AdapterPopulatedRecordArray.create({ modelName: 'recordType' }); - - assert.throws( - () => { - recordArray.replace(); - }, - Error('The result of a server query (on recordType) is immutable.'), - 'throws error' - ); + let model1 = { + type: 'tag', + id: '1', + }; + let model2 = { + type: 'tag', + id: '2', + }; + + let [record1, record2] = store.push({ + data: [model1, model2], }); - test('#update uses _update enabling query specific behavior', function(assert) { - let queryCalled = 0; - let deferred = RSVP.defer(); - - const store = { - _query(modelName, query, array) { - queryCalled++; - assert.equal(modelName, 'recordType'); - assert.equal(query, 'some-query'); - assert.equal(array, recordArray); - - return deferred.promise; - }, - }; - - let recordArray = AdapterPopulatedRecordArray.create({ - modelName: 'recordType', - store, - query: 'some-query', - }); - - assert.equal(recordArray.get('isUpdating'), false, 'should not yet be updating'); + let identifier1 = recordIdentifierFor(record1); + let identifier2 = recordIdentifierFor(record2); - assert.equal(queryCalled, 0); + assert.equal(didAddRecord, 0, 'no records should have been added yet'); - let updateResult = recordArray.update(); - - assert.equal(queryCalled, 1); - - deferred.resolve('return value'); + let didLoad = 0; + if (DEPRECATE_EVENTED_API_USAGE) { + recordArray.on('didLoad', function() { + didLoad++; + }); + } - assert.equal(recordArray.get('isUpdating'), true, 'should be updating'); + let links = { foo: 1 }; + let meta = { bar: 2 }; - return updateResult.then(result => { - assert.equal(result, 'return value'); - assert.equal(recordArray.get('isUpdating'), false, 'should no longer be updating'); - }); + let result = recordArray._setIdentifiers([identifier1, identifier2], { + links, + meta, }); - // TODO: is this method required, i suspect store._query should be refactor so this is not needed - test('#_setInternalModels', function(assert) { - let didAddRecord = 0; - function add(array) { - didAddRecord++; - assert.equal(array, recordArray); - } + assert.equal(result, undefined, '_setIdentifiers should have no return value'); - let recordArray = AdapterPopulatedRecordArray.create({ - query: 'some-query', - manager: new RecordArrayManager({}), - }); + assert.equal(didAddRecord, 2, 'two records should have been added'); - let model1 = internalModelFor({ id: 1 }); - let model2 = internalModelFor({ id: 2 }); - - model1._recordArrays = { add }; - model2._recordArrays = { add }; - - assert.equal(didAddRecord, 0, 'no records should have been added yet'); - - let didLoad = 0; - if (DEPRECATE_EVENTED_API_USAGE) { - recordArray.on('didLoad', function() { - didLoad++; - }); - } - - let links = { foo: 1 }; - let meta = { bar: 2 }; - - run(() => { - assert.equal( - recordArray._setInternalModels([model1, model2], { - links, - meta, - }), - undefined, - '_setInternalModels should have no return value' - ); - - assert.equal(didAddRecord, 2, 'two records should have been added'); - - assert.deepEqual( - recordArray.toArray(), - [model1, model2].map(x => x.getRecord()), - 'should now contain the loaded records' - ); - - if (DEPRECATE_EVENTED_API_USAGE) { - assert.equal(didLoad, 0, 'didLoad event should not have fired'); - } - assert.equal(recordArray.get('links').foo, 1); - assert.equal(recordArray.get('meta').bar, 2); - }); - if (DEPRECATE_EVENTED_API_USAGE) { - assert.equal(didLoad, 1, 'didLoad event should have fired once'); - } - assert.expectDeprecation({ - id: 'ember-data:evented-api-usage', - }); - }); + assert.deepEqual(recordArray.toArray(), [record1, record2], 'should now contain the loaded records by identifier'); - test('change events when receiving a new query payload', function(assert) { - assert.expect(38); - - let arrayDidChange = 0; - let contentDidChange = 0; - let didAddRecord = 0; + if (DEPRECATE_EVENTED_API_USAGE) { + assert.equal(didLoad, 0, 'didLoad event should not have fired'); + } + assert.equal(recordArray.get('links').foo, 1, 'has links'); + assert.equal(recordArray.get('meta').bar, 2, 'has meta'); - function add(array) { - didAddRecord++; - assert.equal(array, recordArray); - } + await settled(); - function del(array) { - assert.equal(array, recordArray); - } + if (DEPRECATE_EVENTED_API_USAGE) { + assert.equal(didLoad, 1, 'didLoad event should have fired once'); + } + assert.expectDeprecation({ + id: 'ember-data:evented-api-usage', + }); + }); - // we need Evented to gain access to the @array:change event - let recordArray = AdapterPopulatedRecordArray.extend(Evented).create({ - query: 'some-query', - manager: new RecordArrayManager({}), - }); + test('change events when receiving a new query payload', async function(assert) { + assert.expect(38); - let model1 = internalModelFor({ id: '1', name: 'Scumbag Dale' }); - let model2 = internalModelFor({ id: '2', name: 'Scumbag Katz' }); + let arrayDidChange = 0; + let contentDidChange = 0; + let didAddRecord = 0; - model1._recordArrays = { add, delete: del }; - model2._recordArrays = { add, delete: del }; + this.owner.register('model:tag', Tag); + let store = this.owner.lookup('service:store'); - run(() => { - recordArray._setInternalModels([model1, model2], {}); - }); + function add(array) { + didAddRecord++; + assert.equal(array, recordArray); + } - assert.equal(didAddRecord, 2, 'expected 2 didAddRecords'); - assert.deepEqual( - recordArray.map(x => x.name), - ['Scumbag Dale', 'Scumbag Katz'] - ); + function del(array) { + assert.equal(array, recordArray); + } - assert.equal(arrayDidChange, 0, 'array should not yet have emitted a change event'); - assert.equal(contentDidChange, 0, 'recordArray.content should not have changed'); + const set = new Set(); + set.add = add; + set.delete = del; + let manager = new RecordArrayManager({ + store, + }); + manager.getRecordArraysForIdentifier = () => { + return set; + }; + let recordArray = AdapterPopulatedRecordArray.extend(Evented).create({ + query: 'some-query', + manager, + content: A(), + store, + }); - recordArray.addObserver('content', function() { - contentDidChange++; - }); + let model1 = { + type: 'tag', + id: '1', + attributes: { + name: 'Scumbag Dale', + }, + }; + let model2 = { + type: 'tag', + id: '2', + attributes: { + name: 'Scumbag Katz', + }, + }; + + let [record1, record2] = store.push({ + data: [model1, model2], + }); - recordArray.one('@array:change', function(array, startIdx, removeAmt, addAmt) { - arrayDidChange++; + recordArray._setIdentifiers([recordIdentifierFor(record1), recordIdentifierFor(record2)], {}); - // first time invoked - assert.equal(array, recordArray, 'should be same record array as above'); - assert.equal(startIdx, 0, 'expected startIdx'); - assert.equal(removeAmt, 2, 'expcted removeAmt'); - assert.equal(addAmt, 2, 'expected addAmt'); - }); + assert.equal(didAddRecord, 2, 'expected 2 didAddRecords'); + assert.deepEqual( + recordArray.map(x => x.name), + ['Scumbag Dale', 'Scumbag Katz'] + ); - assert.equal(recordArray.get('isLoaded'), true, 'should be considered loaded'); - assert.equal(recordArray.get('isUpdating'), false, 'should not yet be updating'); + assert.equal(arrayDidChange, 0, 'array should not yet have emitted a change event'); + assert.equal(contentDidChange, 0, 'recordArray.content should not have changed'); - assert.equal(arrayDidChange, 0); - assert.equal(contentDidChange, 0, 'recordArray.content should not have changed'); + recordArray.addObserver('content', function() { + contentDidChange++; + }); - arrayDidChange = 0; - contentDidChange = 0; - didAddRecord = 0; + recordArray.one('@array:change', function(array, startIdx, removeAmt, addAmt) { + arrayDidChange++; - let model3 = internalModelFor({ id: '3', name: 'Scumbag Penner' }); - let model4 = internalModelFor({ id: '4', name: 'Scumbag Hamilton' }); + // first time invoked + assert.equal(array, recordArray, 'should be same record array as above'); + assert.equal(startIdx, 0, 'expected startIdx'); + assert.equal(removeAmt, 2, 'expected removeAmt'); + assert.equal(addAmt, 2, 'expected addAmt'); + }); - model3._recordArrays = { add, delete: del }; - model4._recordArrays = { add, delete: del }; + assert.equal(recordArray.get('isLoaded'), true, 'should be considered loaded'); + assert.equal(recordArray.get('isUpdating'), false, 'should not yet be updating'); + + assert.equal(arrayDidChange, 0); + assert.equal(contentDidChange, 0, 'recordArray.content should not have changed'); + + arrayDidChange = 0; + contentDidChange = 0; + didAddRecord = 0; + + let model3 = { + type: 'tag', + id: '3', + attributes: { + name: 'Scumbag Penner', + }, + }; + let model4 = { + type: 'tag', + id: '4', + attributes: { + name: 'Scumbag Hamilton', + }, + }; + + let [record3, record4] = store.push({ + data: [model3, model4], + }); - run(() => { - // re-query - recordArray._setInternalModels([model3, model4], {}); - }); + recordArray._setIdentifiers([recordIdentifierFor(record3), recordIdentifierFor(record4)], {}); - assert.equal(didAddRecord, 2, 'expected 2 didAddRecords'); - assert.equal(recordArray.get('isLoaded'), true, 'should be considered loaded'); - assert.equal(recordArray.get('isUpdating'), false, 'should no longer be updating'); + assert.equal(didAddRecord, 2, 'expected 2 didAddRecords'); + assert.equal(recordArray.get('isLoaded'), true, 'should be considered loaded'); + assert.equal(recordArray.get('isUpdating'), false, 'should no longer be updating'); - assert.equal(arrayDidChange, 1, 'record array should have omitted ONE change event'); - assert.equal(contentDidChange, 0, 'recordArray.content should not have changed'); + assert.equal(arrayDidChange, 1, 'record array should have omitted ONE change event'); + assert.equal(contentDidChange, 0, 'recordArray.content should not have changed'); - assert.deepEqual( - recordArray.map(x => x.name), - ['Scumbag Penner', 'Scumbag Hamilton'] - ); + assert.deepEqual( + recordArray.map(x => x.name), + ['Scumbag Penner', 'Scumbag Hamilton'] + ); - arrayDidChange = 0; // reset change event counter - contentDidChange = 0; // reset change event counter - didAddRecord = 0; + arrayDidChange = 0; // reset change event counter + contentDidChange = 0; // reset change event counter + didAddRecord = 0; - recordArray.one('@array:change', function(array, startIdx, removeAmt, addAmt) { - arrayDidChange++; + recordArray.one('@array:change', function(array, startIdx, removeAmt, addAmt) { + arrayDidChange++; - // first time invoked - assert.equal(array, recordArray, 'should be same recordArray as above'); - assert.equal(startIdx, 0, 'expected startIdx'); - assert.equal(removeAmt, 2, 'expcted removeAmt'); - assert.equal(addAmt, 1, 'expected addAmt'); - }); + assert.equal(array, recordArray, 'should be same recordArray as above'); + assert.equal(startIdx, 0, 'expected startIdx'); + assert.equal(removeAmt, 2, 'expected removeAmt'); + assert.equal(addAmt, 1, 'expected addAmt'); + }); - // re-query - assert.equal(recordArray.get('isLoaded'), true, 'should be considered loaded'); - assert.equal(recordArray.get('isUpdating'), false, 'should not yet be updating'); + // re-query + assert.equal(recordArray.get('isLoaded'), true, 'should be considered loaded'); + assert.equal(recordArray.get('isUpdating'), false, 'should not yet be updating'); - assert.equal(arrayDidChange, 0, 'record array should not yet have omitted a change event'); - assert.equal(contentDidChange, 0, 'recordArray.content should not have changed'); + assert.equal(arrayDidChange, 0, 'record array should not yet have omitted a change event'); + assert.equal(contentDidChange, 0, 'recordArray.content should not have changed'); - let model5 = internalModelFor({ id: '3', name: 'Scumbag Penner' }); + let model5 = { + type: 'tag', + id: '5', + attributes: { + name: 'Scumbag Penner', + }, + }; - model5._recordArrays = { add, delete: del }; + let record5 = store.push({ + data: model5, + }); - run(() => { - recordArray._setInternalModels([model5], {}); - }); + recordArray._setIdentifiers([recordIdentifierFor(record5)], {}); - assert.equal(didAddRecord, 1, 'expected 0 didAddRecord'); + assert.equal(didAddRecord, 1, 'expected 0 didAddRecord'); - assert.equal(recordArray.get('isLoaded'), true, 'should be considered loaded'); - assert.equal(recordArray.get('isUpdating'), false, 'should not longer be updating'); + assert.equal(recordArray.get('isLoaded'), true, 'should be considered loaded'); + assert.equal(recordArray.get('isUpdating'), false, 'should not longer be updating'); - assert.equal(arrayDidChange, 1, 'record array should have emitted one change event'); - assert.equal(contentDidChange, 0, 'recordArray.content should not have changed'); + assert.equal(arrayDidChange, 1, 'record array should have emitted one change event'); + assert.equal(contentDidChange, 0, 'recordArray.content should not have changed'); - assert.deepEqual( - recordArray.map(x => x.name), - ['Scumbag Penner'] - ); - assert.expectDeprecation({ - id: 'ember-data:evented-api-usage', - count: 1, - }); + assert.deepEqual( + recordArray.map(x => x.name), + ['Scumbag Penner'] + ); + assert.expectDeprecation({ + id: 'ember-data:evented-api-usage', + count: 1, }); }); -} +}); diff --git a/packages/-ember-data/tests/unit/record-arrays/record-array-test.js b/packages/-ember-data/tests/unit/record-arrays/record-array-test.js index 2360d4d06c4..40ecc4cb17f 100644 --- a/packages/-ember-data/tests/unit/record-arrays/record-array-test.js +++ b/packages/-ember-data/tests/unit/record-arrays/record-array-test.js @@ -1,6 +1,5 @@ import { A } from '@ember/array'; import { get } from '@ember/object'; -import { run } from '@ember/runloop'; import { settled } from '@ember/test-helpers'; import { module, test } from 'qunit'; @@ -9,922 +8,486 @@ import RSVP, { resolve } from 'rsvp'; import DS from 'ember-data'; import { setupTest } from 'ember-qunit'; -import { RECORD_ARRAY_MANAGER_IDENTIFIERS } from '@ember-data/canary-features'; import Model, { attr } from '@ember-data/model'; import { recordIdentifierFor } from '@ember-data/store'; const { RecordArray } = DS; -if (RECORD_ARRAY_MANAGER_IDENTIFIERS) { - class Tag extends Model { - @attr - name; - } - - module('unit/record-arrays/record-array - DS.RecordArray', function(hooks) { - setupTest(hooks); - - test('default initial state', async function(assert) { - let recordArray = RecordArray.create({ modelName: 'recordType' }); - - assert.equal(get(recordArray, 'isLoaded'), false, 'record is not loaded'); - assert.equal(get(recordArray, 'isUpdating'), false, 'record is not updating'); - assert.equal(get(recordArray, 'modelName'), 'recordType', 'has modelName'); - assert.equal(get(recordArray, 'content'), undefined, 'content is not defined'); - assert.strictEqual(get(recordArray, 'store'), null, 'no store with recordArray'); - }); - - test('custom initial state', async function(assert) { - let content = A(); - let store = {}; - let recordArray = RecordArray.create({ - modelName: 'apple', - isLoaded: true, - isUpdating: true, - content, - store, - }); - assert.equal(get(recordArray, 'isLoaded'), true); - assert.equal(get(recordArray, 'isUpdating'), false); // cannot set as default value: - assert.equal(get(recordArray, 'modelName'), 'apple'); - assert.deepEqual(get(recordArray, 'content'), content); - assert.equal(get(recordArray, 'store'), store); - }); - - test('#replace() throws error', async function(assert) { - let recordArray = RecordArray.create({ modelName: 'recordType' }); - - assert.throws( - () => { - recordArray.replace(); - }, - Error( - 'The result of a server query (for all recordType types) is immutable. To modify contents, use toArray()' - ), - 'throws error' - ); - }); - - test('#objectAtContent', async function(assert) { - this.owner.register('model:tag', Tag); - let store = this.owner.lookup('service:store'); - - let records = store.push({ - data: [ - { - type: 'tag', - id: '1', - }, - { - type: 'tag', - id: '3', - }, - { - type: 'tag', - id: '5', - }, - ], - }); - - let recordArray = RecordArray.create({ - modelName: 'recordType', - content: A(records.map(r => recordIdentifierFor(r))), - store, - }); - - assert.equal(get(recordArray, 'length'), 3); - assert.equal(recordArray.objectAtContent(0).id, '1'); - assert.equal(recordArray.objectAtContent(1).id, '3'); - assert.equal(recordArray.objectAtContent(2).id, '5'); - assert.strictEqual(recordArray.objectAtContent(3), undefined); - }); - - test('#update', async function(assert) { - let findAllCalled = 0; - let deferred = RSVP.defer(); - - const store = { - findAll(modelName, options) { - findAllCalled++; - assert.equal(modelName, 'recordType'); - assert.equal(options.reload, true, 'options should contain reload: true'); - return deferred.promise; - }, - }; - - let recordArray = RecordArray.create({ - modelName: 'recordType', - store, - }); - - assert.equal(get(recordArray, 'isUpdating'), false, 'should not yet be updating'); - - assert.equal(findAllCalled, 0); - - let updateResult = recordArray.update(); - - assert.equal(findAllCalled, 1); - - deferred.resolve('return value'); - - assert.equal(get(recordArray, 'isUpdating'), true, 'should be updating'); - - return updateResult.then(result => { - assert.equal(result, 'return value'); - assert.equal(get(recordArray, 'isUpdating'), false, 'should no longer be updating'); - }); - }); - - test('#update while updating', async function(assert) { - let findAllCalled = 0; - let deferred = RSVP.defer(); - const store = { - findAll(modelName, options) { - findAllCalled++; - return deferred.promise; - }, - }; - - let recordArray = RecordArray.create({ - modelName: { modelName: 'recordType' }, - store, - }); - - assert.equal(get(recordArray, 'isUpdating'), false, 'should not be updating'); - assert.equal(findAllCalled, 0); - - let updateResult1 = recordArray.update(); - - assert.equal(findAllCalled, 1); - - let updateResult2 = recordArray.update(); - - assert.equal(findAllCalled, 1); - - assert.equal(updateResult1, updateResult2); - - deferred.resolve('return value'); - - assert.equal(get(recordArray, 'isUpdating'), true, 'should be updating'); - - return updateResult1.then(result => { - assert.equal(result, 'return value'); - assert.equal(get(recordArray, 'isUpdating'), false, 'should no longer be updating'); - }); - }); - - test('#_pushIdentifiers', async function(assert) { - let content = A(); - let recordArray = RecordArray.create({ - content, - }); - - let model1 = { - id: 1, - identifier: { lid: '@ember-data:lid-model-1' }, - getRecord() { - return this; - }, - }; - let model2 = { - id: 2, - identifier: { lid: '@ember-data:lid-model-2' }, - getRecord() { - return this; - }, - }; - let model3 = { - id: 3, - identifier: { lid: '@ember-data:lid-model-3' }, - getRecord() { - return this; - }, - }; - - assert.equal( - recordArray._pushIdentifiers([model1.identifier]), - undefined, - '_pushIdentifiers has no return value' - ); - assert.deepEqual(recordArray.get('content'), [model1.identifier], 'now contains model1'); - - recordArray._pushIdentifiers([model1.identifier]); - assert.deepEqual( - recordArray.get('content'), - [model1.identifier, model1.identifier], - 'allows duplicates, because record-array-manager ensures no duplicates, this layer should not double check' - ); - - recordArray._removeIdentifiers([model1.identifier]); - recordArray._pushIdentifiers([model1.identifier]); - - // can add multiple models at once - recordArray._pushIdentifiers([model2.identifier, model3.identifier]); - assert.deepEqual( - recordArray.get('content'), - [model1.identifier, model2.identifier, model3.identifier], - 'now contains model1, model2, model3' - ); - }); - - test('#_removeIdentifiers', async function(assert) { - let content = A(); - let recordArray = RecordArray.create({ - content, - }); - - let model1 = { - id: 1, - identifier: { lid: '@ember-data:lid-model-1' }, - getRecord() { - return 'model-1'; - }, - }; - let model2 = { - id: 2, - identifier: { lid: '@ember-data:lid-model-2' }, - getRecord() { - return 'model-2'; - }, - }; - let model3 = { - id: 3, - identifier: { lid: '@ember-data:lid-model-3' }, - getRecord() { - return 'model-3'; - }, - }; - - assert.equal(recordArray.get('content').length, 0); - assert.equal( - recordArray._removeIdentifiers([model1.identifier]), - undefined, - '_removeIdentifiers has no return value' - ); - assert.deepEqual(recordArray.get('content'), [], 'now contains no models'); - - recordArray._pushIdentifiers([model1.identifier, model2.identifier]); - - assert.deepEqual( - recordArray.get('content'), - [model1.identifier, model2.identifier], - 'now contains model1, model2,' - ); - assert.equal( - recordArray._removeIdentifiers([model1.identifier]), - undefined, - '_removeIdentifiers has no return value' - ); - assert.deepEqual(recordArray.get('content'), [model2.identifier], 'now only contains model2'); - assert.equal( - recordArray._removeIdentifiers([model2.identifier]), - undefined, - '_removeIdentifiers has no return value' - ); - assert.deepEqual(recordArray.get('content'), [], 'now contains no models'); - - recordArray._pushIdentifiers([model1.identifier, model2.identifier, model3.identifier]); - - assert.equal( - recordArray._removeIdentifiers([model1.identifier, model3.identifier]), - undefined, - '_removeIdentifiers has no return value' - ); - - assert.deepEqual(recordArray.get('content'), [model2.identifier], 'now contains model2'); - assert.equal( - recordArray._removeIdentifiers([model2.identifier]), - undefined, - '_removeIdentifiers has no return value' - ); - assert.deepEqual(recordArray.get('content'), [], 'now contains no models'); - }); - - test('#save', async function(assert) { - this.owner.register('model:tag', Tag); - let store = this.owner.lookup('service:store'); - - let model1 = { - id: '1', - type: 'tag', - }; - let model2 = { - id: '2', - type: 'tag', - save() { - model2Saved++; - return this; - }, - }; - - let [record1, record2] = store.push({ - data: [model1, model2], - }); - let identifiers = A([recordIdentifierFor(record1), recordIdentifierFor(record2)]); - let recordArray = RecordArray.create({ - content: identifiers, - store, - }); - record1._internalModel.save = () => { - model1Saved++; - return resolve(this); - }; - record2._internalModel.save = () => { - model2Saved++; - return resolve(this); - }; - - let model1Saved = 0; - let model2Saved = 0; - - assert.equal(model1Saved, 0, 'save not yet called'); - assert.equal(model2Saved, 0, 'save not yet called'); - - let result = recordArray.save(); - - assert.equal(model1Saved, 1, 'save was called for model1'); - assert.equal(model2Saved, 1, 'save was called for mode2'); +class Tag extends Model { + @attr + name; +} - const r = await result; - assert.equal(r.id, result.id, 'save promise should fulfill with the original recordArray'); - }); +module('unit/record-arrays/record-array - DS.RecordArray', function(hooks) { + setupTest(hooks); - test('#destroy', async function(assert) { - let didUnregisterRecordArray = 0; - let didDissociatieFromOwnRecords = 0; - this.owner.register('model:tag', Tag); - let store = this.owner.lookup('service:store'); - - let model1 = { - id: 1, - type: 'tag', - }; - let record = store.push({ - data: model1, - }); - - const set = new Set(); - set.delete = array => { - didDissociatieFromOwnRecords++; - assert.equal(array, recordArray); - }; - - let recordArray = RecordArray.create({ - content: A([recordIdentifierFor(record)]), - store, - manager: { - getRecordArraysForIdentifier() { - return set; - }, - unregisterRecordArray(_recordArray) { - didUnregisterRecordArray++; - assert.equal(recordArray, _recordArray); - }, - }, - }); - - assert.equal(get(recordArray, 'isDestroyed'), false, 'should not be destroyed'); - assert.equal(get(recordArray, 'isDestroying'), false, 'should not be destroying'); - - assert.equal(get(recordArray, 'length'), 1, 'before destroy, length should be 1'); - assert.equal(didUnregisterRecordArray, 0, 'before destroy, we should not yet have unregisterd the record array'); - assert.equal( - didDissociatieFromOwnRecords, - 0, - 'before destroy, we should not yet have dissociated from own record array' - ); - recordArray.destroy(); - await settled(); - - assert.equal(didUnregisterRecordArray, 1, 'after destroy we should have unregistered the record array'); - assert.equal(didDissociatieFromOwnRecords, 1, 'after destroy, we should have dissociated from own record array'); - - assert.strictEqual(get(recordArray, 'content'), null); - assert.equal(get(recordArray, 'length'), 0, 'after destroy we should have no length'); - assert.equal(get(recordArray, 'isDestroyed'), true, 'should be destroyed'); - }); + test('default initial state', async function(assert) { + let recordArray = RecordArray.create({ modelName: 'recordType' }); - test('#_createSnapshot', async function(assert) { - this.owner.register('model:tag', Tag); - let store = this.owner.lookup('service:store'); - - let model1 = { - id: 1, - type: 'tag', - }; - - let model2 = { - id: 2, - type: 'tag', - }; - let records = store.push({ - data: [model1, model2], - }); - - let recordArray = RecordArray.create({ - content: A(records.map(r => recordIdentifierFor(r))), - store, - }); - - let snapshot = recordArray._createSnapshot(); - let [snapshot1, snapshot2] = snapshot.snapshots(); - - assert.equal( - snapshot1.id, - model1.id, - 'record array snapshot should contain the first internalModel.createSnapshot result' - ); - assert.equal( - snapshot2.id, - model2.id, - 'record array snapshot should contain the second internalModel.createSnapshot result' - ); - }); + assert.equal(get(recordArray, 'isLoaded'), false, 'record is not loaded'); + assert.equal(get(recordArray, 'isUpdating'), false, 'record is not updating'); + assert.equal(get(recordArray, 'modelName'), 'recordType', 'has modelName'); + assert.equal(get(recordArray, 'content'), undefined, 'content is not defined'); + assert.strictEqual(get(recordArray, 'store'), null, 'no store with recordArray'); + }); - test('#destroy second', async function(assert) { - let didUnregisterRecordArray = 0; - let didDissociatieFromOwnRecords = 0; - - this.owner.register('model:tag', Tag); - let store = this.owner.lookup('service:store'); - - let model1 = { - id: 1, - type: 'tag', - }; - let record = store.push({ - data: model1, - }); - - // TODO: this will be removed once we fix ownership related memory leaks. - const set = new Set(); - set.delete = array => { - didDissociatieFromOwnRecords++; - assert.equal(array, recordArray); - }; - // end TODO: - - let recordArray = RecordArray.create({ - content: A([recordIdentifierFor(record)]), - manager: { - getRecordArraysForIdentifier() { - return set; - }, - unregisterRecordArray(_recordArray) { - didUnregisterRecordArray++; - assert.equal(recordArray, _recordArray); - }, - }, - store, - }); - - assert.equal(get(recordArray, 'isDestroyed'), false, 'should not be destroyed'); - assert.equal(get(recordArray, 'isDestroying'), false, 'should not be destroying'); - - assert.equal(get(recordArray, 'length'), 1, 'before destroy, length should be 1'); - assert.equal(didUnregisterRecordArray, 0, 'before destroy, we should not yet have unregisterd the record array'); - assert.equal( - didDissociatieFromOwnRecords, - 0, - 'before destroy, we should not yet have dissociated from own record array' - ); - recordArray.destroy(); - await settled(); - - assert.equal(didUnregisterRecordArray, 1, 'after destroy we should have unregistered the record array'); - assert.equal(didDissociatieFromOwnRecords, 1, 'after destroy, we should have dissociated from own record array'); - recordArray.destroy(); - - assert.strictEqual(get(recordArray, 'content'), null); - assert.equal(get(recordArray, 'length'), 0, 'after destroy we should have no length'); - assert.equal(get(recordArray, 'isDestroyed'), true, 'should be destroyed'); + test('custom initial state', async function(assert) { + let content = A(); + let store = {}; + let recordArray = RecordArray.create({ + modelName: 'apple', + isLoaded: true, + isUpdating: true, + content, + store, }); + assert.equal(get(recordArray, 'isLoaded'), true); + assert.equal(get(recordArray, 'isUpdating'), false); // cannot set as default value: + assert.equal(get(recordArray, 'modelName'), 'apple'); + assert.deepEqual(get(recordArray, 'content'), content); + assert.equal(get(recordArray, 'store'), store); }); -} else { - module('unit/record-arrays/record-array - DS.RecordArray', function() { - test('default initial state', function(assert) { - let recordArray = RecordArray.create({ modelName: 'recordType' }); - - assert.equal(get(recordArray, 'isLoaded'), false); - assert.equal(get(recordArray, 'isUpdating'), false); - assert.equal(get(recordArray, 'modelName'), 'recordType'); - assert.strictEqual(get(recordArray, 'content'), null); - assert.strictEqual(get(recordArray, 'store'), null); - }); - test('custom initial state', function(assert) { - let content = A(); - let store = {}; - let recordArray = RecordArray.create({ - modelName: 'apple', - isLoaded: true, - isUpdating: true, - content, - store, - }); - assert.equal(get(recordArray, 'isLoaded'), true); - assert.equal(get(recordArray, 'isUpdating'), false); // cannot set as default value: - assert.equal(get(recordArray, 'modelName'), 'apple'); - assert.equal(get(recordArray, 'content'), content); - assert.equal(get(recordArray, 'store'), store); - }); + test('#replace() throws error', async function(assert) { + let recordArray = RecordArray.create({ modelName: 'recordType' }); - test('#replace() throws error', function(assert) { - let recordArray = RecordArray.create({ modelName: 'recordType' }); + assert.throws( + () => { + recordArray.replace(); + }, + Error('The result of a server query (for all recordType types) is immutable. To modify contents, use toArray()'), + 'throws error' + ); + }); - assert.throws( - () => { - recordArray.replace(); - }, - Error( - 'The result of a server query (for all recordType types) is immutable. To modify contents, use toArray()' - ), - 'throws error' - ); - }); + test('#objectAtContent', async function(assert) { + this.owner.register('model:tag', Tag); + let store = this.owner.lookup('service:store'); - test('#objectAtContent', function(assert) { - let content = A([ + let records = store.push({ + data: [ { - getRecord() { - return 'foo'; - }, + type: 'tag', + id: '1', }, { - getRecord() { - return 'bar'; - }, + type: 'tag', + id: '3', }, { - getRecord() { - return 'baz'; - }, + type: 'tag', + id: '5', }, - ]); - - let recordArray = RecordArray.create({ - modelName: 'recordType', - content, - }); - - assert.equal(get(recordArray, 'length'), 3); - assert.equal(recordArray.objectAtContent(0), 'foo'); - assert.equal(recordArray.objectAtContent(1), 'bar'); - assert.equal(recordArray.objectAtContent(2), 'baz'); - assert.strictEqual(recordArray.objectAtContent(3), undefined); + ], }); - test('#update', function(assert) { - let findAllCalled = 0; - let deferred = RSVP.defer(); + let recordArray = RecordArray.create({ + modelName: 'recordType', + content: A(records.map(r => recordIdentifierFor(r))), + store, + }); - const store = { - findAll(modelName, options) { - findAllCalled++; - assert.equal(modelName, 'recordType'); - assert.equal(options.reload, true, 'options should contain reload: true'); - return deferred.promise; - }, - }; + assert.equal(get(recordArray, 'length'), 3); + assert.equal(recordArray.objectAtContent(0).id, '1'); + assert.equal(recordArray.objectAtContent(1).id, '3'); + assert.equal(recordArray.objectAtContent(2).id, '5'); + assert.strictEqual(recordArray.objectAtContent(3), undefined); + }); - let recordArray = RecordArray.create({ - modelName: 'recordType', - store, - }); + test('#update', async function(assert) { + let findAllCalled = 0; + let deferred = RSVP.defer(); + + const store = { + findAll(modelName, options) { + findAllCalled++; + assert.equal(modelName, 'recordType'); + assert.equal(options.reload, true, 'options should contain reload: true'); + return deferred.promise; + }, + }; + + let recordArray = RecordArray.create({ + modelName: 'recordType', + store, + }); - assert.equal(get(recordArray, 'isUpdating'), false, 'should not yet be updating'); + assert.equal(get(recordArray, 'isUpdating'), false, 'should not yet be updating'); - assert.equal(findAllCalled, 0); + assert.equal(findAllCalled, 0); - let updateResult = recordArray.update(); + let updateResult = recordArray.update(); - assert.equal(findAllCalled, 1); + assert.equal(findAllCalled, 1); - deferred.resolve('return value'); + deferred.resolve('return value'); - assert.equal(get(recordArray, 'isUpdating'), true, 'should be updating'); + assert.equal(get(recordArray, 'isUpdating'), true, 'should be updating'); - return updateResult.then(result => { - assert.equal(result, 'return value'); - assert.equal(get(recordArray, 'isUpdating'), false, 'should no longer be updating'); - }); + return updateResult.then(result => { + assert.equal(result, 'return value'); + assert.equal(get(recordArray, 'isUpdating'), false, 'should no longer be updating'); }); + }); - test('#update while updating', function(assert) { - let findAllCalled = 0; - let deferred = RSVP.defer(); - const store = { - findAll(modelName, options) { - findAllCalled++; - return deferred.promise; - }, - }; - - let recordArray = RecordArray.create({ - modelName: { modelName: 'recordType' }, - store, - }); + test('#update while updating', async function(assert) { + let findAllCalled = 0; + let deferred = RSVP.defer(); + const store = { + findAll(modelName, options) { + findAllCalled++; + return deferred.promise; + }, + }; + + let recordArray = RecordArray.create({ + modelName: { modelName: 'recordType' }, + store, + }); - assert.equal(get(recordArray, 'isUpdating'), false, 'should not be updating'); - assert.equal(findAllCalled, 0); + assert.equal(get(recordArray, 'isUpdating'), false, 'should not be updating'); + assert.equal(findAllCalled, 0); - let updateResult1 = recordArray.update(); + let updateResult1 = recordArray.update(); - assert.equal(findAllCalled, 1); + assert.equal(findAllCalled, 1); - let updateResult2 = recordArray.update(); + let updateResult2 = recordArray.update(); - assert.equal(findAllCalled, 1); + assert.equal(findAllCalled, 1); - assert.equal(updateResult1, updateResult2); + assert.equal(updateResult1, updateResult2); - deferred.resolve('return value'); + deferred.resolve('return value'); - assert.equal(get(recordArray, 'isUpdating'), true, 'should be updating'); + assert.equal(get(recordArray, 'isUpdating'), true, 'should be updating'); - return updateResult1.then(result => { - assert.equal(result, 'return value'); - assert.equal(get(recordArray, 'isUpdating'), false, 'should no longer be updating'); - }); + return updateResult1.then(result => { + assert.equal(result, 'return value'); + assert.equal(get(recordArray, 'isUpdating'), false, 'should no longer be updating'); }); + }); - test('#_pushInternalModels', function(assert) { - let content = A(); - let recordArray = RecordArray.create({ - content, - }); + test('#_pushIdentifiers', async function(assert) { + let content = A(); + let recordArray = RecordArray.create({ + content, + }); - let model1 = { - id: 1, - getRecord() { - return 'model-1'; - }, - }; - let model2 = { - id: 2, - getRecord() { - return 'model-2'; - }, - }; - let model3 = { - id: 3, - getRecord() { - return 'model-3'; - }, - }; + let model1 = { + id: 1, + identifier: { lid: '@ember-data:lid-model-1' }, + getRecord() { + return this; + }, + }; + let model2 = { + id: 2, + identifier: { lid: '@ember-data:lid-model-2' }, + getRecord() { + return this; + }, + }; + let model3 = { + id: 3, + identifier: { lid: '@ember-data:lid-model-3' }, + getRecord() { + return this; + }, + }; + + assert.equal(recordArray._pushIdentifiers([model1.identifier]), undefined, '_pushIdentifiers has no return value'); + assert.deepEqual(recordArray.get('content'), [model1.identifier], 'now contains model1'); + + recordArray._pushIdentifiers([model1.identifier]); + assert.deepEqual( + recordArray.get('content'), + [model1.identifier, model1.identifier], + 'allows duplicates, because record-array-manager ensures no duplicates, this layer should not double check' + ); + + recordArray._removeIdentifiers([model1.identifier]); + recordArray._pushIdentifiers([model1.identifier]); + + // can add multiple models at once + recordArray._pushIdentifiers([model2.identifier, model3.identifier]); + assert.deepEqual( + recordArray.get('content'), + [model1.identifier, model2.identifier, model3.identifier], + 'now contains model1, model2, model3' + ); + }); - assert.equal(recordArray._pushInternalModels([model1]), undefined, '_pushInternalModels has no return value'); - assert.deepEqual(content, [model1], 'now contains model1'); + test('#_removeIdentifiers', async function(assert) { + let content = A(); + let recordArray = RecordArray.create({ + content, + }); - recordArray._pushInternalModels([model1]); - assert.deepEqual( - content, - [model1, model1], - 'allows duplicates, because record-array-manager via internalModel._recordArrays ensures no duplicates, this layer should not double check' - ); + let model1 = { + id: 1, + identifier: { lid: '@ember-data:lid-model-1' }, + getRecord() { + return 'model-1'; + }, + }; + let model2 = { + id: 2, + identifier: { lid: '@ember-data:lid-model-2' }, + getRecord() { + return 'model-2'; + }, + }; + let model3 = { + id: 3, + identifier: { lid: '@ember-data:lid-model-3' }, + getRecord() { + return 'model-3'; + }, + }; + + assert.equal(recordArray.get('content').length, 0); + assert.equal( + recordArray._removeIdentifiers([model1.identifier]), + undefined, + '_removeIdentifiers has no return value' + ); + assert.deepEqual(recordArray.get('content'), [], 'now contains no models'); + + recordArray._pushIdentifiers([model1.identifier, model2.identifier]); + + assert.deepEqual( + recordArray.get('content'), + [model1.identifier, model2.identifier], + 'now contains model1, model2,' + ); + assert.equal( + recordArray._removeIdentifiers([model1.identifier]), + undefined, + '_removeIdentifiers has no return value' + ); + assert.deepEqual(recordArray.get('content'), [model2.identifier], 'now only contains model2'); + assert.equal( + recordArray._removeIdentifiers([model2.identifier]), + undefined, + '_removeIdentifiers has no return value' + ); + assert.deepEqual(recordArray.get('content'), [], 'now contains no models'); + + recordArray._pushIdentifiers([model1.identifier, model2.identifier, model3.identifier]); + + assert.equal( + recordArray._removeIdentifiers([model1.identifier, model3.identifier]), + undefined, + '_removeIdentifiers has no return value' + ); + + assert.deepEqual(recordArray.get('content'), [model2.identifier], 'now contains model2'); + assert.equal( + recordArray._removeIdentifiers([model2.identifier]), + undefined, + '_removeIdentifiers has no return value' + ); + assert.deepEqual(recordArray.get('content'), [], 'now contains no models'); + }); - recordArray._removeInternalModels([model1]); - recordArray._pushInternalModels([model1]); + test('#save', async function(assert) { + this.owner.register('model:tag', Tag); + let store = this.owner.lookup('service:store'); + + let model1 = { + id: '1', + type: 'tag', + }; + let model2 = { + id: '2', + type: 'tag', + save() { + model2Saved++; + return this; + }, + }; - // can add multiple models at once - recordArray._pushInternalModels([model2, model3]); - assert.deepEqual(content, [model1, model2, model3], 'now contains model1, model2, model3'); + let [record1, record2] = store.push({ + data: [model1, model2], }); + let identifiers = A([recordIdentifierFor(record1), recordIdentifierFor(record2)]); + let recordArray = RecordArray.create({ + content: identifiers, + store, + }); + record1._internalModel.save = () => { + model1Saved++; + return resolve(this); + }; + record2._internalModel.save = () => { + model2Saved++; + return resolve(this); + }; - test('#_removeInternalModels', function(assert) { - let content = A(); - let recordArray = RecordArray.create({ - content, - }); - - let model1 = { - id: 1, - getRecord() { - return 'model-1'; - }, - }; - let model2 = { - id: 2, - getRecord() { - return 'model-2'; - }, - }; - let model3 = { - id: 3, - getRecord() { - return 'model-3'; - }, - }; - - assert.equal(content.length, 0); - assert.equal(recordArray._removeInternalModels([model1]), undefined, '_removeInternalModels has no return value'); - assert.deepEqual(content, [], 'now contains no models'); + let model1Saved = 0; + let model2Saved = 0; - recordArray._pushInternalModels([model1, model2]); + assert.equal(model1Saved, 0, 'save not yet called'); + assert.equal(model2Saved, 0, 'save not yet called'); - assert.deepEqual(content, [model1, model2], 'now contains model1, model2,'); - assert.equal(recordArray._removeInternalModels([model1]), undefined, '_removeInternalModels has no return value'); - assert.deepEqual(content, [model2], 'now only contains model2'); - assert.equal(recordArray._removeInternalModels([model2]), undefined, '_removeInternalModels has no return value'); - assert.deepEqual(content, [], 'now contains no models'); + let result = recordArray.save(); - recordArray._pushInternalModels([model1, model2, model3]); + assert.equal(model1Saved, 1, 'save was called for model1'); + assert.equal(model2Saved, 1, 'save was called for mode2'); - assert.equal( - recordArray._removeInternalModels([model1, model3]), - undefined, - '_removeInternalModels has no return value' - ); + const r = await result; + assert.equal(r.id, result.id, 'save promise should fulfill with the original recordArray'); + }); - assert.deepEqual(content, [model2], 'now contains model2'); - assert.equal(recordArray._removeInternalModels([model2]), undefined, '_removeInternalModels has no return value'); - assert.deepEqual(content, [], 'now contains no models'); + test('#destroy', async function(assert) { + let didUnregisterRecordArray = 0; + let didDissociatieFromOwnRecords = 0; + this.owner.register('model:tag', Tag); + let store = this.owner.lookup('service:store'); + + let model1 = { + id: 1, + type: 'tag', + }; + let record = store.push({ + data: model1, }); - class FakeInternalModel { - constructor(record) { - this._record = record; - this.__recordArrays = null; - } - - get _recordArrays() { - return this.__recordArrays; - } - - getRecord() { - return this._record; - } - - createSnapshot() { - return this._record; - } - } - - function internalModelFor(record) { - return new FakeInternalModel(record); - } - - test('#save', function(assert) { - let model1 = { - save() { - model1Saved++; - return this; + const set = new Set(); + set.delete = array => { + didDissociatieFromOwnRecords++; + assert.equal(array, recordArray); + }; + + let recordArray = RecordArray.create({ + content: A([recordIdentifierFor(record)]), + store, + manager: { + getRecordArraysForIdentifier() { + return set; }, - }; - let model2 = { - save() { - model2Saved++; - return this; + unregisterRecordArray(_recordArray) { + didUnregisterRecordArray++; + assert.equal(recordArray, _recordArray); }, - }; - let content = A([internalModelFor(model1), internalModelFor(model2)]); - - let recordArray = RecordArray.create({ - content, - }); - - let model1Saved = 0; - let model2Saved = 0; - - assert.equal(model1Saved, 0); - assert.equal(model2Saved, 0); - - let result = recordArray.save(); + }, + }); - assert.equal(model1Saved, 1); - assert.equal(model2Saved, 1); + assert.equal(get(recordArray, 'isDestroyed'), false, 'should not be destroyed'); + assert.equal(get(recordArray, 'isDestroying'), false, 'should not be destroying'); + + assert.equal(get(recordArray, 'length'), 1, 'before destroy, length should be 1'); + assert.equal(didUnregisterRecordArray, 0, 'before destroy, we should not yet have unregisterd the record array'); + assert.equal( + didDissociatieFromOwnRecords, + 0, + 'before destroy, we should not yet have dissociated from own record array' + ); + recordArray.destroy(); + await settled(); + + assert.equal(didUnregisterRecordArray, 1, 'after destroy we should have unregistered the record array'); + assert.equal(didDissociatieFromOwnRecords, 1, 'after destroy, we should have dissociated from own record array'); + + assert.strictEqual(get(recordArray, 'content'), null); + assert.equal(get(recordArray, 'length'), 0, 'after destroy we should have no length'); + assert.equal(get(recordArray, 'isDestroyed'), true, 'should be destroyed'); + }); - return result.then(result => { - assert.equal(result, result, 'save promise should fulfill with the original recordArray'); - }); + test('#_createSnapshot', async function(assert) { + this.owner.register('model:tag', Tag); + let store = this.owner.lookup('service:store'); + + let model1 = { + id: 1, + type: 'tag', + }; + + let model2 = { + id: 2, + type: 'tag', + }; + let records = store.push({ + data: [model1, model2], }); - test('#destroy', function(assert) { - let didUnregisterRecordArray = 0; - let didDissociatieFromOwnRecords = 0; - let model1 = {}; - let internalModel1 = internalModelFor(model1); - - // TODO: this will be removed once we fix ownership related memory leaks. - internalModel1.__recordArrays = { - delete(array) { - didDissociatieFromOwnRecords++; - assert.equal(array, recordArray); - }, - }; - // end TODO: - - let recordArray = RecordArray.create({ - content: A([internalModel1]), - manager: { - unregisterRecordArray(_recordArray) { - didUnregisterRecordArray++; - assert.equal(recordArray, _recordArray); - }, - }, - }); - - assert.equal(get(recordArray, 'isDestroyed'), false, 'should not be destroyed'); - assert.equal(get(recordArray, 'isDestroying'), false, 'should not be destroying'); - - run(() => { - assert.equal(get(recordArray, 'length'), 1, 'before destroy, length should be 1'); - assert.equal( - didUnregisterRecordArray, - 0, - 'before destroy, we should not yet have unregisterd the record array' - ); - assert.equal( - didDissociatieFromOwnRecords, - 0, - 'before destroy, we should not yet have dissociated from own record array' - ); - recordArray.destroy(); - }); - - assert.equal(didUnregisterRecordArray, 1, 'after destroy we should have unregistered the record array'); - assert.equal(didDissociatieFromOwnRecords, 1, 'after destroy, we should have dissociated from own record array'); - - assert.strictEqual(get(recordArray, 'content'), null); - assert.equal(get(recordArray, 'length'), 0, 'after destroy we should have no length'); - assert.equal(get(recordArray, 'isDestroyed'), true, 'should be destroyed'); + let recordArray = RecordArray.create({ + content: A(records.map(r => recordIdentifierFor(r))), + store, }); - test('#_createSnapshot', function(assert) { - let model1 = { - id: 1, - }; - - let model2 = { - id: 2, - }; - - let content = A([internalModelFor(model1), internalModelFor(model2)]); + let snapshot = recordArray._createSnapshot(); + let [snapshot1, snapshot2] = snapshot.snapshots(); + + assert.equal( + snapshot1.id, + model1.id, + 'record array snapshot should contain the first internalModel.createSnapshot result' + ); + assert.equal( + snapshot2.id, + model2.id, + 'record array snapshot should contain the second internalModel.createSnapshot result' + ); + }); - let recordArray = RecordArray.create({ - content, - }); + test('#destroy second', async function(assert) { + let didUnregisterRecordArray = 0; + let didDissociatieFromOwnRecords = 0; - let snapshot = recordArray._createSnapshot(); - let snapshots = snapshot.snapshots(); + this.owner.register('model:tag', Tag); + let store = this.owner.lookup('service:store'); - assert.deepEqual( - snapshots, - [model1, model2], - 'record array snapshot should contain the internalModel.createSnapshot result' - ); + let model1 = { + id: 1, + type: 'tag', + }; + let record = store.push({ + data: model1, }); - test('#destroy', function(assert) { - let didUnregisterRecordArray = 0; - let didDissociatieFromOwnRecords = 0; - let model1 = {}; - let internalModel1 = internalModelFor(model1); - - // TODO: this will be removed once we fix ownership related memory leaks. - internalModel1.__recordArrays = { - delete(array) { - didDissociatieFromOwnRecords++; - assert.equal(array, recordArray); + // TODO: this will be removed once we fix ownership related memory leaks. + const set = new Set(); + set.delete = array => { + didDissociatieFromOwnRecords++; + assert.equal(array, recordArray); + }; + // end TODO: + + let recordArray = RecordArray.create({ + content: A([recordIdentifierFor(record)]), + manager: { + getRecordArraysForIdentifier() { + return set; }, - }; - // end TODO: - - let recordArray = RecordArray.create({ - content: A([internalModel1]), - manager: { - unregisterRecordArray(_recordArray) { - didUnregisterRecordArray++; - assert.equal(recordArray, _recordArray); - }, + unregisterRecordArray(_recordArray) { + didUnregisterRecordArray++; + assert.equal(recordArray, _recordArray); }, - }); - - assert.equal(get(recordArray, 'isDestroyed'), false, 'should not be destroyed'); - assert.equal(get(recordArray, 'isDestroying'), false, 'should not be destroying'); - - run(() => { - assert.equal(get(recordArray, 'length'), 1, 'before destroy, length should be 1'); - assert.equal( - didUnregisterRecordArray, - 0, - 'before destroy, we should not yet have unregisterd the record array' - ); - assert.equal( - didDissociatieFromOwnRecords, - 0, - 'before destroy, we should not yet have dissociated from own record array' - ); - recordArray.destroy(); - }); - - assert.equal(didUnregisterRecordArray, 1, 'after destroy we should have unregistered the record array'); - assert.equal(didDissociatieFromOwnRecords, 1, 'after destroy, we should have dissociated from own record array'); - recordArray.destroy(); - - assert.strictEqual(get(recordArray, 'content'), null); - assert.equal(get(recordArray, 'length'), 0, 'after destroy we should have no length'); - assert.equal(get(recordArray, 'isDestroyed'), true, 'should be destroyed'); + }, + store, }); + + assert.equal(get(recordArray, 'isDestroyed'), false, 'should not be destroyed'); + assert.equal(get(recordArray, 'isDestroying'), false, 'should not be destroying'); + + assert.equal(get(recordArray, 'length'), 1, 'before destroy, length should be 1'); + assert.equal(didUnregisterRecordArray, 0, 'before destroy, we should not yet have unregisterd the record array'); + assert.equal( + didDissociatieFromOwnRecords, + 0, + 'before destroy, we should not yet have dissociated from own record array' + ); + recordArray.destroy(); + await settled(); + + assert.equal(didUnregisterRecordArray, 1, 'after destroy we should have unregistered the record array'); + assert.equal(didDissociatieFromOwnRecords, 1, 'after destroy, we should have dissociated from own record array'); + recordArray.destroy(); + + assert.strictEqual(get(recordArray, 'content'), null); + assert.equal(get(recordArray, 'length'), 0, 'after destroy we should have no length'); + assert.equal(get(recordArray, 'isDestroyed'), true, 'should be destroyed'); }); -} +}); diff --git a/packages/store/addon/-private/system/core-store.ts b/packages/store/addon/-private/system/core-store.ts index 88306d2bd58..d6bbb9b815a 100644 --- a/packages/store/addon/-private/system/core-store.ts +++ b/packages/store/addon/-private/system/core-store.ts @@ -19,7 +19,6 @@ import { all, default as RSVP, defer, Promise, resolve } from 'rsvp'; import { CUSTOM_MODEL_CLASS, - RECORD_ARRAY_MANAGER_IDENTIFIERS, RECORD_DATA_ERRORS, RECORD_DATA_STATE, REQUEST_SERVICE, @@ -1487,19 +1486,15 @@ abstract class CoreStore extends Service { const normalizedId = ensureStringId(id); const resource = constructResource(type, normalizedId); - if (RECORD_ARRAY_MANAGER_IDENTIFIERS) { - let identifier: StableRecordIdentifier = identifierCacheFor(this).getOrCreateRecordIdentifier(resource); - if (identifier) { - if (RECORD_REFERENCES.has(identifier)) { - return RECORD_REFERENCES.get(identifier); - } - - let reference = new RecordReference(this, identifier); - RECORD_REFERENCES.set(identifier, reference); - return reference; + let identifier: StableRecordIdentifier = identifierCacheFor(this).getOrCreateRecordIdentifier(resource); + if (identifier) { + if (RECORD_REFERENCES.has(identifier)) { + return RECORD_REFERENCES.get(identifier); } - } else { - return internalModelFactoryFor(this).lookup(resource).recordReference; + + let reference = new RecordReference(this, identifier); + RECORD_REFERENCES.set(identifier, reference); + return reference; } } @@ -2714,11 +2709,7 @@ abstract class CoreStore extends Service { internalModel.setupData(data); if (!isUpdate) { - if (RECORD_ARRAY_MANAGER_IDENTIFIERS) { - this.recordArrayManager.recordDidChange(identifier); - } else { - this.recordArrayManager.recordDidChange(internalModel); - } + this.recordArrayManager.recordDidChange(identifier); } return internalModel; diff --git a/packages/store/addon/-private/system/model/internal-model.ts b/packages/store/addon/-private/system/model/internal-model.ts index 541a614c66c..298da5f57f3 100644 --- a/packages/store/addon/-private/system/model/internal-model.ts +++ b/packages/store/addon/-private/system/model/internal-model.ts @@ -13,7 +13,6 @@ import RSVP, { Promise } from 'rsvp'; import { CUSTOM_MODEL_CLASS, FULL_LINKS_ON_RELATIONSHIPS, - RECORD_ARRAY_MANAGER_IDENTIFIERS, RECORD_DATA_ERRORS, RECORD_DATA_STATE, REMOVE_RECORD_ARRAY_MANAGER_LEGACY_COMPAT, @@ -234,11 +233,7 @@ export default class InternalModel { get recordReference() { if (this._recordReference === null) { - if (RECORD_ARRAY_MANAGER_IDENTIFIERS) { - this._recordReference = new RecordReference(this.store, this.identifier); - } else { - this._recordReference = new RecordReference(this.store, this); - } + this._recordReference = new RecordReference(this.store, this.identifier); } return this._recordReference; } @@ -1367,11 +1362,7 @@ export default class InternalModel { @private */ updateRecordArrays() { - if (RECORD_ARRAY_MANAGER_IDENTIFIERS) { - this.store.recordArrayManager.recordDidChange(this.identifier); - } else { - this.store.recordArrayManager.recordDidChange(this); - } + this.store.recordArrayManager.recordDidChange(this.identifier); } setId(id: string) { @@ -1574,12 +1565,7 @@ export default class InternalModel { } let relationshipKind = relationship.relationshipMeta.kind; - let identifierOrInternalModel; - if (RECORD_ARRAY_MANAGER_IDENTIFIERS) { - identifierOrInternalModel = this.identifier; - } else { - identifierOrInternalModel = this; - } + let identifierOrInternalModel = this.identifier; if (relationshipKind === 'belongsTo') { reference = new BelongsToReference(this.store, identifierOrInternalModel, relationship, name); @@ -1594,25 +1580,12 @@ export default class InternalModel { } } -if (RECORD_ARRAY_MANAGER_IDENTIFIERS) { - // in production code, this is only accesssed in `record-array-manager` - // if REMOVE_RECORD_ARRAY_MANAGER_LEGACY_COMPAT is also false - if (!REMOVE_RECORD_ARRAY_MANAGER_LEGACY_COMPAT) { - Object.defineProperty(InternalModel.prototype, '_recordArrays', { - get() { - return recordArraysForIdentifier(this.identifier); - }, - }); - } -} else { - // TODO investigate removing this property since it will only be used in tests - // once RECORD_ARRAY_MANAGER_IDENTIFIERS is turned on +// in production code, this is only accesssed in `record-array-manager` +// if REMOVE_RECORD_ARRAY_MANAGER_LEGACY_COMPAT is also false +if (!REMOVE_RECORD_ARRAY_MANAGER_LEGACY_COMPAT) { Object.defineProperty(InternalModel.prototype, '_recordArrays', { get() { - if (this.__recordArrays === null) { - this.__recordArrays = new Set(); - } - return this.__recordArrays; + return recordArraysForIdentifier(this.identifier); }, }); } diff --git a/packages/store/addon/-private/system/record-array-manager.js b/packages/store/addon/-private/system/record-array-manager.js index 3645874d2c2..81f2611968d 100644 --- a/packages/store/addon/-private/system/record-array-manager.js +++ b/packages/store/addon/-private/system/record-array-manager.js @@ -8,10 +8,7 @@ import { get, set } from '@ember/object'; import { assign } from '@ember/polyfills'; import { run as emberRunloop } from '@ember/runloop'; -import { - RECORD_ARRAY_MANAGER_IDENTIFIERS, - REMOVE_RECORD_ARRAY_MANAGER_LEGACY_COMPAT, -} from '@ember-data/canary-features'; +import { REMOVE_RECORD_ARRAY_MANAGER_LEGACY_COMPAT } from '@ember-data/canary-features'; import isStableIdentifier from '../identifiers/is-stable-identifier'; import { AdapterPopulatedRecordArray, RecordArray } from './record-arrays'; @@ -20,10 +17,6 @@ import { internalModelFactoryFor } from './store/internal-model-factory'; const RecordArraysCache = new WeakMap(); const emberRun = emberRunloop.backburner; -let RecordArrayManager; -// TODO: Remove when RECORD_ARRAY_MANAGER_IDENTIFIERS is turned on -let associateWithRecordArray; - export function recordArraysForIdentifier(identifierOrInternalModel) { if (RecordArraysCache.has(identifierOrInternalModel)) { // return existing Set if exists @@ -35,788 +28,436 @@ export function recordArraysForIdentifier(identifierOrInternalModel) { return RecordArraysCache.get(identifierOrInternalModel); } -/** - @class RecordArrayManager - @private -*/ -if (!RECORD_ARRAY_MANAGER_IDENTIFIERS) { - RecordArrayManager = class LegacyRecordArrayManager { - constructor(options) { - this.store = options.store; - this.isDestroying = false; - this.isDestroyed = false; - this._liveRecordArrays = Object.create(null); - this._pending = Object.create(null); - this._adapterPopulatedRecordArrays = []; - } - - /** - @method recordDidChange - @internal - */ - recordDidChange(internalModel) { - let modelName = internalModel.modelName; - - if (internalModel._pendingRecordArrayManagerFlush) { - return; - } - - internalModel._pendingRecordArrayManagerFlush = true; - - let pending = this._pending; - let models = (pending[modelName] = pending[modelName] || []); - if (models.push(internalModel) !== 1) { - return; - } - - emberRun.schedule('actions', this, this._flush); - } - - _flushPendingInternalModelsForModelName(modelName, internalModels) { - let modelsToRemove = []; - - for (let j = 0; j < internalModels.length; j++) { - let internalModel = internalModels[j]; - // mark internalModels, so they can once again be processed by the - // recordArrayManager - internalModel._pendingRecordArrayManagerFlush = false; - // build up a set of models to ensure we have purged correctly; - if (internalModel.isHiddenFromRecordArrays()) { - modelsToRemove.push(internalModel); - } - } - - let array = this._liveRecordArrays[modelName]; - if (array) { - // TODO: skip if it only changed - // process liveRecordArrays - updateInternalModelsForLiveRecordArray(array, internalModels); - } - - // process adapterPopulatedRecordArrays - if (modelsToRemove.length > 0) { - removeInternalModelsFromAdapterPopulatedRecordArrays(modelsToRemove); - } - } - - _flush() { - let pending = this._pending; - this._pending = Object.create(null); - - for (let modelName in pending) { - this._flushPendingInternalModelsForModelName(modelName, pending[modelName]); - } - } - - _syncLiveRecordArray(array, modelName) { - assert( - `recordArrayManger.syncLiveRecordArray expects modelName not modelClass as the second param`, - typeof modelName === 'string' - ); - let pending = this._pending[modelName]; - let hasPendingChanges = Array.isArray(pending); - let hasNoPotentialDeletions = !hasPendingChanges || pending.length === 0; - let map = internalModelFactoryFor(this.store).modelMapFor(modelName); - let hasNoInsertionsOrRemovals = get(map, 'length') === get(array, 'length'); - - /* - Ideally the recordArrayManager has knowledge of the changes to be applied to - liveRecordArrays, and is capable of strategically flushing those changes and applying - small diffs if desired. However, until we've refactored recordArrayManager, this dirty - check prevents us from unnecessarily wiping out live record arrays returned by peekAll. - */ - if (hasNoPotentialDeletions && hasNoInsertionsOrRemovals) { - return; - } - - if (hasPendingChanges) { - this._flushPendingInternalModelsForModelName(modelName, pending); - delete this._pending[modelName]; - } - - let internalModels = this._visibleInternalModelsByType(modelName); - let modelsToAdd = []; - for (let i = 0; i < internalModels.length; i++) { - let internalModel = internalModels[i]; - let recordArrays = internalModel._recordArrays; - if (recordArrays.has(array) === false) { - recordArrays.add(array); - modelsToAdd.push(internalModel); - } - } - - if (modelsToAdd.length) { - array._pushInternalModels(modelsToAdd); - } - } - - _didUpdateAll(modelName) { - let recordArray = this._liveRecordArrays[modelName]; - if (recordArray) { - set(recordArray, 'isUpdating', false); - } - } +const pendingForIdentifier = new Set([]); +const IMDematerializing = new WeakMap(); + +const getIdentifier = function getIdentifier(identifierOrInternalModel) { + let i = identifierOrInternalModel; + if (!REMOVE_RECORD_ARRAY_MANAGER_LEGACY_COMPAT && !isStableIdentifier(identifierOrInternalModel)) { + // identifier may actually be an internalModel + // but during materialization we will get an identifier that + // has already been removed from the identifiers cache yet + // so it will not behave as if stable. This is a bug we should fix. + i = identifierOrInternalModel.identifier || i; + } - /** - Get the `RecordArray` for a modelName, which contains all loaded records of - given modelName. - - @method liveRecordArrayFor - @param {String} modelName - @return {RecordArray} - */ - liveRecordArrayFor(modelName) { - assert( - `recordArrayManger.liveRecordArrayFor expects modelName not modelClass as the param`, - typeof modelName === 'string' - ); - - let array = this._liveRecordArrays[modelName]; - - if (array) { - // if the array already exists, synchronize - this._syncLiveRecordArray(array, modelName); - } else { - // if the array is being newly created merely create it with its initial - // content already set. This prevents unneeded change events. - let internalModels = this._visibleInternalModelsByType(modelName); - array = this.createRecordArray(modelName, internalModels); - this._liveRecordArrays[modelName] = array; - } + return i; +}; - return array; +// REMOVE_RECORD_ARRAY_MANAGER_LEGACY_COMPAT only +const peekIMCache = function peekIMCache(cache, identifier) { + if (!REMOVE_RECORD_ARRAY_MANAGER_LEGACY_COMPAT) { + let im = IMDematerializing.get(identifier); + if (im === undefined) { + // if not im._isDematerializing + im = cache.peek(identifier); } - _visibleInternalModelsByType(modelName) { - let all = internalModelFactoryFor(this.store).modelMapFor(modelName)._models; - let visible = []; - for (let i = 0; i < all.length; i++) { - let model = all[i]; - if (model.isHiddenFromRecordArrays() === false) { - visible.push(model); - } - } - return visible; - } + return im; + } - /** - Create a `RecordArray` for a modelName. - - @method createRecordArray - @param {String} modelName - @param {Array} [internalModels] - @return {RecordArray} - */ - createRecordArray(modelName, internalModels) { - assert( - `recordArrayManger.createRecordArray expects modelName not modelClass as the param`, - typeof modelName === 'string' - ); - - let array = RecordArray.create({ - modelName, - content: A(internalModels || []), - store: this.store, - isLoaded: true, - manager: this, - }); + return cache.peek(identifier); +}; - if (Array.isArray(internalModels)) { - associateWithRecordArray(internalModels, array); - } +const shouldIncludeInRecordArrays = function shouldIncludeInRecordArrays(store, identifier) { + const cache = internalModelFactoryFor(store); + const internalModel = cache.peek(identifier); - return array; - } + if (internalModel === null) { + return false; + } + return !internalModel.isHiddenFromRecordArrays(); +}; - /** - Create a `AdapterPopulatedRecordArray` for a modelName with given query. - - @method createAdapterPopulatedRecordArray - @param {String} modelName - @param {Object} query - @param {Array} internalModels - @param {Object} payload - @return {AdapterPopulatedRecordArray} - */ - createAdapterPopulatedRecordArray(modelName, query, internalModels, payload) { - assert( - `recordArrayManger.createAdapterPopulatedRecordArray expects modelName not modelClass as the first param, received ${modelName}`, - typeof modelName === 'string' - ); - - let array; - if (Array.isArray(internalModels)) { - array = AdapterPopulatedRecordArray.create({ - modelName, - query: query, - content: A(internalModels), - store: this.store, - manager: this, - isLoaded: true, - isUpdating: false, - meta: assign({}, payload.meta), - links: assign({}, payload.links), - }); - - associateWithRecordArray(internalModels, array); - } else { - array = AdapterPopulatedRecordArray.create({ - modelName, - query: query, - content: A(), - store: this.store, - manager: this, - }); - } +/** + @class RecordArrayManager + @private +*/ +class RecordArrayManager { + constructor(options) { + this.store = options.store; + this.isDestroying = false; + this.isDestroyed = false; + this._liveRecordArrays = Object.create(null); + this._pendingIdentifiers = Object.create(null); + this._adapterPopulatedRecordArrays = []; + } - this._adapterPopulatedRecordArrays.push(array); + /** + * @method getRecordArraysForIdentifier + * @public + * @param {StableIdentifier} param + * @return {RecordArray} array + */ + getRecordArraysForIdentifier(identifier) { + return recordArraysForIdentifier(identifier); + } - return array; + _flushPendingIdentifiersForModelName(modelName, identifiers) { + if (this.isDestroying || this.isDestroyed) { + return; } + let modelsToRemove = []; - /** - Unregister a RecordArray. - So manager will not update this array. - - @method unregisterRecordArray - @param {RecordArray} array - */ - unregisterRecordArray(array) { - let modelName = array.modelName; - - // remove from adapter populated record array - let removedFromAdapterPopulated = removeEntry(this._adapterPopulatedRecordArrays, array); - - if (!removedFromAdapterPopulated) { - let liveRecordArrayForType = this._liveRecordArrays[modelName]; - // unregister live record array - if (liveRecordArrayForType) { - if (array === liveRecordArrayForType) { - delete this._liveRecordArrays[modelName]; - } - } + for (let j = 0; j < identifiers.length; j++) { + let i = identifiers[j]; + // mark identifiers, so they can once again be processed by the + // recordArrayManager + pendingForIdentifier.delete(i); + // build up a set of models to ensure we have purged correctly; + let isIncluded = shouldIncludeInRecordArrays(this.store, i); + if (!isIncluded) { + modelsToRemove.push(i); } } - _associateWithRecordArray(internalModels, array) { - associateWithRecordArray(internalModels, array); + let array = this._liveRecordArrays[modelName]; + if (array) { + // TODO: skip if it only changed + // process liveRecordArrays + updateLiveRecordArray(this.store, array, identifiers); } - willDestroy() { - Object.keys(this._liveRecordArrays).forEach(modelName => this._liveRecordArrays[modelName].destroy()); - this._adapterPopulatedRecordArrays.forEach(destroyEntry); - this.isDestroyed = true; - } - - destroy() { - this.isDestroying = true; - emberRun.schedule('actions', this, this.willDestroy); + // process adapterPopulatedRecordArrays + if (modelsToRemove.length > 0) { + removeFromAdapterPopulatedRecordArrays(this.store, modelsToRemove); } - }; - - const destroyEntry = function destroyEntry(entry) { - entry.destroy(); - }; + } - const removeEntry = function removeEntry(array, item) { - let index = array.indexOf(item); + _flush() { + let pending = this._pendingIdentifiers; + this._pendingIdentifiers = Object.create(null); - if (index !== -1) { - array.splice(index, 1); - return true; + for (let modelName in pending) { + this._flushPendingIdentifiersForModelName(modelName, pending[modelName]); } + } - return false; - }; - - const updateInternalModelsForLiveRecordArray = function updateInternalModelsForLiveRecordArray( - array, - internalModels - ) { + _syncLiveRecordArray(array, modelName) { + assert( + `recordArrayManger.syncLiveRecordArray expects modelName not modelClass as the second param`, + typeof modelName === 'string' + ); + let pending = this._pendingIdentifiers[modelName]; + let hasPendingChanges = Array.isArray(pending); + let hasNoPotentialDeletions = !hasPendingChanges || pending.length === 0; + let map = internalModelFactoryFor(this.store).modelMapFor(modelName); + let hasNoInsertionsOrRemovals = get(map, 'length') === get(array, 'length'); + + /* + Ideally the recordArrayManager has knowledge of the changes to be applied to + liveRecordArrays, and is capable of strategically flushing those changes and applying + small diffs if desired. However, until we've refactored recordArrayManager, this dirty + check prevents us from unnecessarily wiping out live record arrays returned by peekAll. + */ + if (hasNoPotentialDeletions && hasNoInsertionsOrRemovals) { + return; + } + + if (hasPendingChanges) { + this._flushPendingIdentifiersForModelName(modelName, pending); + delete this._pendingIdentifiers[modelName]; + } + + let identifiers = this._visibleIdentifiersByType(modelName); let modelsToAdd = []; - let modelsToRemove = []; - - for (let i = 0; i < internalModels.length; i++) { - let internalModel = internalModels[i]; - let isDeleted = internalModel.isHiddenFromRecordArrays(); - let recordArrays = internalModel._recordArrays; - - if (!isDeleted && !internalModel.isEmpty()) { - if (!recordArrays.has(array)) { - modelsToAdd.push(internalModel); - recordArrays.add(array); - } - } - - if (isDeleted) { - modelsToRemove.push(internalModel); - recordArrays.delete(array); + for (let i = 0; i < identifiers.length; i++) { + let identifier = identifiers[i]; + let recordArrays = recordArraysForIdentifier(identifier); + if (recordArrays.has(array) === false) { + recordArrays.add(array); + modelsToAdd.push(identifier); } } - if (modelsToAdd.length > 0) { - array._pushInternalModels(modelsToAdd); + if (modelsToAdd.length) { + array._pushIdentifiers(modelsToAdd); } - if (modelsToRemove.length > 0) { - array._removeInternalModels(modelsToRemove); - } - }; - - const removeInternalModelsFromAdapterPopulatedRecordArrays = function removeInternalModelsFromAdapterPopulatedRecordArrays( - internalModels - ) { - for (let i = 0; i < internalModels.length; i++) { - removeInternalModelFromAll(internalModels[i]); - } - }; - - const removeInternalModelFromAll = function removeInternalModelFromAll(internalModel) { - const recordArrays = internalModel._recordArrays; - - recordArrays.forEach(function(recordArray) { - recordArray._removeInternalModels([internalModel]); - }); - - recordArrays.clear(); - }; - - associateWithRecordArray = function associateWithRecordArray(internalModels, array) { - for (let i = 0, l = internalModels.length; i < l; i++) { - let internalModel = internalModels[i]; - internalModel._recordArrays.add(array); - } - }; -} else { - const emberRun = emberRunloop.backburner; - const pendingForIdentifier = new Set([]); - const IMDematerializing = new WeakMap(); - - const getIdentifier = function getIdentifier(identifierOrInternalModel) { - let i = identifierOrInternalModel; - if (!REMOVE_RECORD_ARRAY_MANAGER_LEGACY_COMPAT && !isStableIdentifier(identifierOrInternalModel)) { - // identifier may actually be an internalModel - // but during materialization we will get an identifier that - // has already been removed from the identifiers cache yet - // so it will not behave as if stable. This is a bug we should fix. - i = identifierOrInternalModel.identifier || i; - } - - return i; - }; - - // REMOVE_RECORD_ARRAY_MANAGER_LEGACY_COMPAT only - const peekIMCache = function peekIMCache(cache, identifier) { - if (!REMOVE_RECORD_ARRAY_MANAGER_LEGACY_COMPAT) { - let im = IMDematerializing.get(identifier); - if (im === undefined) { - // if not im._isDematerializing - im = cache.peek(identifier); - } - - return im; - } - - return cache.peek(identifier); - }; - - const shouldIncludeInRecordArrays = function shouldIncludeInRecordArrays(store, identifier) { - const cache = internalModelFactoryFor(store); - const internalModel = cache.peek(identifier); - - if (internalModel === null) { - return false; - } - return !internalModel.isHiddenFromRecordArrays(); - }; - - RecordArrayManager = class IdentifiersRecordArrayManager { - constructor(options) { - this.store = options.store; - this.isDestroying = false; - this.isDestroyed = false; - this._liveRecordArrays = Object.create(null); - this._pendingIdentifiers = Object.create(null); - this._adapterPopulatedRecordArrays = []; - } - - /** - * @method getRecordArraysForIdentifier - * @public - * @param {StableIdentifier} param - * @return {RecordArray} array - */ - getRecordArraysForIdentifier(identifier) { - return recordArraysForIdentifier(identifier); - } - - _flushPendingIdentifiersForModelName(modelName, identifiers) { - if (this.isDestroying || this.isDestroyed) { - return; - } - let modelsToRemove = []; - - for (let j = 0; j < identifiers.length; j++) { - let i = identifiers[j]; - // mark identifiers, so they can once again be processed by the - // recordArrayManager - pendingForIdentifier.delete(i); - // build up a set of models to ensure we have purged correctly; - let isIncluded = shouldIncludeInRecordArrays(this.store, i); - if (!isIncluded) { - modelsToRemove.push(i); - } - } - - let array = this._liveRecordArrays[modelName]; - if (array) { - // TODO: skip if it only changed - // process liveRecordArrays - updateLiveRecordArray(this.store, array, identifiers); - } + } - // process adapterPopulatedRecordArrays - if (modelsToRemove.length > 0) { - removeFromAdapterPopulatedRecordArrays(this.store, modelsToRemove); - } + _didUpdateAll(modelName) { + let recordArray = this._liveRecordArrays[modelName]; + if (recordArray) { + set(recordArray, 'isUpdating', false); } + } - _flush() { - let pending = this._pendingIdentifiers; - this._pendingIdentifiers = Object.create(null); - - for (let modelName in pending) { - this._flushPendingIdentifiersForModelName(modelName, pending[modelName]); - } + /** + Get the `RecordArray` for a modelName, which contains all loaded records of + given modelName. + + @method liveRecordArrayFor + @param {String} modelName + @return {RecordArray} + */ + liveRecordArrayFor(modelName) { + assert( + `recordArrayManger.liveRecordArrayFor expects modelName not modelClass as the param`, + typeof modelName === 'string' + ); + + let array = this._liveRecordArrays[modelName]; + + if (array) { + // if the array already exists, synchronize + this._syncLiveRecordArray(array, modelName); + } else { + // if the array is being newly created merely create it with its initial + // content already set. This prevents unneeded change events. + let identifiers = this._visibleIdentifiersByType(modelName); + array = this.createRecordArray(modelName, identifiers); + this._liveRecordArrays[modelName] = array; } - _syncLiveRecordArray(array, modelName) { - assert( - `recordArrayManger.syncLiveRecordArray expects modelName not modelClass as the second param`, - typeof modelName === 'string' - ); - let pending = this._pendingIdentifiers[modelName]; - let hasPendingChanges = Array.isArray(pending); - let hasNoPotentialDeletions = !hasPendingChanges || pending.length === 0; - let map = internalModelFactoryFor(this.store).modelMapFor(modelName); - let hasNoInsertionsOrRemovals = get(map, 'length') === get(array, 'length'); - - /* - Ideally the recordArrayManager has knowledge of the changes to be applied to - liveRecordArrays, and is capable of strategically flushing those changes and applying - small diffs if desired. However, until we've refactored recordArrayManager, this dirty - check prevents us from unnecessarily wiping out live record arrays returned by peekAll. - */ - if (hasNoPotentialDeletions && hasNoInsertionsOrRemovals) { - return; - } - - if (hasPendingChanges) { - this._flushPendingIdentifiersForModelName(modelName, pending); - delete this._pendingIdentifiers[modelName]; - } - - let identifiers = this._visibleIdentifiersByType(modelName); - let modelsToAdd = []; - for (let i = 0; i < identifiers.length; i++) { - let identifier = identifiers[i]; - let recordArrays = recordArraysForIdentifier(identifier); - if (recordArrays.has(array) === false) { - recordArrays.add(array); - modelsToAdd.push(identifier); - } - } + return array; + } - if (modelsToAdd.length) { - array._pushIdentifiers(modelsToAdd); - } - } + _visibleIdentifiersByType(modelName) { + let all = internalModelFactoryFor(this.store).modelMapFor(modelName).recordIdentifiers; + let visible = []; + for (let i = 0; i < all.length; i++) { + let identifier = all[i]; + let shouldInclude = shouldIncludeInRecordArrays(this.store, identifier); - _didUpdateAll(modelName) { - let recordArray = this._liveRecordArrays[modelName]; - if (recordArray) { - set(recordArray, 'isUpdating', false); + if (shouldInclude) { + visible.push(identifier); } } + return visible; + } - /** - Get the `RecordArray` for a modelName, which contains all loaded records of - given modelName. - - @method liveRecordArrayFor - @param {String} modelName - @return {RecordArray} - */ - liveRecordArrayFor(modelName) { - assert( - `recordArrayManger.liveRecordArrayFor expects modelName not modelClass as the param`, - typeof modelName === 'string' - ); - - let array = this._liveRecordArrays[modelName]; - - if (array) { - // if the array already exists, synchronize - this._syncLiveRecordArray(array, modelName); - } else { - // if the array is being newly created merely create it with its initial - // content already set. This prevents unneeded change events. - let identifiers = this._visibleIdentifiersByType(modelName); - array = this.createRecordArray(modelName, identifiers); - this._liveRecordArrays[modelName] = array; - } + /** + Create a `RecordArray` for a modelName. + + @method createRecordArray + @param {String} modelName + @param {Array} [identifiers] + @return {RecordArray} + */ + createRecordArray(modelName, identifiers) { + assert( + `recordArrayManger.createRecordArray expects modelName not modelClass as the param`, + typeof modelName === 'string' + ); + + let array = RecordArray.create({ + modelName, + content: A(identifiers || []), + store: this.store, + isLoaded: true, + manager: this, + }); - return array; + if (Array.isArray(identifiers)) { + this._associateWithRecordArray(identifiers, array); } - _visibleIdentifiersByType(modelName) { - let all = internalModelFactoryFor(this.store).modelMapFor(modelName).recordIdentifiers; - let visible = []; - for (let i = 0; i < all.length; i++) { - let identifier = all[i]; - let shouldInclude = shouldIncludeInRecordArrays(this.store, identifier); - - if (shouldInclude) { - visible.push(identifier); - } - } - return visible; - } + return array; + } - /** - Create a `RecordArray` for a modelName. - - @method createRecordArray - @param {String} modelName - @param {Array} [identifiers] - @return {RecordArray} - */ - createRecordArray(modelName, identifiers) { - assert( - `recordArrayManger.createRecordArray expects modelName not modelClass as the param`, - typeof modelName === 'string' - ); - - let array = RecordArray.create({ + /** + Create a `AdapterPopulatedRecordArray` for a modelName with given query. + + @method createAdapterPopulatedRecordArray + @param {String} modelName + @param {Object} query + @return {AdapterPopulatedRecordArray} + */ + createAdapterPopulatedRecordArray(modelName, query, identifiers, payload) { + assert( + `recordArrayManger.createAdapterPopulatedRecordArray expects modelName not modelClass as the first param, received ${modelName}`, + typeof modelName === 'string' + ); + + let array; + if (Array.isArray(identifiers)) { + array = AdapterPopulatedRecordArray.create({ modelName, - content: A(identifiers || []), + query: query, + content: A(identifiers), store: this.store, - isLoaded: true, manager: this, + isLoaded: true, + isUpdating: false, + meta: assign({}, payload.meta), + links: assign({}, payload.links), }); - if (Array.isArray(identifiers)) { - this._associateWithRecordArray(identifiers, array); - } - - return array; + this._associateWithRecordArray(identifiers, array); + } else { + array = AdapterPopulatedRecordArray.create({ + modelName, + query: query, + content: A(), + store: this.store, + manager: this, + }); } - /** - Create a `AdapterPopulatedRecordArray` for a modelName with given query. - - @method createAdapterPopulatedRecordArray - @param {String} modelName - @param {Object} query - @return {AdapterPopulatedRecordArray} - */ - createAdapterPopulatedRecordArray(modelName, query, identifiers, payload) { - assert( - `recordArrayManger.createAdapterPopulatedRecordArray expects modelName not modelClass as the first param, received ${modelName}`, - typeof modelName === 'string' - ); - - let array; - if (Array.isArray(identifiers)) { - array = AdapterPopulatedRecordArray.create({ - modelName, - query: query, - content: A(identifiers), - store: this.store, - manager: this, - isLoaded: true, - isUpdating: false, - meta: assign({}, payload.meta), - links: assign({}, payload.links), - }); - - this._associateWithRecordArray(identifiers, array); - } else { - array = AdapterPopulatedRecordArray.create({ - modelName, - query: query, - content: A(), - store: this.store, - manager: this, - }); - } - - this._adapterPopulatedRecordArrays.push(array); + this._adapterPopulatedRecordArrays.push(array); - return array; - } + return array; + } - /** - Unregister a RecordArray. - So manager will not update this array. - - @method unregisterRecordArray - @param {RecordArray} array - */ - unregisterRecordArray(array) { - let modelName = array.modelName; - - // remove from adapter populated record array - let removedFromAdapterPopulated = removeFromArray(this._adapterPopulatedRecordArrays, array); - - if (!removedFromAdapterPopulated) { - let liveRecordArrayForType = this._liveRecordArrays[modelName]; - // unregister live record array - if (liveRecordArrayForType) { - if (array === liveRecordArrayForType) { - delete this._liveRecordArrays[modelName]; - } + /** + Unregister a RecordArray. + So manager will not update this array. + + @method unregisterRecordArray + @param {RecordArray} array + */ + unregisterRecordArray(array) { + let modelName = array.modelName; + + // remove from adapter populated record array + let removedFromAdapterPopulated = removeFromArray(this._adapterPopulatedRecordArrays, array); + + if (!removedFromAdapterPopulated) { + let liveRecordArrayForType = this._liveRecordArrays[modelName]; + // unregister live record array + if (liveRecordArrayForType) { + if (array === liveRecordArrayForType) { + delete this._liveRecordArrays[modelName]; } } } + } - /** - * @method _associateWithRecordArray - * @private - * @param {StableIdentifier} identifiers - * @param {RecordArray} array - */ - _associateWithRecordArray(identifiers, array) { - for (let i = 0, l = identifiers.length; i < l; i++) { - let identifier = identifiers[i]; - identifier = getIdentifier(identifier); - let recordArrays = this.getRecordArraysForIdentifier(identifier); - recordArrays.add(array); - } - } - - /** - @method recordDidChange - @internal - */ - recordDidChange(identifier) { - if (this.isDestroying || this.isDestroyed) { - return; - } - let modelName = identifier.type; + /** + * @method _associateWithRecordArray + * @private + * @param {StableIdentifier} identifiers + * @param {RecordArray} array + */ + _associateWithRecordArray(identifiers, array) { + for (let i = 0, l = identifiers.length; i < l; i++) { + let identifier = identifiers[i]; identifier = getIdentifier(identifier); + let recordArrays = this.getRecordArraysForIdentifier(identifier); + recordArrays.add(array); + } + } - if (!REMOVE_RECORD_ARRAY_MANAGER_LEGACY_COMPAT) { - const cache = internalModelFactoryFor(this.store); - const im = peekIMCache(cache, identifier); - if (im && im._isDematerializing) { - IMDematerializing.set(identifier, im); - } - } + /** + @method recordDidChange + @internal + */ + recordDidChange(identifier) { + if (this.isDestroying || this.isDestroyed) { + return; + } + let modelName = identifier.type; + identifier = getIdentifier(identifier); - if (pendingForIdentifier.has(identifier)) { - return; + if (!REMOVE_RECORD_ARRAY_MANAGER_LEGACY_COMPAT) { + const cache = internalModelFactoryFor(this.store); + const im = peekIMCache(cache, identifier); + if (im && im._isDematerializing) { + IMDematerializing.set(identifier, im); } + } - pendingForIdentifier.add(identifier); + if (pendingForIdentifier.has(identifier)) { + return; + } - let pending = this._pendingIdentifiers; - let models = (pending[modelName] = pending[modelName] || []); - if (models.push(identifier) !== 1) { - return; - } + pendingForIdentifier.add(identifier); - emberRun.schedule('actions', this, this._flush); + let pending = this._pendingIdentifiers; + let models = (pending[modelName] = pending[modelName] || []); + if (models.push(identifier) !== 1) { + return; } - willDestroy() { - Object.keys(this._liveRecordArrays).forEach(modelName => this._liveRecordArrays[modelName].destroy()); - this._adapterPopulatedRecordArrays.forEach(entry => entry.destroy()); - this.isDestroyed = true; - } + emberRun.schedule('actions', this, this._flush); + } - destroy() { - this.isDestroying = true; - emberRun.schedule('actions', this, this.willDestroy); - } - }; + willDestroy() { + Object.keys(this._liveRecordArrays).forEach(modelName => this._liveRecordArrays[modelName].destroy()); + this._adapterPopulatedRecordArrays.forEach(entry => entry.destroy()); + this.isDestroyed = true; + } - const removeFromArray = function removeFromArray(array, item) { - let index = array.indexOf(item); + destroy() { + this.isDestroying = true; + emberRun.schedule('actions', this, this.willDestroy); + } +} - if (index !== -1) { - array.splice(index, 1); - return true; - } +const removeFromArray = function removeFromArray(array, item) { + let index = array.indexOf(item); - return false; - }; + if (index !== -1) { + array.splice(index, 1); + return true; + } - const updateLiveRecordArray = function updateLiveRecordArray(store, recordArray, identifiers) { - let identifiersToAdd = []; - let identifiersToRemove = []; + return false; +}; - for (let i = 0; i < identifiers.length; i++) { - let identifier = identifiers[i]; - let shouldInclude = shouldIncludeInRecordArrays(store, identifier); - let recordArrays = recordArraysForIdentifier(identifier); +const updateLiveRecordArray = function updateLiveRecordArray(store, recordArray, identifiers) { + let identifiersToAdd = []; + let identifiersToRemove = []; - if (shouldInclude) { - if (!recordArrays.has(recordArray)) { - identifiersToAdd.push(identifier); - recordArrays.add(recordArray); - } - } + for (let i = 0; i < identifiers.length; i++) { + let identifier = identifiers[i]; + let shouldInclude = shouldIncludeInRecordArrays(store, identifier); + let recordArrays = recordArraysForIdentifier(identifier); - if (!shouldInclude) { - identifiersToRemove.push(identifier); - recordArrays.delete(recordArray); + if (shouldInclude) { + if (!recordArrays.has(recordArray)) { + identifiersToAdd.push(identifier); + recordArrays.add(recordArray); } } - if (identifiersToAdd.length > 0) { - pushIdentifiers(recordArray, identifiersToAdd, internalModelFactoryFor(store)); - } - if (identifiersToRemove.length > 0) { - removeIdentifiers(recordArray, identifiersToRemove, internalModelFactoryFor(store)); - } - }; - - const pushIdentifiers = function pushIdentifiers(recordArray, identifiers, cache) { - if (!REMOVE_RECORD_ARRAY_MANAGER_LEGACY_COMPAT && !recordArray._pushIdentifiers) { - // deprecate('not allowed to use this intimate api any more'); - recordArray._pushInternalModels(identifiers.map(i => peekIMCache(cache, i))); - } else { - recordArray._pushIdentifiers(identifiers); - } - }; - const removeIdentifiers = function removeIdentifiers(recordArray, identifiers, cache) { - if (!REMOVE_RECORD_ARRAY_MANAGER_LEGACY_COMPAT && !recordArray._removeIdentifiers) { - // deprecate('not allowed to use this intimate api any more'); - recordArray._removeInternalModels(identifiers.map(i => peekIMCache(cache, i))); - } else { - recordArray._removeIdentifiers(identifiers); + if (!shouldInclude) { + identifiersToRemove.push(identifier); + recordArrays.delete(recordArray); } - }; + } - const removeFromAdapterPopulatedRecordArrays = function removeFromAdapterPopulatedRecordArrays(store, identifiers) { - for (let i = 0; i < identifiers.length; i++) { - removeFromAll(store, identifiers[i]); - } - }; + if (identifiersToAdd.length > 0) { + pushIdentifiers(recordArray, identifiersToAdd, internalModelFactoryFor(store)); + } + if (identifiersToRemove.length > 0) { + removeIdentifiers(recordArray, identifiersToRemove, internalModelFactoryFor(store)); + } +}; + +const pushIdentifiers = function pushIdentifiers(recordArray, identifiers, cache) { + if (!REMOVE_RECORD_ARRAY_MANAGER_LEGACY_COMPAT && !recordArray._pushIdentifiers) { + // deprecate('not allowed to use this intimate api any more'); + recordArray._pushInternalModels(identifiers.map(i => peekIMCache(cache, i))); + } else { + recordArray._pushIdentifiers(identifiers); + } +}; +const removeIdentifiers = function removeIdentifiers(recordArray, identifiers, cache) { + if (!REMOVE_RECORD_ARRAY_MANAGER_LEGACY_COMPAT && !recordArray._removeIdentifiers) { + // deprecate('not allowed to use this intimate api any more'); + recordArray._removeInternalModels(identifiers.map(i => peekIMCache(cache, i))); + } else { + recordArray._removeIdentifiers(identifiers); + } +}; - const removeFromAll = function removeFromAll(store, identifier) { - identifier = getIdentifier(identifier); - const recordArrays = recordArraysForIdentifier(identifier); - const cache = internalModelFactoryFor(store); +const removeFromAdapterPopulatedRecordArrays = function removeFromAdapterPopulatedRecordArrays(store, identifiers) { + for (let i = 0; i < identifiers.length; i++) { + removeFromAll(store, identifiers[i]); + } +}; - recordArrays.forEach(function(recordArray) { - removeIdentifiers(recordArray, [identifier], cache); - }); +const removeFromAll = function removeFromAll(store, identifier) { + identifier = getIdentifier(identifier); + const recordArrays = recordArraysForIdentifier(identifier); + const cache = internalModelFactoryFor(store); - recordArrays.clear(); - }; -} + recordArrays.forEach(function(recordArray) { + removeIdentifiers(recordArray, [identifier], cache); + }); -export { associateWithRecordArray }; + recordArrays.clear(); +}; export default RecordArrayManager; diff --git a/packages/store/addon/-private/system/record-arrays/adapter-populated-record-array.js b/packages/store/addon/-private/system/record-arrays/adapter-populated-record-array.js index f698a0a23c3..429a6dc9da2 100644 --- a/packages/store/addon/-private/system/record-arrays/adapter-populated-record-array.js +++ b/packages/store/addon/-private/system/record-arrays/adapter-populated-record-array.js @@ -4,7 +4,6 @@ import { assign } from '@ember/polyfills'; import { once } from '@ember/runloop'; import { DEBUG } from '@glimmer/env'; -import { RECORD_ARRAY_MANAGER_IDENTIFIERS } from '@ember-data/canary-features'; import { DEPRECATE_EVENTED_API_USAGE } from '@ember-data/private-build-infra/deprecations'; import RecordArray from './record-array'; @@ -98,32 +97,16 @@ let AdapterPopulatedRecordArray = RecordArray.extend({ } } }, -}); -if (RECORD_ARRAY_MANAGER_IDENTIFIERS) { - AdapterPopulatedRecordArray = AdapterPopulatedRecordArray.extend({ - /** - @method _setIdentifiers - @param {StableRecordIdentifier[]} identifiers - @param {Object} payload normalized payload - @internal - */ - _setIdentifiers(identifiers, payload) { - this._setObjects(identifiers, payload); - }, - }); -} else { - AdapterPopulatedRecordArray = AdapterPopulatedRecordArray.extend({ - /** - @method _setInternalModels - @param {Array} internalModels - @param {Object} payload normalized payload - @internal - */ - _setInternalModels(internalModels, payload) { - this._setObjects(internalModels, payload); - }, - }); -} + /** + @method _setIdentifiers + @param {StableRecordIdentifier[]} identifiers + @param {Object} payload normalized payload + @internal + */ + _setIdentifiers(identifiers, payload) { + this._setObjects(identifiers, payload); + }, +}); export default AdapterPopulatedRecordArray; diff --git a/packages/store/addon/-private/system/record-arrays/record-array.js b/packages/store/addon/-private/system/record-arrays/record-array.js index be2684c7486..115eaf72503 100644 --- a/packages/store/addon/-private/system/record-arrays/record-array.js +++ b/packages/store/addon/-private/system/record-arrays/record-array.js @@ -7,8 +7,6 @@ import { DEBUG } from '@glimmer/env'; import { Promise } from 'rsvp'; -import { RECORD_ARRAY_MANAGER_IDENTIFIERS } from '@ember-data/canary-features'; - import DeprecatedEvented from '../deprecated-evented'; import { PromiseArray } from '../promise-proxies'; import SnapshotRecordArray from '../snapshot-record-array'; @@ -122,13 +120,8 @@ let RecordArray = ArrayProxy.extend(DeprecatedEvented, { @return {Model} record */ objectAtContent(index) { - 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; - } + let identifier = get(this, 'content').objectAt(index); + return identifier ? recordForIdentifier(this.store, identifier) : undefined; }, /** @@ -236,105 +229,54 @@ let RecordArray = ArrayProxy.extend(DeprecatedEvented, { // this is private for users, but public for ember-data internals return new SnapshotRecordArray(this, this.get('meta'), options); }, -}); - -if (RECORD_ARRAY_MANAGER_IDENTIFIERS) { - RecordArray = RecordArray.extend({ - /** - @method _dissociateFromOwnRecords - @internal - */ - _dissociateFromOwnRecords() { - this.get('content').forEach(identifier => { - let recordArrays = this.manager.getRecordArraysForIdentifier(identifier); - - if (recordArrays) { - recordArrays.delete(this); - } - }); - }, - - /** - Adds identifiers to the `RecordArray` without duplicates - - @method _pushIdentifiers - @internal - @param {StableRecordIdentifier[]} identifiers - */ - _pushIdentifiers(identifiers) { - get(this, 'content').pushObjects(identifiers); - }, - /** - Removes identifiers from the `RecordArray`. + /** + @method _dissociateFromOwnRecords + @internal + */ + _dissociateFromOwnRecords() { + this.get('content').forEach(identifier => { + let recordArrays = this.manager.getRecordArraysForIdentifier(identifier); - @method _removeIdentifiers - @internal - @param {StableRecordIdentifier[]} identifiers - */ - _removeIdentifiers(identifiers) { - get(this, 'content').removeObjects(identifiers); - }, + if (recordArrays) { + recordArrays.delete(this); + } + }); + }, - /** - @method _takeSnapshot - @internal - */ - _takeSnapshot() { - return get(this, 'content').map(identifier => - internalModelFactoryFor(this.store) - .lookup(identifier) - .createSnapshot() - ); - }, - }); -} else { - RecordArray = RecordArray.extend({ - /** - @method _dissociateFromOwnRecords - @internal - */ - _dissociateFromOwnRecords() { - this.get('content').forEach(internalModel => { - let recordArrays = internalModel.__recordArrays; + /** + Adds identifiers to the `RecordArray` without duplicates - if (recordArrays) { - recordArrays.delete(this); - } - }); - }, + @method _pushIdentifiers + @internal + @param {StableRecordIdentifier[]} identifiers + */ + _pushIdentifiers(identifiers) { + get(this, 'content').pushObjects(identifiers); + }, - /** - Adds an internal model to the `RecordArray` without duplicates - @method _pushInternalModels - @private - @param {InternalModel} internalModel - */ - _pushInternalModels(internalModels) { - // pushObjects because the internalModels._recordArrays set was already - // consulted for inclusion, so addObject and its on .contains call is not - // required. - get(this, 'content').pushObjects(internalModels); - }, + /** + Removes identifiers from the `RecordArray`. - /** - Removes an internalModel to the `RecordArray`. - @method _removeInternalModels - @private - @param {InternalModel} internalModel - */ - _removeInternalModels(internalModels) { - get(this, 'content').removeObjects(internalModels); - }, + @method _removeIdentifiers + @internal + @param {StableRecordIdentifier[]} identifiers + */ + _removeIdentifiers(identifiers) { + get(this, 'content').removeObjects(identifiers); + }, - /** - @method _takeSnapshot - @internal - */ - _takeSnapshot() { - return get(this, 'content').map(internalModel => internalModel.createSnapshot()); - }, - }); -} + /** + @method _takeSnapshot + @internal + */ + _takeSnapshot() { + return get(this, 'content').map(identifier => + internalModelFactoryFor(this.store) + .lookup(identifier) + .createSnapshot() + ); + }, +}); export default RecordArray; diff --git a/packages/store/addon/-private/system/references/belongs-to.js b/packages/store/addon/-private/system/references/belongs-to.js index 6f1e32415da..d8fee64bd89 100644 --- a/packages/store/addon/-private/system/references/belongs-to.js +++ b/packages/store/addon/-private/system/references/belongs-to.js @@ -2,7 +2,6 @@ import { deprecate } from '@ember/debug'; import { resolve } from 'rsvp'; -import { RECORD_ARRAY_MANAGER_IDENTIFIERS } from '@ember-data/canary-features'; import { DEPRECATE_BELONGS_TO_REFERENCE_PUSH } from '@ember-data/private-build-infra/deprecations'; import { assertPolymorphicType } from '@ember-data/store/-debug'; @@ -28,13 +27,8 @@ export default class BelongsToReference extends Reference { this.key = key; this.belongsToRelationship = belongsToRelationship; this.type = belongsToRelationship.relationshipMeta.type; - if (RECORD_ARRAY_MANAGER_IDENTIFIERS) { - this.parent = internalModelFactoryFor(store).peek(parentIMOrIdentifier).recordReference; - this.parentIdentifier = parentIMOrIdentifier; - } else { - this.parent = parentIMOrIdentifier.recordReference; - this.parentInternalModel = parentIMOrIdentifier; - } + this.parent = internalModelFactoryFor(store).peek(parentIMOrIdentifier).recordReference; + this.parentIdentifier = parentIMOrIdentifier; // TODO inverse } @@ -288,12 +282,8 @@ export default class BelongsToReference extends Reference { @return {Promise} a promise that resolves with the record in this belongs-to relationship. */ load(options) { - if (RECORD_ARRAY_MANAGER_IDENTIFIERS) { - let parentInternalModel = internalModelFactoryFor(this.store).peek(this.parentIdentifier); - return parentInternalModel.getBelongsTo(this.key, options); - } else { - return this.parentInternalModel.getBelongsTo(this.key, options); - } + let parentInternalModel = internalModelFactoryFor(this.store).peek(this.parentIdentifier); + return parentInternalModel.getBelongsTo(this.key, options); } /** @@ -346,12 +336,7 @@ export default class BelongsToReference extends Reference { @return {Promise} a promise that resolves with the record in this belongs-to relationship after the reload has completed. */ reload(options) { - let parentInternalModel; - if (RECORD_ARRAY_MANAGER_IDENTIFIERS) { - parentInternalModel = internalModelFactoryFor(this.store).peek(this.parentIdentifier); - } else { - parentInternalModel = this.parentInternalModel; - } + let parentInternalModel = internalModelFactoryFor(this.store).peek(this.parentIdentifier); return parentInternalModel.reloadBelongsTo(this.key, options).then(internalModel => { return this.value(); }); diff --git a/packages/store/addon/-private/system/references/has-many.js b/packages/store/addon/-private/system/references/has-many.js index cde08c1e3e3..9232d0ab3bc 100644 --- a/packages/store/addon/-private/system/references/has-many.js +++ b/packages/store/addon/-private/system/references/has-many.js @@ -2,7 +2,6 @@ import { DEBUG } from '@glimmer/env'; import { resolve } from 'rsvp'; -import { RECORD_ARRAY_MANAGER_IDENTIFIERS } from '@ember-data/canary-features'; import { assertPolymorphicType } from '@ember-data/store/-debug'; import recordDataFor from '../record-data-for'; @@ -27,11 +26,7 @@ export default class HasManyReference extends Reference { this.hasManyRelationship = hasManyRelationship; this.type = hasManyRelationship.relationshipMeta.type; - if (RECORD_ARRAY_MANAGER_IDENTIFIERS) { - this.parent = internalModelFactoryFor(store).peek(parentIMOrIdentifier).recordReference; - } else { - this.parent = parentIMOrIdentifier.recordReference; - } + this.parent = internalModelFactoryFor(store).peek(parentIMOrIdentifier).recordReference; // TODO inverse } diff --git a/packages/store/addon/-private/system/references/record.ts b/packages/store/addon/-private/system/references/record.ts index 2390c75821b..c4f4441a149 100644 --- a/packages/store/addon/-private/system/references/record.ts +++ b/packages/store/addon/-private/system/references/record.ts @@ -1,7 +1,5 @@ import RSVP, { resolve } from 'rsvp'; -import { RECORD_ARRAY_MANAGER_IDENTIFIERS } from '@ember-data/canary-features'; - import Reference, { internalModelForReference, REFERENCE_CACHE } from './reference'; type SingleResourceDocument = import('../../ts-interfaces/ember-data-json-api').SingleResourceDocument; @@ -21,24 +19,16 @@ type StableRecordIdentifier = import('../../ts-interfaces/identifier').StableRec */ export default class RecordReference extends Reference { public get type(): string { - if (RECORD_ARRAY_MANAGER_IDENTIFIERS) { - return this.identifier().type; - } else { - return internalModelForReference(this)!.modelName; - } + return this.identifier().type; } private get _id(): string | null { - if (RECORD_ARRAY_MANAGER_IDENTIFIERS) { - let identifier = this.identifier(); - if (identifier) { - return identifier.id; - } - - return null; - } else { - return internalModelForReference(this)!.id; + let identifier = this.identifier(); + if (identifier) { + return identifier.id; } + + return null; } /** diff --git a/packages/store/addon/-private/system/references/reference.ts b/packages/store/addon/-private/system/references/reference.ts index 35f5964e1c1..6e1bb871951 100644 --- a/packages/store/addon/-private/system/references/reference.ts +++ b/packages/store/addon/-private/system/references/reference.ts @@ -1,6 +1,6 @@ import { deprecate } from '@ember/debug'; -import { FULL_LINKS_ON_RELATIONSHIPS, RECORD_ARRAY_MANAGER_IDENTIFIERS } from '@ember-data/canary-features'; +import { FULL_LINKS_ON_RELATIONSHIPS } from '@ember-data/canary-features'; import { DEPRECATE_REFERENCE_INTERNAL_MODEL } from '@ember-data/private-build-infra/deprecations'; import { internalModelFactoryFor } from '../store/internal-model-factory'; @@ -32,15 +32,10 @@ function isResourceIdentiferWithRelatedLinks( return value && value.links && value.links.related; } -// TODO: simplify after 3.23 release and only store identifier -export const REFERENCE_CACHE = new WeakMap(); +export const REFERENCE_CACHE = new WeakMap(); export function internalModelForReference(reference: Reference): InternalModel | null | undefined { - if (RECORD_ARRAY_MANAGER_IDENTIFIERS) { - return internalModelFactoryFor(reference.store).peek(REFERENCE_CACHE.get(reference) as StableRecordIdentifier); - } else { - return REFERENCE_CACHE.get(reference) as InternalModel; - } + return internalModelFactoryFor(reference.store).peek(REFERENCE_CACHE.get(reference) as StableRecordIdentifier); } /** @@ -53,20 +48,12 @@ interface Reference { links(): PaginationLinks | null; } abstract class Reference { - constructor(public store: CoreStore, identifierOrInternalModel: InternalModel | StableRecordIdentifier) { - if (RECORD_ARRAY_MANAGER_IDENTIFIERS) { - REFERENCE_CACHE.set(this, identifierOrInternalModel); - } else { - REFERENCE_CACHE.set(this, identifierOrInternalModel); - } + constructor(public store: CoreStore, identifier: StableRecordIdentifier) { + REFERENCE_CACHE.set(this, identifier); } get recordData() { - if (RECORD_ARRAY_MANAGER_IDENTIFIERS) { - return this.store.recordDataFor(REFERENCE_CACHE.get(this) as StableRecordIdentifier, false); - } else { - return internalModelForReference(this)?._recordData; - } + return this.store.recordDataFor(REFERENCE_CACHE.get(this) as StableRecordIdentifier, false); } public _resource(): ResourceIdentifier | JsonApiRelationship | void {} diff --git a/packages/store/addon/-private/system/store/finders.js b/packages/store/addon/-private/system/store/finders.js index 93278f44197..fb21e87d7ec 100644 --- a/packages/store/addon/-private/system/store/finders.js +++ b/packages/store/addon/-private/system/store/finders.js @@ -5,7 +5,7 @@ import { DEBUG } from '@glimmer/env'; import { Promise } from 'rsvp'; -import { RECORD_ARRAY_MANAGER_IDENTIFIERS, REQUEST_SERVICE } from '@ember-data/canary-features'; +import { REQUEST_SERVICE } from '@ember-data/canary-features'; import coerceId from '../coerce-id'; import { _bind, _guard, _objectIsAlive, guardDestroyedStore } from './common'; @@ -375,29 +375,16 @@ export function _query(adapter, store, modelName, query, recordArray, options) { 'The response to store.query is expected to be an array but it was a single record. Please wrap your response in an array or use `store.queryRecord` to query for a single record.', Array.isArray(internalModels) ); - if (RECORD_ARRAY_MANAGER_IDENTIFIERS) { - let identifiers = internalModels.map(im => im.identifier); - if (recordArray) { - recordArray._setIdentifiers(identifiers, payload); - } else { - recordArray = store.recordArrayManager.createAdapterPopulatedRecordArray( - modelName, - query, - identifiers, - payload - ); - } + let identifiers = internalModels.map(im => im.identifier); + if (recordArray) { + recordArray._setIdentifiers(identifiers, payload); } else { - if (recordArray) { - recordArray._setInternalModels(internalModels, payload); - } else { - recordArray = store.recordArrayManager.createAdapterPopulatedRecordArray( - modelName, - query, - internalModels, - payload - ); - } + recordArray = store.recordArrayManager.createAdapterPopulatedRecordArray( + modelName, + query, + identifiers, + payload + ); } return recordArray;