From 7e66606481bf9b601f18a209ef20d58a41473936 Mon Sep 17 00:00:00 2001 From: Chris Thoburn Date: Tue, 9 Aug 2022 01:11:58 -0700 Subject: [PATCH] Love of Mine (#8084) * ill follow you into the dark * more sanity * fix imports * get tests passing * replace usage of internalModel.isHiddenFromRecordArrays * begin move of deleteRecord logic into the store * trim trim trim * even more trim * still a lot to do, but starting to take shape * the shape of things * fix most tests, 44 to go * 71 test failures to go, implemente PromiseManyArray deprecation from RFC https://github.com/emberjs/rfcs/pull/745 * add better debugging capabilities * cleanup * further rationalization * logging improvements * progress * down to 36 failed tests * down to 17 failed tests * cleanup * All the cleanup * fix lint * all the main tests fixed * bump some deps * make nice for deprecation stripping * some minor cleanup * fix some scenarios * fix lock * fix node14 --- .eslintrc.js | 33 +- CHANGELOG.md | 30 +- ember-data-types/q/ds-model.ts | 2 - ember-data-types/q/identifier.ts | 5 +- .../q/minimum-adapter-interface.ts | 4 +- .../q/minimum-serializer-interface.ts | 2 +- .../q/record-data-record-wrapper.ts | 12 +- ember-data-types/q/record-data-schemas.ts | 2 +- ember-data-types/q/record-data.ts | 2 - .../q/relationship-record-data.ts | 2 +- packages/-ember-data/addon/-private/index.ts | 2 - packages/-ember-data/addon/index.js | 2 - packages/-ember-data/config/ember-try.js | 4 +- packages/-ember-data/ember-cli-build.js | 16 +- .../node-tests/fixtures/expected.js | 4 - .../relationships/belongs-to-test.js | 48 +- .../acceptance/relationships/has-many-test.js | 276 ++++- .../integration/adapter/find-all-test.js | 4 +- .../adapter/json-api-adapter-test.js | 16 +- .../integration/adapter/rest-adapter-test.js | 209 ++-- .../rest-adapter/create-record-test.js | 55 +- .../adapter/rest-adapter/find-record-test.js | 36 +- .../integration/adapter/store-adapter-test.js | 19 +- .../tests/integration/application-test.js | 6 +- .../non-dasherized-lookups-test.js | 12 +- .../tests/integration/inverse-test.js | 2 +- .../polymorphic-belongs-to-test.js | 11 +- .../integration/record-array-manager-test.js | 18 +- .../tests/integration/record-array-test.js | 22 +- .../adapter-populated-record-array-test.js | 92 +- .../record-arrays/peeked-records-test.js | 2 +- .../record-data/record-data-errors-test.ts | 14 +- .../record-data/record-data-state-test.ts | 51 +- .../record-data/record-data-test.ts | 18 +- .../record-data/store-wrapper-test.ts | 13 +- .../record-data/unloading-record-data-test.js | 8 +- .../records/collection-save-test.js | 2 +- .../integration/records/create-record-test.js | 38 +- .../integration/records/delete-record-test.js | 73 +- .../integration/records/edit-record-test.js | 98 +- .../tests/integration/records/load-test.js | 58 +- .../records/relationship-changes-test.js | 751 +++++++------- .../tests/integration/records/reload-test.js | 36 +- .../integration/records/rematerialize-test.js | 22 +- .../tests/integration/records/save-test.js | 23 +- .../tests/integration/records/unload-test.js | 870 +++++++--------- .../integration/references/has-many-test.js | 34 +- .../relationships/belongs-to-test.js | 185 ++-- .../relationships/has-many-test.js | 958 ++++++++---------- .../inverse-relationship-load-test.js | 422 ++++---- .../inverse-relationships-test.js | 156 +-- .../relationships/json-api-links-test.js | 235 ++--- .../relationships/many-to-many-test.js | 115 +-- .../relationships/nested-relationship-test.js | 149 ++- .../relationships/one-to-many-test.js | 258 +++-- .../relationships/one-to-one-test.js | 144 ++- .../polymorphic-mixins-belongs-to-test.js | 12 +- .../polymorphic-mixins-has-many-test.js | 27 +- .../relationships/promise-many-array-test.js | 169 +-- .../embedded-records-mixin-test.js | 12 +- .../serializers/json-api-serializer-test.js | 176 ++-- .../serializers/json-serializer-test.js | 15 +- .../serializers/rest-serializer-test.js | 109 +- .../tests/integration/snapshot-test.js | 34 +- .../tests/integration/store-test.js | 38 +- .../tests/integration/store/query-test.js | 27 +- packages/-ember-data/tests/test-helper.js | 2 - .../group-records-for-find-many-test.js | 2 +- .../custom-class-model-test.ts | 6 +- .../-ember-data/tests/unit/many-array-test.js | 11 +- packages/-ember-data/tests/unit/model-test.js | 140 +-- .../tests/unit/model/errors-test.js | 28 +- .../tests/unit/model/merge-test.js | 66 +- .../model/relationships/belongs-to-test.js | 187 ++-- .../unit/model/relationships/has-many-test.js | 119 +-- .../model/relationships/record-array-test.js | 15 +- .../unit/model/rollback-attributes-test.js | 138 +-- .../-ember-data/tests/unit/private-test.js | 9 - .../tests/unit/promise-proxies-test.js | 2 +- .../adapter-populated-record-array-test.js | 52 +- .../unit/record-arrays/record-array-test.js | 24 +- .../tests/unit/store/adapter-interop-test.js | 55 +- .../tests/unit/store/create-record-test.js | 8 +- .../tests/unit/store/finders-test.js | 4 +- .../-ember-data/tests/unit/store/push-test.js | 56 +- .../tests/unit/store/unload-test.js | 5 +- .../polymorphic-relationship-payloads-test.js | 68 +- packages/-ember-data/tests/unit/utils-test.js | 124 +-- .../adapter/addon/-private/build-url-mixin.ts | 4 +- .../-private/utils/serialize-into-hash.ts | 2 +- packages/adapter/addon/error.js | 2 +- packages/adapter/addon/index.ts | 4 +- packages/adapter/addon/json-api.ts | 6 +- packages/adapter/addon/rest.ts | 13 +- packages/debug/addon/index.js | 33 +- packages/model/addon/-private/attr.js | 15 +- packages/model/addon/-private/belongs-to.js | 12 +- packages/model/addon/-private/errors.ts | 12 +- packages/model/addon/-private/has-many.js | 10 +- .../model/addon/-private/legacy-data-fetch.js | 16 +- .../model/addon/-private/legacy-data-utils.ts | 2 +- .../-private/legacy-relationships-support.ts | 75 +- packages/model/addon/-private/many-array.ts | 20 +- packages/model/addon/-private/model.js | 166 +-- .../model/addon/-private/notify-changes.ts | 2 +- .../addon/-private/promise-many-array.ts | 282 ++++-- packages/model/addon/-private/record-state.ts | 58 +- .../addon/-private/references/belongs-to.ts | 10 +- .../addon/-private/references/has-many.ts | 14 +- .../addon/current-deprecations.ts | 2 +- .../private-build-infra/addon/debugging.ts | 5 + .../private-build-infra/addon/deprecations.ts | 2 +- .../addon-build-config-for-data-package.js | 20 +- .../private-build-infra/src/debug-macros.js | 17 +- packages/private-build-infra/src/debugging.js | 16 + .../src/stripped-build-plugins.js | 4 +- .../src/utilities/require-module.js | 16 +- .../record-data/addon/-private/coerce-id.ts | 4 +- .../addon/-private/graph/-utils.ts | 3 +- .../record-data/addon/-private/graph/index.ts | 47 +- .../record-data/addon/-private/record-data.ts | 152 +-- .../relationships/state/belongs-to.ts | 19 +- .../-private/relationships/state/has-many.ts | 48 +- packages/record-data/ember-cli-build.js | 4 +- packages/record-data/index.js | 1 + packages/record-data/tests/test-helper.js | 2 - .../addon/-private/embedded-records-mixin.js | 11 +- packages/serializer/addon/-private/index.js | 1 - packages/serializer/addon/-private/utils.js | 10 - packages/serializer/addon/json-api.js | 2 +- packages/serializer/addon/json.js | 27 +- packages/serializer/addon/rest.js | 4 +- packages/store/addon/-debug/index.js | 2 +- .../-private/{ => caches}/identifier-cache.ts | 91 +- .../addon/-private/caches/instance-cache.ts | 759 ++++++++++++++ .../-private/{ => caches}/record-data-for.ts | 4 +- packages/store/addon/-private/identity-map.ts | 54 - packages/store/addon/-private/index.ts | 27 +- .../store/addon/-private/instance-cache.ts | 387 ------- .../addon/-private/internal-model-factory.ts | 359 ------- .../addon/-private/internal-model-map.ts | 121 --- .../record-reference.ts | 6 +- .../schema-definition-service.ts | 9 +- .../shim-model-class.ts | 4 +- .../{ => managers}/record-array-manager.ts | 60 +- .../record-data-store-wrapper.ts | 90 +- .../managers/record-notification-manager.ts | 96 ++ .../addon/-private/model/internal-model.ts | 600 ----------- .../-private/{ => network}/fetch-manager.ts | 34 +- .../addon/-private/{ => network}/finders.js | 16 +- .../-private/{ => network}/request-cache.ts | 0 .../{ => network}/snapshot-record-array.ts | 6 +- .../addon/-private/{ => network}/snapshot.ts | 55 +- .../-private/{ => proxies}/promise-proxies.ts | 8 +- .../{ => proxies}/promise-proxy-base.d.ts | 0 .../{ => proxies}/promise-proxy-base.js | 0 .../adapter-populated-record-array.ts | 20 +- .../-private/record-arrays/record-array.ts | 30 +- .../-private/record-notification-manager.ts | 73 -- .../{core-store.ts => store-service.ts} | 313 +++--- .../addon/-private/{ => utils}/coerce-id.ts | 2 +- .../addon/-private/{ => utils}/common.js | 3 +- .../-private/utils/construct-resource.ts | 4 +- .../{ => utils}/identifer-debug-consts.ts | 0 .../{ => utils}/normalize-model-name.ts | 0 .../addon/-private/utils/promise-record.ts | 6 +- .../{ => utils}/serializer-response.ts | 4 +- .../addon/-private/{ => utils}/weak-cache.ts | 0 .../app/services/store.js | 4 +- .../tests/integration/belongs-to-test.js | 12 +- .../tests/integration/has-many-test.js | 18 +- .../app/models/person.js | 2 +- .../app/templates/index.hbs | 9 +- .../config/ember-try.js | 2 +- .../ember-cli-build.js | 4 +- .../app/services/store.js | 4 +- .../tests/integration/relationships-test.js | 6 +- .../addon-test-support/async.js | 42 - .../addon-test-support/legacy.js | 28 - .../unpublished-test-infra/ember-cli-build.js | 4 +- tsconfig.json | 30 +- 181 files changed, 5610 insertions(+), 6394 deletions(-) delete mode 100644 packages/-ember-data/tests/unit/private-test.js create mode 100644 packages/private-build-infra/addon/debugging.ts create mode 100644 packages/private-build-infra/src/debugging.js delete mode 100644 packages/serializer/addon/-private/utils.js rename packages/store/addon/-private/{ => caches}/identifier-cache.ts (86%) create mode 100644 packages/store/addon/-private/caches/instance-cache.ts rename packages/store/addon/-private/{ => caches}/record-data-for.ts (94%) delete mode 100644 packages/store/addon/-private/identity-map.ts delete mode 100644 packages/store/addon/-private/instance-cache.ts delete mode 100644 packages/store/addon/-private/internal-model-factory.ts delete mode 100644 packages/store/addon/-private/internal-model-map.ts rename packages/store/addon/-private/{model => legacy-model-support}/record-reference.ts (96%) rename packages/store/addon/-private/{ => legacy-model-support}/schema-definition-service.ts (94%) rename packages/store/addon/-private/{model => legacy-model-support}/shim-model-class.ts (97%) rename packages/store/addon/-private/{ => managers}/record-array-manager.ts (86%) rename packages/store/addon/-private/{ => managers}/record-data-store-wrapper.ts (75%) create mode 100644 packages/store/addon/-private/managers/record-notification-manager.ts delete mode 100644 packages/store/addon/-private/model/internal-model.ts rename packages/store/addon/-private/{ => network}/fetch-manager.ts (95%) rename packages/store/addon/-private/{ => network}/finders.js (89%) rename packages/store/addon/-private/{ => network}/request-cache.ts (100%) rename packages/store/addon/-private/{ => network}/snapshot-record-array.ts (96%) rename packages/store/addon/-private/{ => network}/snapshot.ts (91%) rename packages/store/addon/-private/{ => proxies}/promise-proxies.ts (96%) rename packages/store/addon/-private/{ => proxies}/promise-proxy-base.d.ts (100%) rename packages/store/addon/-private/{ => proxies}/promise-proxy-base.js (100%) delete mode 100644 packages/store/addon/-private/record-notification-manager.ts rename packages/store/addon/-private/{core-store.ts => store-service.ts} (90%) rename packages/store/addon/-private/{ => utils}/coerce-id.ts (98%) rename packages/store/addon/-private/{ => utils}/common.js (92%) rename packages/store/addon/-private/{ => utils}/identifer-debug-consts.ts (100%) rename packages/store/addon/-private/{ => utils}/normalize-model-name.ts (100%) rename packages/store/addon/-private/{ => utils}/serializer-response.ts (95%) rename packages/store/addon/-private/{ => utils}/weak-cache.ts (100%) delete mode 100644 packages/unpublished-test-infra/addon-test-support/async.js delete mode 100644 packages/unpublished-test-infra/addon-test-support/legacy.js diff --git a/.eslintrc.js b/.eslintrc.js index 4d7f8046bc6..c14145eee93 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -194,26 +194,21 @@ module.exports = { 'ember-data-types/q/fetch-manager.ts', 'ember-data-types/q/ember-data-json-api.ts', 'ember-data-types/q/ds-model.ts', - 'packages/store/addon/-private/record-data-store-wrapper.ts', - 'packages/store/addon/-private/internal-model-factory.ts', - 'packages/store/addon/-private/snapshot.ts', - 'packages/store/addon/-private/snapshot-record-array.ts', - 'packages/store/addon/-private/schema-definition-service.ts', - 'packages/store/addon/-private/request-cache.ts', - 'packages/store/addon/-private/references/reference.ts', - 'packages/store/addon/-private/references/record.ts', - 'packages/store/addon/-private/record-notification-manager.ts', - 'packages/store/addon/-private/record-data-for.ts', - 'packages/store/addon/-private/normalize-model-name.ts', - 'packages/store/addon/-private/model/shim-model-class.ts', - 'packages/store/addon/-private/model/internal-model.ts', - 'packages/store/addon/-private/internal-model-map.ts', - 'packages/store/addon/-private/identity-map.ts', - 'packages/store/addon/-private/fetch-manager.ts', - 'packages/store/addon/-private/core-store.ts', - 'packages/store/addon/-private/coerce-id.ts', + 'packages/store/addon/-private/managers/record-data-store-wrapper.ts', + 'packages/store/addon/-private/network/snapshot.ts', + 'packages/store/addon/-private/network/snapshot-record-array.ts', + 'packages/store/addon/-private/legacy-model-support/schema-definition-service.ts', + 'packages/store/addon/-private/network/request-cache.ts', + 'packages/store/addon/-private/legacy-model-support/record-reference.ts', + 'packages/store/addon/-private/managers/record-notification-manager.ts', + 'packages/store/addon/-private/caches/record-data-for.ts', + 'packages/store/addon/-private/utils/normalize-model-name.ts', + 'packages/store/addon/-private/legacy-model-support/shim-model-class.ts', + 'packages/store/addon/-private/network/fetch-manager.ts', + 'packages/store/addon/-private/store-service.ts', + 'packages/store/addon/-private/utils/coerce-id.ts', 'packages/store/addon/-private/index.ts', - 'packages/store/addon/-private/identifier-cache.ts', + 'packages/store/addon/-private/caches/identifier-cache.ts', 'packages/serializer/tests/dummy/app/routes/application/route.ts', 'packages/serializer/tests/dummy/app/router.ts', 'packages/serializer/tests/dummy/app/resolver.ts', diff --git a/CHANGELOG.md b/CHANGELOG.md index b2b5fe697cd..0968e63d5db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2445,10 +2445,10 @@ it should be pretty straight forward to update current code to the public Snapshot API: ```js -post.get('id') => postSnapshot.id -post.get('title') => postSnapshot.attr('title') -post.get('author') => postSnapshot.belongsTo('author') -post.get('comments') => postSnapshot.hasMany('comments') +post.id => postSnapshot.id +post.title => postSnapshot.attr('title') +post.author => postSnapshot.belongsTo('author') +post.comments => postSnapshot.hasMany('comments') post.constructor => postSnapshot.type; post.constructor.typeKey => postSnapshot.typeKey ``` @@ -2525,7 +2525,7 @@ To access attributes you should now use the `attr` function. ```js // Ember Data 1.0.0-beta.14.1 -post.get('title'); +post.title; // Ember Data 1.0.0-beta.15 postSnapshot.attr('title'); ``` @@ -2534,7 +2534,7 @@ To access a belongsTo relationship you should use `.belongsTo()` method. ```js // Ember Data 1.0.0-beta.14.1 -post.get('author'); +post.author; // Ember Data 1.0.0-beta.15 postSnapshot.belongsTo('author'); ``` @@ -2543,7 +2543,7 @@ To access a hasMany relationship you should use `.hasMany()` method. ```js // Ember Data 1.0.0-beta.14.1 -post.get('comments'); +post.comments; // Ember Data 1.0.0-beta.15 postSnapshot.hasMany('comments'); ``` @@ -2619,13 +2619,13 @@ var post = store.push('post', { author: 'Tomster', }); -post.get('title'); // => 'Ember.js is fantastic' -post.get('author'); // => 'Tomster' +post.title; // => 'Ember.js is fantastic' +post.author; // => 'Tomster' store.push('post', { id: 1, author: 'Tom Dale' }); -post.get('title'); // => 'Ember.js is fantastic' -post.get('author'); // => 'Tom Dale' +post.title; // => 'Ember.js is fantastic' +post.author; // => 'Tom Dale' ``` This also mean that properties missing in the payload will no longer be reset, @@ -2698,10 +2698,10 @@ underlying array you will now need to use the `.toArray()` method. ```javascript // Ember Data 1.0.0-beta.12 -record.get('myHasManyRelationship').get('content').map(...); +record.myHasManyRelationship.content.map(...); // Ember Data 1.0.0-beta.14 -record.get('myHasManyRelationship').toArray().map(...); +record.myHasManyRelationship.toArray().map(...); ``` Additionally if you were using the `RecordArray`'s `.addRecord()` and @@ -2736,7 +2736,7 @@ Additionally if you were using the `RecordArray`'s `.addRecord()` and - [Feature thrownError] tag errorThrown from jQuery onto the jqXHR like ic-ajax does. - Cache relationships meta in production - Deprecate store.update() -- hasMany relationships are no longer `RecordArray`, but `ManyArray`. To access the underlying array use `relationship.toArray()` instead of `relationship.get('content')`. +- hasMany relationships are no longer `RecordArray`, but `ManyArray`. To access the underlying array use `relationship.toArray()` instead of `relationship.content`. ### Ember Data 1.0.0-beta.12 (November 25, 2014) @@ -2765,7 +2765,7 @@ Ember Data's build. You should upgrade to Ember 1.8 as soon as you can. ##### Observing `data` For Changes Has Been Removed -Although `model.get('data')` has been private in Ember Data for a long time, we +Although `model.data` has been private in Ember Data for a long time, we have noticed users may subscribe to changes on `data` for any change to the model's attributes. This means that the following code: diff --git a/ember-data-types/q/ds-model.ts b/ember-data-types/q/ds-model.ts index 1915075a33f..711e7fdcb49 100644 --- a/ember-data-types/q/ds-model.ts +++ b/ember-data-types/q/ds-model.ts @@ -2,7 +2,6 @@ import type EmberObject from '@ember/object'; import type { Errors } from '@ember-data/model/-private'; import type Store from '@ember-data/store'; -import type InternalModel from '@ember-data/store/-private/model/internal-model'; import type { JsonApiValidationError } from './record-data-json-api'; import type { AttributeSchema, RelationshipSchema, RelationshipsSchema } from './record-data-schemas'; @@ -12,7 +11,6 @@ export interface DSModel extends EmberObject { constructor: DSModelSchema; store: Store; errors: Errors; - _internalModel: InternalModel; toString(): string; save(): Promise; eachRelationship(callback: (this: T, key: string, meta: RelationshipSchema) => void, binding?: T): void; diff --git a/ember-data-types/q/identifier.ts b/ember-data-types/q/identifier.ts index ca6b65efdfe..e16ab2c7d50 100644 --- a/ember-data-types/q/identifier.ts +++ b/ember-data-types/q/identifier.ts @@ -2,7 +2,10 @@ @module @ember-data/store */ -import { DEBUG_CLIENT_ORIGINATED, DEBUG_IDENTIFIER_BUCKET } from '@ember-data/store/-private/identifer-debug-consts'; +import { + DEBUG_CLIENT_ORIGINATED, + DEBUG_IDENTIFIER_BUCKET, +} from '@ember-data/store/-private/utils/identifer-debug-consts'; import type { ExistingResourceObject, ResourceIdentifierObject } from './ember-data-json-api'; diff --git a/ember-data-types/q/minimum-adapter-interface.ts b/ember-data-types/q/minimum-adapter-interface.ts index 81f52bc66e2..67e370ef0f6 100644 --- a/ember-data-types/q/minimum-adapter-interface.ts +++ b/ember-data-types/q/minimum-adapter-interface.ts @@ -1,7 +1,7 @@ import type Store from '@ember-data/store'; +import type Snapshot from '@ember-data/store/-private/network/snapshot'; +import type SnapshotRecordArray from '@ember-data/store/-private/network/snapshot-record-array'; import type AdapterPopulatedRecordArray from '@ember-data/store/-private/record-arrays/adapter-populated-record-array'; -import type Snapshot from '@ember-data/store/-private/snapshot'; -import type SnapshotRecordArray from '@ember-data/store/-private/snapshot-record-array'; import type { ModelSchema } from './ds-model'; import type { RelationshipSchema } from './record-data-schemas'; diff --git a/ember-data-types/q/minimum-serializer-interface.ts b/ember-data-types/q/minimum-serializer-interface.ts index 81ac89e2f14..022375e1800 100644 --- a/ember-data-types/q/minimum-serializer-interface.ts +++ b/ember-data-types/q/minimum-serializer-interface.ts @@ -1,7 +1,7 @@ import type { Object as JSONObject } from 'json-typescript'; import type Store from '@ember-data/store'; -import type Snapshot from '@ember-data/store/-private/snapshot'; +import type Snapshot from '@ember-data/store/-private/network/snapshot'; import type { ModelSchema } from './ds-model'; import type { JsonApiDocument, SingleResourceDocument } from './ember-data-json-api'; diff --git a/ember-data-types/q/record-data-record-wrapper.ts b/ember-data-types/q/record-data-record-wrapper.ts index 8b0241ea189..079898863ac 100644 --- a/ember-data-types/q/record-data-record-wrapper.ts +++ b/ember-data-types/q/record-data-record-wrapper.ts @@ -7,7 +7,7 @@ import type { JsonApiValidationError } from './record-data-json-api'; @module @ember-data/store */ -export interface RecordDataRecordWrapper { +export interface RecordDataWrapper { rollbackAttributes(): string[]; changedAttributes(): ChangedAttributesHash; hasChangedAttributes(): boolean; @@ -16,17 +16,15 @@ export interface RecordDataRecordWrapper { getAttr(key: string): any; getHasMany(key: string): CollectionResourceRelationship; - addToHasMany(key: string, recordDatas: RecordDataRecordWrapper[], idx?: number): void; - removeFromHasMany(key: string, recordDatas: RecordDataRecordWrapper[]): void; - setDirtyHasMany(key: string, recordDatas: RecordDataRecordWrapper[]): void; + addToHasMany(key: string, recordDatas: RecordDataWrapper[], idx?: number): void; + removeFromHasMany(key: string, recordDatas: RecordDataWrapper[]): void; + setDirtyHasMany(key: string, recordDatas: RecordDataWrapper[]): void; getBelongsTo(key: string): SingleResourceRelationship; - setDirtyBelongsTo(name: string, recordData: RecordDataRecordWrapper | null): void; + setDirtyBelongsTo(name: string, recordData: RecordDataWrapper | null): void; // ----- unspecced - isAttrDirty(key: string): boolean; - removeFromInverseRelationships(): void; hasAttr(key: string): boolean; // new diff --git a/ember-data-types/q/record-data-schemas.ts b/ember-data-types/q/record-data-schemas.ts index e7f8095e6ca..d9dcae701bf 100644 --- a/ember-data-types/q/record-data-schemas.ts +++ b/ember-data-types/q/record-data-schemas.ts @@ -14,7 +14,7 @@ export interface RelationshipSchema { key: string; // TODO @runspired remove our uses // TODO @runspired should RFC be updated to make this optional? // TODO @runspired sohuld RFC be update to enforce async and inverse are set? else internals need to know - // that meta came from DS.Model vs not from DS.Model as defaults should switch. + // that meta came from @ember-data/model vs not from @ember-data/model as defaults should switch. options: { async?: boolean; // controls inverse unloading "client side delete semantics" so we should replace that with a real flag polymorphic?: boolean; diff --git a/ember-data-types/q/record-data.ts b/ember-data-types/q/record-data.ts index 097a24ab7db..410e0391d32 100644 --- a/ember-data-types/q/record-data.ts +++ b/ember-data-types/q/record-data.ts @@ -45,8 +45,6 @@ export interface RecordData { didCommit(data: JsonApiResource | null): void; // ----- unspecced - isAttrDirty(key: string): boolean; - removeFromInverseRelationships(): void; hasAttr(key: string): boolean; isRecordInUse(): boolean; diff --git a/ember-data-types/q/relationship-record-data.ts b/ember-data-types/q/relationship-record-data.ts index e5188b2d155..d8a46158cd5 100644 --- a/ember-data-types/q/relationship-record-data.ts +++ b/ember-data-types/q/relationship-record-data.ts @@ -16,7 +16,7 @@ export interface RelationshipRecordData extends RecordData { storeWrapper: RecordDataStoreWrapper; identifier: StableRecordIdentifier; id: string | null; - clientId: string | null; + lid: string | null; isEmpty(): boolean; getResourceIdentifier(): RecordIdentifier; getBelongsTo(key: string): DefaultSingleResourceRelationship; diff --git a/packages/-ember-data/addon/-private/index.ts b/packages/-ember-data/addon/-private/index.ts index 0c32ba10e4c..f36e0474224 100644 --- a/packages/-ember-data/addon/-private/index.ts +++ b/packages/-ember-data/addon/-private/index.ts @@ -4,11 +4,9 @@ export { default as DS } from './core'; export { Errors } from '@ember-data/model/-private'; export { Snapshot } from '@ember-data/store/-private'; -// `ember-data-model-fragments` relies on `InternalModel` // `ember-data-model-fragments' and `ember-data-change-tracker` rely on `normalizeModelName` export { AdapterPopulatedRecordArray, - InternalModel, PromiseArray, PromiseObject, RecordArray, diff --git a/packages/-ember-data/addon/index.js b/packages/-ember-data/addon/index.js index e8172e88f58..b8085ebe3d1 100644 --- a/packages/-ember-data/addon/index.js +++ b/packages/-ember-data/addon/index.js @@ -30,7 +30,6 @@ import { AdapterPopulatedRecordArray, DS, Errors, - InternalModel, ManyArray, PromiseArray, PromiseManyArray, @@ -52,7 +51,6 @@ DS.Model = Model; DS.attr = attr; DS.Errors = Errors; -DS.InternalModel = InternalModel; DS.Snapshot = Snapshot; DS.Adapter = Adapter; diff --git a/packages/-ember-data/config/ember-try.js b/packages/-ember-data/config/ember-try.js index 7afb04cddac..87c8c208c0a 100644 --- a/packages/-ember-data/config/ember-try.js +++ b/packages/-ember-data/config/ember-try.js @@ -14,7 +14,7 @@ module.exports = function () { }, npm: { devDependencies: { - 'ember-fetch': '*', + 'ember-fetch': '^8.1.1', '@ember/jquery': null, }, }, @@ -23,7 +23,7 @@ module.exports = function () { name: 'with-ember-fetch-and-jquery', npm: { devDependencies: { - 'ember-fetch': '*', + 'ember-fetch': '^8.1.1', '@ember/jquery': '^2.0.0', }, }, diff --git a/packages/-ember-data/ember-cli-build.js b/packages/-ember-data/ember-cli-build.js index 7433a4ef5d6..2d78ece93ed 100644 --- a/packages/-ember-data/ember-cli-build.js +++ b/packages/-ember-data/ember-cli-build.js @@ -37,14 +37,22 @@ module.exports = function (defaults) { terserSettings.enabled = false; } - let app = new EmberAddon(defaults, { - emberData: { - compatWith, + let config = { + compatWith, + debug: { + LOG_NOTIFICATIONS: process.env.DEBUG_DATA ? true : false, + LOG_REQUEST_STATUS: process.env.DEBUG_DATA ? true : false, + LOG_IDENTIFIERS: process.env.DEBUG_DATA ? true : false, + LOG_GRAPH: process.env.DEBUG_DATA ? true : false, + LOG_INSTANCE_CACHE: process.env.DEBUG_DATA ? true : false, }, + }; + let app = new EmberAddon(defaults, { + emberData: config, babel: { // this ensures that the same build-time code stripping that is done // for library packages is also done for our tests and dummy app - plugins: [...require('@ember-data/private-build-infra/src/debug-macros')(null, isProd, compatWith)], + plugins: [...require('@ember-data/private-build-infra/src/debug-macros')(null, isProd, config)], }, 'ember-cli-babel': { throwUnlessParallelizable: true, diff --git a/packages/-ember-data/node-tests/fixtures/expected.js b/packages/-ember-data/node-tests/fixtures/expected.js index 688d966e8b9..0ea39b24a2b 100644 --- a/packages/-ember-data/node-tests/fixtures/expected.js +++ b/packages/-ember-data/node-tests/fixtures/expected.js @@ -39,7 +39,6 @@ module.exports = { '(private) @ember-data/debug InspectorDataAdapter#watchTypeIfUnseen', '(private) @ember-data/model Model#_createSnapshot', '(private) @ember-data/model Model#_debugInfo', - '(private) @ember-data/model Model#_internalModel', '(private) @ember-data/model Model#_notifyProperties', '(private) @ember-data/model Model#create', '(private) @ember-data/model Model#currentState', @@ -74,8 +73,6 @@ module.exports = { '(private) @ember-data/store IdentifierCache#_getRecordIdentifier', '(private) @ember-data/store IdentifierCache#_mergeRecordIdentifiers', '(private) @ember-data/store IdentifierCache#peekRecordIdentifier', - '(private) @ember-data/store InternalModelFactory#lookup', - '(private) @ember-data/store InternalModelFactory#peek', '(private) @ember-data/store ManyArray#isPolymorphic', '(private) @ember-data/store ManyArray#relationship', '(private) @ember-data/store RecordArray#_createSnapshot', @@ -90,7 +87,6 @@ module.exports = { '(private) @ember-data/store Store#_push', '(private) @ember-data/store Store#find', '(private) @ember-data/store Store#init', - '(private) @ember-data/store Store#recordWasInvalid', '(public) @ember-data/adapter Adapter#coalesceFindRequests', '(public) @ember-data/adapter Adapter#createRecord', '(public) @ember-data/adapter Adapter#deleteRecord', diff --git a/packages/-ember-data/tests/acceptance/relationships/belongs-to-test.js b/packages/-ember-data/tests/acceptance/relationships/belongs-to-test.js index e4d0a7b37ea..8ec1b0b7ace 100644 --- a/packages/-ember-data/tests/acceptance/relationships/belongs-to-test.js +++ b/packages/-ember-data/tests/acceptance/relationships/belongs-to-test.js @@ -12,7 +12,7 @@ import JSONAPIAdapter from '@ember-data/adapter/json-api'; import Model, { attr, belongsTo, hasMany } from '@ember-data/model'; import { LEGACY_SUPPORT } from '@ember-data/model/-private'; import JSONAPISerializer from '@ember-data/serializer/json-api'; -import Store from '@ember-data/store'; +import Store, { recordIdentifierFor } from '@ember-data/store'; import { implicitRelationshipsFor } from '../../helpers/accessors'; @@ -263,7 +263,7 @@ module('async belongs-to rendering tests', function (hooks) { }); const storeWrapper = store._instanceCache._storeWrapper; - const identifier = pete._internalModel.identifier; + const identifier = recordIdentifierFor(pete); const implicitRelationships = implicitRelationshipsFor(storeWrapper, identifier); const implicitKeys = Object.keys(implicitRelationships); const petOwnerImplicit = implicitRelationships[implicitKeys[0]]; @@ -290,11 +290,11 @@ module('async belongs-to rendering tests', function (hooks) { assert.strictEqual(petOwnerImplicit.canonicalMembers.size, 2); - let petOwner = await goofy.get('petOwner'); - assert.strictEqual(petOwner.get('name'), 'Pete', 'We have the expected owner for goofy'); + let petOwner = await goofy.petOwner; + assert.strictEqual(petOwner.name, 'Pete', 'We have the expected owner for goofy'); - petOwner = await tweety.get('petOwner'); - assert.strictEqual(petOwner.get('name'), 'Pete', 'We have the expected owner for tweety'); + petOwner = await tweety.petOwner; + assert.strictEqual(petOwner.name, 'Pete', 'We have the expected owner for tweety'); await goofy.destroyRecord(); assert.ok(goofy.isDeleted, 'goofy is deleted after calling destroyRecord'); @@ -317,8 +317,8 @@ module('async belongs-to rendering tests', function (hooks) { }, }); - petOwner = await jerry.get('petOwner'); - assert.strictEqual(petOwner.get('name'), 'Pete'); + petOwner = await jerry.petOwner; + assert.strictEqual(petOwner.name, 'Pete'); assert.strictEqual(petOwnerImplicit.canonicalMembers.size, 1); @@ -363,7 +363,7 @@ module('async belongs-to rendering tests', function (hooks) { let shen = store.peekRecord('pet', '1'); let pirate = store.peekRecord('pet', '2'); - let bestDog = await chris.get('bestDog'); + let bestDog = await chris.bestDog; this.set('chris', chris); @@ -373,38 +373,38 @@ module('async belongs-to rendering tests', function (hooks) { await settled(); assert.strictEqual(this.element.textContent.trim(), '', 'initially there is no name for bestDog'); - assert.strictEqual(shen.get('bestHuman'), null, 'precond - Shen has no best human'); - assert.strictEqual(pirate.get('bestHuman'), null, 'precond - pirate has no best human'); + assert.strictEqual(shen.bestHuman, null, 'precond - Shen has no best human'); + assert.strictEqual(pirate.bestHuman, null, 'precond - pirate has no best human'); assert.strictEqual(bestDog, null, 'precond - Chris has no best dog'); // locally update chris.set('bestDog', shen); - bestDog = await chris.get('bestDog'); + bestDog = await chris.bestDog; await settled(); assert.strictEqual(this.element.textContent.trim(), 'Shen'); - assert.strictEqual(shen.get('bestHuman'), chris, "scene 1 - Chris is Shen's best human"); - assert.strictEqual(pirate.get('bestHuman'), null, 'scene 1 - pirate has no best human'); + assert.strictEqual(shen.bestHuman, chris, "scene 1 - Chris is Shen's best human"); + assert.strictEqual(pirate.bestHuman, null, 'scene 1 - pirate has no best human'); assert.strictEqual(bestDog, shen, "scene 1 - Shen is Chris's best dog"); // locally update to a different value chris.set('bestDog', pirate); - bestDog = await chris.get('bestDog'); + bestDog = await chris.bestDog; await settled(); assert.strictEqual(this.element.textContent.trim(), 'Pirate'); - assert.strictEqual(shen.get('bestHuman'), null, "scene 2 - Chris is no longer Shen's best human"); - assert.strictEqual(pirate.get('bestHuman'), chris, 'scene 2 - pirate now has Chris as best human'); + assert.strictEqual(shen.bestHuman, null, "scene 2 - Chris is no longer Shen's best human"); + assert.strictEqual(pirate.bestHuman, chris, 'scene 2 - pirate now has Chris as best human'); assert.strictEqual(bestDog, pirate, "scene 2 - Pirate is now Chris's best dog"); // locally clear the relationship chris.set('bestDog', null); - bestDog = await chris.get('bestDog'); + bestDog = await chris.bestDog; await settled(); assert.strictEqual(this.element.textContent.trim(), ''); - assert.strictEqual(shen.get('bestHuman'), null, "scene 3 - Chris remains no longer Shen's best human"); - assert.strictEqual(pirate.get('bestHuman'), null, 'scene 3 - pirate no longer has Chris as best human'); + assert.strictEqual(shen.bestHuman, null, "scene 3 - Chris remains no longer Shen's best human"); + assert.strictEqual(pirate.bestHuman, null, 'scene 3 - pirate no longer has Chris as best human'); assert.strictEqual(bestDog, null, 'scene 3 - Chris has no best dog'); }); }); @@ -531,14 +531,14 @@ module('async belongs-to rendering tests', function (hooks) { assert.false(!!relationship.link, 'The relationship does not have a link'); try { - let result = await sedona.get('parent.content'); + let result = await sedona.parent.content; assert.strictEqual(result, null, 're-access is safe'); } catch (e) { assert.ok(false, `Accessing resulted in rejected promise error: ${e.message}`); } try { - await sedona.get('parent'); + await sedona.parent; assert.ok(false, 're-access should throw original rejection'); } catch (e) { assert.ok(true, `Accessing resulted in rejected promise error: ${e.message}`); @@ -558,7 +558,7 @@ module('async belongs-to rendering tests', function (hooks) { adapter.setupPayloads(assert, [new ServerError([], error)]); try { - await sedona.get('parent'); + await sedona.parent; assert.ok(false, `should have rejected`); } catch (e) { assert.strictEqual(e.message, error, `should have rejected with '${error}'`); @@ -571,7 +571,7 @@ module('async belongs-to rendering tests', function (hooks) { assert.strictEqual(this.element.textContent.trim(), '', 'we have no parent'); try { - await sedona.get('parent'); + await sedona.parent; assert.ok(false, `should have rejected`); } catch (e) { assert.strictEqual(e.message, error, `should have rejected with '${error}'`); diff --git a/packages/-ember-data/tests/acceptance/relationships/has-many-test.js b/packages/-ember-data/tests/acceptance/relationships/has-many-test.js index 0f29c76699f..b42884fcc05 100644 --- a/packages/-ember-data/tests/acceptance/relationships/has-many-test.js +++ b/packages/-ember-data/tests/acceptance/relationships/has-many-test.js @@ -18,6 +18,7 @@ import Model, { attr, belongsTo, hasMany } from '@ember-data/model'; import { LEGACY_SUPPORT } from '@ember-data/model/-private'; import JSONAPISerializer from '@ember-data/serializer/json-api'; import Store from '@ember-data/store'; +import { deprecatedTest } from '@ember-data/unpublished-test-infra/test-support/deprecated-test'; class Person extends Model { @attr() @@ -751,14 +752,212 @@ module('autotracking has-many', function (hooks) { assert.deepEqual(names, ['RGB', 'RGB'], 'rendered 2 children'); }); + deprecatedTest( + 'We can re-render hasMany w/PromiseManyArray.sortBy', + { id: 'ember-data:deprecate-promise-many-array-behaviors', until: '5.0', count: 6 }, + async function (assert) { + class ChildrenList extends Component { + @service store; + + get sortedChildren() { + return this.args.person.children.sortBy('name'); + } + + @action + createChild() { + const parent = this.args.person; + const name = 'RGB'; + this.store.createRecord('person', { name, parent }); + } + } + + let layout = hbs` + + +

{{this.sortedChildren.length}}

+
    + {{#each this.sortedChildren as |child|}} +
  • {{child.name}}
  • + {{/each}} +
+ `; + this.owner.register('component:children-list', ChildrenList); + this.owner.register('template:components/children-list', layout); + + store.createRecord('person', { id: '1', name: 'Doodad' }); + this.person = store.peekRecord('person', '1'); + + await render(hbs``); + + let names = findAll('li').map((e) => e.textContent); + + assert.deepEqual(names, [], 'rendered no children'); + + await click('#createChild'); + + names = findAll('li').map((e) => e.textContent); + assert.deepEqual(names, ['RGB'], 'rendered 1 child'); + + await click('#createChild'); + + names = findAll('li').map((e) => e.textContent); + assert.deepEqual(names, ['RGB', 'RGB'], 'rendered 2 children'); + } + ); + + deprecatedTest( + 'We can re-render hasMany with sort computed macro on PromiseManyArray', + { id: 'ember-data:deprecate-promise-many-array-behaviors', until: '5.0', count: 6 }, + async function (assert) { + class ChildrenList extends Component { + @service store; + + sortProperties = ['name']; + @sort('args.person.children', 'sortProperties') sortedChildren; + + @action + createChild() { + const parent = this.args.person; + const name = 'RGB'; + this.store.createRecord('person', { name, parent }); + } + } + + let layout = hbs` + + +

{{this.sortedChildren.length}}

+
    + {{#each this.sortedChildren as |child|}} +
  • {{child.name}}
  • + {{/each}} +
+ `; + this.owner.register('component:children-list', ChildrenList); + this.owner.register('template:components/children-list', layout); + + store.createRecord('person', { id: '1', name: 'Doodad' }); + this.person = store.peekRecord('person', '1'); + + await render(hbs``); + + let names = findAll('li').map((e) => e.textContent); + + assert.deepEqual(names, [], 'rendered no children'); + + await click('#createChild'); + + names = findAll('li').map((e) => e.textContent); + assert.deepEqual(names, ['RGB'], 'rendered 1 child'); + + await click('#createChild'); + + names = findAll('li').map((e) => e.textContent); + assert.deepEqual(names, ['RGB', 'RGB'], 'rendered 2 children'); + } + ); + + deprecatedTest( + 'We can re-render hasMany with PromiseManyArray.objectAt', + { id: 'ember-data:deprecate-promise-many-array-behaviors', until: '5.0', count: 6 }, + async function (assert) { + class ChildrenList extends Component { + @service store; + + get firstChild() { + return this.args.person.children.objectAt(0); + } + + @action + createChild() { + const parent = this.args.person; + const name = 'RGB'; + this.store.createRecord('person', { name, parent }); + } + } + + let layout = hbs` + + +

{{this.firstChild.name}}

+ `; + this.owner.register('component:children-list', ChildrenList); + this.owner.register('template:components/children-list', layout); + + store.createRecord('person', { id: '1', name: 'Doodad' }); + this.person = store.peekRecord('person', '1'); + + await render(hbs``); + + assert.dom('h2').hasText('', 'rendered no children'); + + await click('#createChild'); + + assert.dom('h2').hasText('RGB', 'renders first child'); + + await click('#createChild'); + + assert.dom('h2').hasText('RGB', 'renders first child'); + } + ); + + deprecatedTest( + 'We can re-render hasMany with PromiseManyArray.map', + { id: 'ember-data:deprecate-promise-many-array-behaviors', until: '5.0', count: 6 }, + async function (assert) { + class ChildrenList extends Component { + @service store; + + get children() { + return this.args.person.children.map((child) => child); + } + + @action + createChild() { + const parent = this.args.person; + const name = 'RGB'; + this.store.createRecord('person', { name, parent }); + } + } + + let layout = hbs` + + +

{{this.children.length}}

+
    + {{#each this.children as |child|}} +
  • {{child.name}}
  • + {{/each}} +
+ `; + this.owner.register('component:children-list', ChildrenList); + this.owner.register('template:components/children-list', layout); + + store.createRecord('person', { id: '1', name: 'Doodad' }); + this.person = store.peekRecord('person', '1'); + + await render(hbs``); + + let names = findAll('li').map((e) => e.textContent); + + assert.deepEqual(names, [], 'rendered no children'); + + await click('#createChild'); + + names = findAll('li').map((e) => e.textContent); + assert.deepEqual(names, ['RGB'], 'rendered 1 child'); + + await click('#createChild'); + + names = findAll('li').map((e) => e.textContent); + assert.deepEqual(names, ['RGB', 'RGB'], 'rendered 2 children'); + } + ); + test('We can re-render hasMany', async function (assert) { class ChildrenList extends Component { @service store; - get sortedChildren() { - return this.args.person.children.sortBy('name'); - } - @action createChild() { const parent = this.args.person; @@ -770,9 +969,9 @@ module('autotracking has-many', function (hooks) { let layout = hbs` -

{{this.sortedChildren.length}}

+

{{@person.children.length}}

    - {{#each this.sortedChildren as |child|}} + {{#each @person.children as |child|}}
  • {{child.name}}
  • {{/each}}
@@ -805,7 +1004,7 @@ module('autotracking has-many', function (hooks) { @service store; sortProperties = ['name']; - @sort('args.person.children', 'sortProperties') sortedChildren; + @sort('args.children', 'sortProperties') sortedChildren; @action createChild() { @@ -830,8 +1029,9 @@ module('autotracking has-many', function (hooks) { store.createRecord('person', { id: '1', name: 'Doodad' }); this.person = store.peekRecord('person', '1'); + this.children = await this.person.children; - await render(hbs``); + await render(hbs``); let names = findAll('li').map((e) => e.textContent); @@ -853,7 +1053,7 @@ module('autotracking has-many', function (hooks) { @service store; get firstChild() { - return this.args.person.children.objectAt(0); + return this.args.children.objectAt(0); } @action @@ -874,8 +1074,9 @@ module('autotracking has-many', function (hooks) { store.createRecord('person', { id: '1', name: 'Doodad' }); this.person = store.peekRecord('person', '1'); + this.children = await this.person.children; - await render(hbs``); + await render(hbs``); assert.dom('h2').hasText('', 'rendered no children'); @@ -893,7 +1094,7 @@ module('autotracking has-many', function (hooks) { @service store; get children() { - return this.args.person.children.map((child) => child); + return this.args.children.map((child) => child); } @action @@ -919,8 +1120,59 @@ module('autotracking has-many', function (hooks) { store.createRecord('person', { id: '1', name: 'Doodad' }); this.person = store.peekRecord('person', '1'); + this.children = await this.person.children; - await render(hbs``); + await render(hbs``); + + let names = findAll('li').map((e) => e.textContent); + + assert.deepEqual(names, [], 'rendered no children'); + + await click('#createChild'); + + names = findAll('li').map((e) => e.textContent); + assert.deepEqual(names, ['RGB'], 'rendered 1 child'); + + await click('#createChild'); + + names = findAll('li').map((e) => e.textContent); + assert.deepEqual(names, ['RGB', 'RGB'], 'rendered 2 children'); + }); + + test('We can re-render hasMany with toArray', async function (assert) { + class ChildrenList extends Component { + @service store; + + get children() { + return this.args.children.toArray(); + } + + @action + createChild() { + const parent = this.args.person; + const name = 'RGB'; + this.store.createRecord('person', { name, parent }); + } + } + + let layout = hbs` + + +

{{this.children.length}}

+
    + {{#each this.children as |child|}} +
  • {{child.name}}
  • + {{/each}} +
+ `; + this.owner.register('component:children-list', ChildrenList); + this.owner.register('template:components/children-list', layout); + + store.createRecord('person', { id: '1', name: 'Doodad' }); + this.person = store.peekRecord('person', '1'); + this.children = await this.person.children; + + await render(hbs``); let names = findAll('li').map((e) => e.textContent); diff --git a/packages/-ember-data/tests/integration/adapter/find-all-test.js b/packages/-ember-data/tests/integration/adapter/find-all-test.js index 12e6853b56e..45b7ec65e4f 100644 --- a/packages/-ember-data/tests/integration/adapter/find-all-test.js +++ b/packages/-ember-data/tests/integration/adapter/find-all-test.js @@ -153,9 +153,7 @@ module('integration/adapter/find-all - Finding All Records of a Type', function ); store.createRecord('person', { name: 'Carsten Nielsen' }); - await settled(); - - await settled(); + // await settled(); assert.strictEqual(allRecords.length, 1, "the record array's length is 1"); assert.strictEqual( diff --git a/packages/-ember-data/tests/integration/adapter/json-api-adapter-test.js b/packages/-ember-data/tests/integration/adapter/json-api-adapter-test.js index 87397505871..3fd16a2aa70 100644 --- a/packages/-ember-data/tests/integration/adapter/json-api-adapter-test.js +++ b/packages/-ember-data/tests/integration/adapter/json-api-adapter-test.js @@ -189,26 +189,32 @@ module('integration/adapter/json-api-adapter - JSONAPIAdapter', function (hooks) assert.strictEqual(posts.firstObject.title, 'Ember.js rocks', 'The title for the first post is correct'); assert.strictEqual(posts.lastObject.title, 'Tomster rules', 'The title for the second post is correct'); + const firstPostAuthor = await posts.firstObject.author; + const lastPostAuthor = await posts.lastObject.author; + assert.strictEqual( - posts.firstObject.author.get('firstName'), + firstPostAuthor.firstName, 'Yehuda', 'The author for the first post is loaded and has the correct first name' ); assert.strictEqual( - posts.lastObject.author.get('lastName'), + lastPostAuthor.lastName, 'Katz', 'The author for the last post is loaded and has the correct last name' ); - assert.strictEqual(posts.firstObject.comments.length, 0, 'First post doesnt have comments'); + const firstComments = await posts.firstObject.comments; + const lastComments = await posts.lastObject.comments; + + assert.strictEqual(firstComments.length, 0, 'First post doesnt have comments'); assert.strictEqual( - posts.lastObject.comments.firstObject.text, + lastComments.firstObject.text, 'This is the first comment', 'Loads first comment for second post' ); assert.strictEqual( - posts.lastObject.comments.lastObject.text, + lastComments.lastObject.text, 'This is the second comment', 'Loads second comment for second post' ); diff --git a/packages/-ember-data/tests/integration/adapter/rest-adapter-test.js b/packages/-ember-data/tests/integration/adapter/rest-adapter-test.js index 1dafb1860ae..893df575366 100644 --- a/packages/-ember-data/tests/integration/adapter/rest-adapter-test.js +++ b/packages/-ember-data/tests/integration/adapter/rest-adapter-test.js @@ -5,13 +5,22 @@ import Pretender from 'pretender'; import { module, test } from 'qunit'; import { reject, resolve } from 'rsvp'; -import DS from 'ember-data'; import { singularize } from 'ember-inflector'; import { setupTest } from 'ember-qunit'; +import AdapterError, { + AbortError, + ConflictError, + ForbiddenError, + NotFoundError, + ServerError, + UnauthorizedError, +} from '@ember-data/adapter/error'; import RESTAdapter from '@ember-data/adapter/rest'; import Model, { attr, belongsTo, hasMany } from '@ember-data/model'; import RESTSerializer from '@ember-data/serializer/rest'; +import { recordIdentifierFor } from '@ember-data/store'; +import { Snapshot } from '@ember-data/store/-private'; import deepCopy from '@ember-data/unpublished-test-infra/test-support/deep-copy'; import testInDebug from '@ember-data/unpublished-test-infra/test-support/test-in-debug'; @@ -147,8 +156,8 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { assert.strictEqual(passedVerb, 'PUT'); assert.deepEqual(passedHash.data, { post: { name: 'The Parley Letter' } }); - assert.false(post.get('hasDirtyAttributes'), "the post isn't dirty anymore"); - assert.strictEqual(post.get('name'), 'The Parley Letter', 'the post was updated'); + assert.false(post.hasDirtyAttributes, "the post isn't dirty anymore"); + assert.strictEqual(post.name, 'The Parley Letter', 'the post was updated'); }); test('updateRecord - passes the requestType to buildURL', async function (assert) { @@ -216,8 +225,8 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { assert.strictEqual(passedVerb, 'PUT'); assert.deepEqual(passedHash.data, { post: { name: 'The Parley Letter' } }); - assert.false(post.get('hasDirtyAttributes'), "the post isn't dirty anymore"); - assert.strictEqual(post.get('name'), 'Dat Parley Letter', 'the post was updated'); + assert.false(post.hasDirtyAttributes, "the post isn't dirty anymore"); + assert.strictEqual(post.name, 'Dat Parley Letter', 'the post was updated'); }); test('updateRecord - a payload with updates applies the updates (with legacy singular name)', async function (assert) { @@ -251,8 +260,8 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { assert.strictEqual(passedVerb, 'PUT'); assert.deepEqual(passedHash.data, { post: { name: 'The Parley Letter' } }); - assert.false(post.get('hasDirtyAttributes'), "the post isn't dirty anymore"); - assert.strictEqual(post.get('name'), 'Dat Parley Letter', 'the post was updated'); + assert.false(post.hasDirtyAttributes, "the post isn't dirty anymore"); + assert.strictEqual(post.name, 'Dat Parley Letter', 'the post was updated'); }); test('updateRecord - a payload with sideloaded updates pushes the updates', async function (assert) { @@ -278,12 +287,12 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { assert.strictEqual(passedVerb, 'POST'); assert.deepEqual(passedHash.data, { post: { name: 'The Parley Letter' } }); - assert.strictEqual(post.get('id'), '1', 'the post has the updated ID'); - assert.false(post.get('hasDirtyAttributes'), "the post isn't dirty anymore"); - assert.strictEqual(post.get('name'), 'Dat Parley Letter', 'the post was updated'); + assert.strictEqual(post.id, '1', 'the post has the updated ID'); + assert.false(post.hasDirtyAttributes, "the post isn't dirty anymore"); + assert.strictEqual(post.name, 'Dat Parley Letter', 'the post was updated'); let comment = store.peekRecord('comment', 1); - assert.strictEqual(comment.get('name'), 'FIRST', 'The comment was sideloaded'); + assert.strictEqual(comment.name, 'FIRST', 'The comment was sideloaded'); }); test('updateRecord - a payload with sideloaded updates pushes the updates', async function (assert) { @@ -320,11 +329,11 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { assert.strictEqual(passedVerb, 'PUT'); assert.deepEqual(passedHash.data, { post: { name: 'The Parley Letter' } }); - assert.false(post.get('hasDirtyAttributes'), "the post isn't dirty anymore"); - assert.strictEqual(post.get('name'), 'Dat Parley Letter', 'the post was updated'); + assert.false(post.hasDirtyAttributes, "the post isn't dirty anymore"); + assert.strictEqual(post.name, 'Dat Parley Letter', 'the post was updated'); let comment = store.peekRecord('comment', 1); - assert.strictEqual(comment.get('name'), 'FIRST', 'The comment was sideloaded'); + assert.strictEqual(comment.name, 'FIRST', 'The comment was sideloaded'); }); test("updateRecord - a serializer's primary key and attributes are consulted when building the payload", async function (assert) { @@ -341,13 +350,13 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { adapter.shouldBackgroundReloadRecord = () => false; this.owner.register( 'serializer:post', - DS.RESTSerializer.extend({ - primaryKey: '_id_', + class extends RESTSerializer { + primaryKey = '_id_'; - attrs: { + attrs = { name: '_name_', - }, - }) + }; + } ); store.push({ @@ -420,14 +429,14 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { await store.findRecord('comment', 2); let post = await store.findRecord('post', 1); let newComment = store.peekRecord('comment', 2); - let comments = post.get('comments'); + let comments = post.comments; // Replace the comment with a new one comments.popObject(); comments.pushObject(newComment); await post.save(); - assert.strictEqual(post.get('comments.length'), 1, 'the post has the correct number of comments'); + assert.strictEqual(post.comments.length, 1, 'the post has the correct number of comments'); assert.strictEqual(post.get('comments.firstObject.name'), 'Yes. Yes it is.', 'the post has the correct comment'); }); @@ -473,12 +482,12 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { }); let post = await store.peekRecord('post', 1); - assert.strictEqual(post.get('comments.length'), 1, 'the post has one comment'); + assert.strictEqual(post.comments.length, 1, 'the post has one comment'); post.set('name', 'Everyone uses Rails'); post = await post.save(); - assert.strictEqual(post.get('comments.length'), 0, 'the post has the no comments'); + assert.strictEqual(post.comments.length, 0, 'the post has the no comments'); }); test('updateRecord - hasMany relationships set locally will be removed with empty response', async function (assert) { @@ -522,11 +531,11 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { let comment = await store.peekRecord('comment', 1); let comments = post.comments; comments.pushObject(comment); - assert.strictEqual(post.get('comments.length'), 1, 'the post has one comment'); + assert.strictEqual(post.comments.length, 1, 'the post has one comment'); post = await post.save(); - assert.strictEqual(post.get('comments.length'), 0, 'the post has the no comments'); + assert.strictEqual(post.comments.length, 0, 'the post has the no comments'); }); test('deleteRecord - an empty payload is a basic success', async function (assert) { @@ -562,8 +571,8 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { assert.strictEqual(passedVerb, 'DELETE'); assert.strictEqual(passedHash, undefined); - assert.false(post.get('hasDirtyAttributes'), "the post isn't dirty anymore"); - assert.true(post.get('isDeleted'), 'the post is now deleted'); + assert.false(post.hasDirtyAttributes, "the post isn't dirty anymore"); + assert.true(post.isDeleted, 'the post is now deleted'); }); test('deleteRecord - passes the requestType to buildURL', async function (assert) { @@ -635,11 +644,11 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { assert.strictEqual(passedVerb, 'DELETE'); assert.strictEqual(passedHash, undefined); - assert.false(post.get('hasDirtyAttributes'), "the post isn't dirty anymore"); - assert.true(post.get('isDeleted'), 'the post is now deleted'); + assert.false(post.hasDirtyAttributes, "the post isn't dirty anymore"); + assert.true(post.isDeleted, 'the post is now deleted'); let comment = store.peekRecord('comment', 1); - assert.strictEqual(comment.get('name'), 'FIRST', 'The comment was sideloaded'); + assert.strictEqual(comment.name, 'FIRST', 'The comment was sideloaded'); }); test('deleteRecord - a payload with sidloaded updates pushes the updates when the original record is omitted', async function (assert) { @@ -675,11 +684,11 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { assert.strictEqual(passedVerb, 'DELETE'); assert.strictEqual(passedHash, undefined); - assert.false(post.get('hasDirtyAttributes'), "the original post isn't dirty anymore"); - assert.true(post.get('isDeleted'), 'the original post is now deleted'); + assert.false(post.hasDirtyAttributes, "the original post isn't dirty anymore"); + assert.true(post.isDeleted, 'the original post is now deleted'); let newPost = store.peekRecord('post', 2); - assert.strictEqual(newPost.get('name'), 'The Parley Letter', 'The new post was added to the store'); + assert.strictEqual(newPost.name, 'The Parley Letter', 'The new post was added to the store'); }); test('deleteRecord - deleting a newly created record should not throw an error', async function (assert) { @@ -695,18 +704,19 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { this.owner.register('model:comment', Comment); let post = store.createRecord('post'); - let internalModel = post._internalModel; + let identifier = recordIdentifierFor(post); post.deleteRecord(); await post.save(); - assert.true(post.get('isDeleted'), 'the post is now deleted'); - assert.false(post.get('isError'), 'the post is not an error'); + assert.true(post.isDeleted, 'the post is now deleted'); + assert.false(post.isError, 'the post is not an error'); assert.strictEqual(passedUrl, null, 'There is no ajax call to delete a record that has never been saved.'); assert.strictEqual(passedVerb, null, 'There is no ajax call to delete a record that has never been saved.'); assert.strictEqual(passedHash, null, 'There is no ajax call to delete a record that has never been saved.'); - assert.true(internalModel.isEmpty, 'the post is now deleted'); + const isLoaded = store._instanceCache.recordIsLoaded(identifier); + assert.false(isLoaded, 'the post is now deleted'); }); test('findAll - returning an array populates the array', async function (assert) { @@ -740,8 +750,8 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { assert.deepEqual(post2.getProperties('id', 'name'), { id: '2', name: 'The Parley Letter' }, 'Post 2 is loaded'); - assert.strictEqual(posts.get('length'), 2, 'The posts are in the array'); - assert.true(posts.get('isLoaded'), 'The RecordArray is loaded'); + assert.strictEqual(posts.length, 2, 'The posts are in the array'); + assert.true(posts.isLoaded, 'The RecordArray is loaded'); assert.deepEqual(posts.toArray(), [post1, post2], 'The correct records are in the array'); }); @@ -835,10 +845,10 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { this.owner.register( 'serializer:post', - DS.RESTSerializer.extend({ - primaryKey: '_ID_', - attrs: { name: '_NAME_' }, - }) + class extends RESTSerializer { + primaryKey = '_ID_'; + attrs = { name: '_NAME_' }; + } ); ajaxResponse({ @@ -855,8 +865,8 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { assert.deepEqual(post1.getProperties('id', 'name'), { id: '1', name: 'Rails is omakase' }, 'Post 1 is loaded'); assert.deepEqual(post2.getProperties('id', 'name'), { id: '2', name: 'The Parley Letter' }, 'Post 2 is loaded'); - assert.strictEqual(posts.get('length'), 2, 'The posts are in the array'); - assert.true(posts.get('isLoaded'), 'The RecordArray is loaded'); + assert.strictEqual(posts.length, 2, 'The posts are in the array'); + assert.true(posts.isLoaded, 'The RecordArray is loaded'); assert.deepEqual(posts.toArray(), [post1, post2], 'The correct records are in the array'); }); @@ -987,7 +997,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { }); let posts = await store.query('post', { page: 2 }); - assert.strictEqual(posts.get('meta.offset'), 5, 'Reponse metadata can be accessed with recordArray.meta'); + assert.strictEqual(posts.meta.offset, 5, 'Reponse metadata can be accessed with recordArray.meta'); }); test("query - each record array can have it's own meta object", async function (assert) { @@ -1008,15 +1018,15 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { }); let posts = await store.query('post', { page: 2 }); - assert.strictEqual(posts.get('meta.offset'), 5, 'Reponse metadata can be accessed with recordArray.meta'); + assert.strictEqual(posts.meta.offset, 5, 'Reponse metadata can be accessed with recordArray.meta'); ajaxResponse({ meta: { offset: 1 }, posts: [{ id: 1, name: 'Rails is very expensive sushi' }], }); let newPosts = await store.query('post', { page: 1 }); - assert.strictEqual(newPosts.get('meta.offset'), 1, 'new array has correct metadata'); - assert.strictEqual(posts.get('meta.offset'), 5, 'metadata on the old array hasnt been clobbered'); + assert.strictEqual(newPosts.meta.offset, 1, 'new array has correct metadata'); + assert.strictEqual(posts.meta.offset, 5, 'metadata on the old array hasnt been clobbered'); }); test('query - returning an array populates the array', async function (assert) { @@ -1049,8 +1059,8 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { assert.deepEqual(post1.getProperties('id', 'name'), { id: '1', name: 'Rails is omakase' }, 'Post 1 is loaded'); assert.deepEqual(post2.getProperties('id', 'name'), { id: '2', name: 'The Parley Letter' }, 'Post 2 is loaded'); - assert.strictEqual(posts.get('length'), 2, 'The posts are in the array'); - assert.true(posts.get('isLoaded'), 'The RecordArray is loaded'); + assert.strictEqual(posts.length, 2, 'The posts are in the array'); + assert.true(posts.isLoaded, 'The RecordArray is loaded'); assert.deepEqual(posts.toArray(), [post1, post2], 'The correct records are in the array'); }); @@ -1094,10 +1104,10 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { this.owner.register( 'serializer:post', - DS.RESTSerializer.extend({ - primaryKey: '_ID_', - attrs: { name: '_NAME_' }, - }) + class extends RESTSerializer { + primaryKey = '_ID_'; + attrs = { name: '_NAME_' }; + } ); ajaxResponse({ @@ -1115,8 +1125,8 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { assert.deepEqual(post2.getProperties('id', 'name'), { id: '2', name: 'The Parley Letter' }, 'Post 2 is loaded'); - assert.strictEqual(posts.get('length'), 2, 'The posts are in the array'); - assert.true(posts.get('isLoaded'), 'The RecordArray is loaded'); + assert.strictEqual(posts.length, 2, 'The posts are in the array'); + assert.true(posts.isLoaded, 'The RecordArray is loaded'); assert.deepEqual(posts.toArray(), [post1, post2], 'The correct records are in the array'); }); @@ -1178,7 +1188,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { }); let post = await store.queryRecord('post', { slug: 'ember-js-rocks' }); - assert.deepEqual(post.get('name'), 'Ember.js rocks'); + assert.deepEqual(post.name, 'Ember.js rocks'); }); test('queryRecord - returning sideloaded data loads the data', async function (assert) { @@ -1264,7 +1274,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { this.owner.register( 'serializer:post', - DS.RESTSerializer.extend({ + RESTSerializer.extend({ primaryKey: '_ID_', attrs: { name: '_NAME_' }, }) @@ -1325,7 +1335,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { ], }); - await post.get('comments'); + await post.comments; assert.strictEqual(passedUrl, '/comments'); assert.deepEqual(passedHash, { data: { ids: ['1', '2', '3'] } }); }); @@ -1377,7 +1387,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { ], }); - await post.get('comments'); + await post.comments; assert.strictEqual(passedUrl, '/findMany/comment'); }); @@ -1422,7 +1432,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { ], }); - await post.get('comments'); + await post.comments; assert.strictEqual(passedUrl, '/comments/3'); assert.deepEqual(passedHash.data, {}); }); @@ -1470,7 +1480,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { ], }); - let comments = await post.get('comments'); + let comments = await post.comments; let comment1 = store.peekRecord('comment', 1); let comment2 = store.peekRecord('comment', 2); let comment3 = store.peekRecord('comment', 3); @@ -1529,7 +1539,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { posts: [{ id: 2, name: 'The Parley Letter' }], }); - let comments = await post.get('comments'); + let comments = await post.comments; let comment1 = store.peekRecord('comment', 1); let comment2 = store.peekRecord('comment', 2); @@ -1561,7 +1571,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { adapter.shouldBackgroundReloadRecord = () => false; this.owner.register( 'serializer:post', - DS.RESTSerializer.extend({ + RESTSerializer.extend({ primaryKey: '_ID_', attrs: { name: '_NAME_' }, }) @@ -1569,7 +1579,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { this.owner.register( 'serializer:comment', - DS.RESTSerializer.extend({ + RESTSerializer.extend({ primaryKey: '_ID_', attrs: { name: '_NAME_' }, }) @@ -1606,7 +1616,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { ], }); - let comments = await post.get('comments'); + let comments = await post.comments; let comment1 = store.peekRecord('comment', 1); let comment2 = store.peekRecord('comment', 2); let comment3 = store.peekRecord('comment', 3); @@ -1661,7 +1671,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { ], }); - let comments = await post.get('comments'); + let comments = await post.comments; assert.strictEqual(passedUrl, '/posts/1/comments'); assert.strictEqual(passedVerb, 'GET'); assert.strictEqual(passedHash, undefined); @@ -1687,7 +1697,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { assert.expect(2); adapter.shouldBackgroundReloadRecord = () => false; adapter.buildURL = function (type, id, snapshot, requestType) { - assert.ok(snapshot instanceof DS.Snapshot); + assert.ok(snapshot instanceof Snapshot); assert.strictEqual(requestType, 'findHasMany'); }; @@ -1729,7 +1739,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { ], }); - await post.get('comments'); + await post.comments; }); test('findMany - returning sideloaded data loads the data (with JSONApi Links)', async function (assert) { @@ -1775,7 +1785,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { posts: [{ id: 2, name: 'The Parley Letter' }], }); - let comments = await post.get('comments'); + let comments = await post.comments; let comment1 = store.peekRecord('comment', 1); let comment2 = store.peekRecord('comment', 2); let comment3 = store.peekRecord('comment', 3); @@ -1790,7 +1800,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { adapter.shouldBackgroundReloadRecord = () => false; this.owner.register( 'serializer:post', - DS.RESTSerializer.extend({ + RESTSerializer.extend({ primaryKey: '_ID_', attrs: { name: '_NAME_' }, }) @@ -1798,7 +1808,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { this.owner.register( 'serializer:comment', - DS.RESTSerializer.extend({ + RESTSerializer.extend({ primaryKey: '_ID_', attrs: { name: '_NAME_' }, }) @@ -1842,7 +1852,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { { _ID_: 3, _NAME_: 'What is omakase?' }, ], }); - return post.get('comments'); + return post.comments; }) .then((comments) => { let comment1 = store.peekRecord('comment', 1); @@ -1875,7 +1885,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { assert.expect(2); adapter.shouldBackgroundReloadRecord = () => false; adapter.buildURL = function (type, id, snapshot, requestType) { - assert.ok(snapshot instanceof DS.Snapshot); + assert.ok(snapshot instanceof Snapshot); assert.strictEqual(requestType, 'findBelongsTo'); }; @@ -1898,7 +1908,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { let comment = await store.findRecord('comment', '1'); ajaxResponse({ post: { id: 1, name: 'Rails is omakase' } }); - await comment.get('post'); + await comment.post; }); testInDebug( @@ -1940,7 +1950,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { assert.expectWarning(async () => { try { - await post.get('comments'); + await post.comments; } catch (e) { assert.strictEqual( e.message, @@ -2000,7 +2010,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { let post = store.peekRecord('post', 2); - await post.get('comments'); + await post.comments; }); test('groupRecordsForFindMany groups records correctly when singular URLs are encoded as query params', async function (assert) { @@ -2052,7 +2062,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { let post = store.peekRecord('post', 2); - await post.get('comments'); + await post.comments; }); test('normalizeKey - to set up _ids and _id', async function (assert) { @@ -2076,7 +2086,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { this.owner.register( 'serializer:application', - DS.RESTSerializer.extend({ + RESTSerializer.extend({ keyForAttribute(attr) { return underscore(attr); }, @@ -2126,9 +2136,9 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { }); let post = await store.findRecord('post', 1); - assert.strictEqual(post.get('authorName'), '@d2h'); - assert.strictEqual(post.get('author.name'), 'D2H'); - assert.deepEqual(post.get('comments').mapBy('body'), ['Rails is unagi', 'What is omakase?']); + assert.strictEqual(post.authorName, '@d2h'); + assert.strictEqual(post.author.name, 'D2H'); + assert.deepEqual(post.comments.mapBy('body'), ['Rails is unagi', 'What is omakase?']); }); test('groupRecordsForFindMany splits up calls for large ids', async function (assert) { @@ -2184,7 +2194,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { return reject(); }; - post.get('comments'); + post.comments; }); test('groupRecordsForFindMany groups calls for small ids', async function (assert) { @@ -2238,7 +2248,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { return resolve({ comments: [{ id: a100 }, { id: b100 }] }); }; - await post.get('comments'); + await post.comments; }); test('calls adapter.handleResponse with the jqXHR and json', async function (assert) { @@ -2303,7 +2313,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { assert.deepEqual(status, 400); assert.deepEqual(json, responseText); assert.deepEqual(requestData, expectedRequestData); - return new DS.AdapterError('nope!'); + return new AdapterError('nope!'); }; try { @@ -2313,7 +2323,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { } }); - test('rejects promise if DS.AdapterError is returned from adapter.handleResponse', async function (assert) { + test('rejects promise if AdapterError is returned from adapter.handleResponse', async function (assert) { class Post extends Model { @attr name; @hasMany('comment', { async: true, inverse: 'post' }) comments; @@ -2337,14 +2347,14 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { adapter.handleResponse = function (status, headers, json) { assert.ok(true, 'handleResponse should be called'); - return new DS.AdapterError(json); + return new AdapterError(json); }; try { await store.findRecord('post', '1'); } catch (reason) { assert.ok(true, 'promise should be rejected'); - assert.ok(reason instanceof DS.AdapterError, 'reason should be an instance of DS.AdapterError'); + assert.ok(reason instanceof AdapterError, 'reason should be an instance of AdapterError'); } }); @@ -2428,7 +2438,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { try { await store.findRecord('post', '1'); } catch (err) { - assert.ok(err instanceof DS.AbortError, 'reason should be an instance of DS.AbortError'); + assert.ok(err instanceof AbortError, 'reason should be an instance of AbortError'); assert.strictEqual(err.errors.length, 1, 'AbortError includes errors with request/response details'); let expectedError = { title: 'Adapter Error', @@ -2475,10 +2485,13 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { } catch (err) { assert.strictEqual(err, errorThrown); assert.ok(err, 'promise rejected'); + if (err !== errorThrown) { + throw err; + } } }); - test('on error wraps the error string in an DS.AdapterError object', async function (assert) { + test('on error wraps the error string in an AdapterError object', async function (assert) { assert.expect(2); class Post extends Model { @attr name; @@ -2513,7 +2526,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { } }); - test('rejects promise with a specialized subclass of DS.AdapterError if ajax responds with http error codes', async function (assert) { + test('rejects promise with a specialized subclass of AdapterError if ajax responds with http error codes', async function (assert) { class Post extends Model { @attr name; @hasMany('comment', { async: true, inverse: 'post' }) comments; @@ -2532,7 +2545,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { await store.findRecord('post', '1'); } catch (reason) { assert.ok(true, 'promise should be rejected'); - assert.ok(reason instanceof DS.UnauthorizedError, 'reason should be an instance of DS.UnauthorizedError'); + assert.ok(reason instanceof UnauthorizedError, 'reason should be an instance of UnauthorizedError'); } ajaxError('error', 403); @@ -2541,7 +2554,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { await store.findRecord('post', '1'); } catch (reason) { assert.ok(true, 'promise should be rejected'); - assert.ok(reason instanceof DS.ForbiddenError, 'reason should be an instance of DS.ForbiddenError'); + assert.ok(reason instanceof ForbiddenError, 'reason should be an instance of ForbiddenError'); } ajaxError('error', 404); @@ -2550,7 +2563,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { await store.findRecord('post', '1'); } catch (reason) { assert.ok(true, 'promise should be rejected'); - assert.ok(reason instanceof DS.NotFoundError, 'reason should be an instance of DS.NotFoundError'); + assert.ok(reason instanceof NotFoundError, 'reason should be an instance of NotFoundError'); } ajaxError('error', 409); @@ -2559,7 +2572,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { await store.findRecord('post', '1'); } catch (reason) { assert.ok(true, 'promise should be rejected'); - assert.ok(reason instanceof DS.ConflictError, 'reason should be an instance of DS.ConflictError'); + assert.ok(reason instanceof ConflictError, 'reason should be an instance of ConflictError'); } ajaxError('error', 500); @@ -2568,7 +2581,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { await store.findRecord('post', '1'); } catch (reason) { assert.ok(true, 'promise should be rejected'); - assert.ok(reason instanceof DS.ServerError, 'reason should be an instance of DS.ServerError'); + assert.ok(reason instanceof ServerError, 'reason should be an instance of ServerError'); } }); @@ -2626,7 +2639,7 @@ module('integration/adapter/rest_adapter - REST Adapter', function (hooks) { } }); - test('findAll resolves with a collection of Models, not DS.InternalModels', async function (assert) { + test('findAll resolves with a collection of Models, not Identifiers', async function (assert) { class Post extends Model { @attr name; @hasMany('comment', { async: true, inverse: 'post' }) comments; diff --git a/packages/-ember-data/tests/integration/adapter/rest-adapter/create-record-test.js b/packages/-ember-data/tests/integration/adapter/rest-adapter/create-record-test.js index 71cb850e511..010142b8f97 100644 --- a/packages/-ember-data/tests/integration/adapter/rest-adapter/create-record-test.js +++ b/packages/-ember-data/tests/integration/adapter/rest-adapter/create-record-test.js @@ -51,8 +51,8 @@ module('integration/adapter/rest_adapter - REST Adapter - createRecord', functio assert.strictEqual(passedVerb, 'POST'); assert.deepEqual(passedHash.data, { post: { id: 'some-uuid', name: 'The Parley Letter' } }); - assert.false(post.get('hasDirtyAttributes'), "the post isn't dirty anymore"); - assert.strictEqual(post.get('name'), 'The Parley Letter', 'the post was updated'); + assert.false(post.hasDirtyAttributes, "the post isn't dirty anymore"); + assert.strictEqual(post.name, 'The Parley Letter', 'the post was updated'); }); test('createRecord - passes buildURL the requestType', async function (assert) { @@ -111,9 +111,9 @@ module('integration/adapter/rest_adapter - REST Adapter - createRecord', functio assert.strictEqual(passedVerb, 'POST'); assert.deepEqual(passedHash.data, { post: { name: 'The Parley Letter' } }); - assert.strictEqual(post.get('id'), '1', 'the post has the updated ID'); - assert.false(post.get('hasDirtyAttributes'), "the post isn't dirty anymore"); - assert.strictEqual(post.get('name'), 'Dat Parley Letter', 'the post was updated'); + assert.strictEqual(post.id, '1', 'the post has the updated ID'); + assert.false(post.hasDirtyAttributes, "the post isn't dirty anymore"); + assert.strictEqual(post.name, 'Dat Parley Letter', 'the post was updated'); }); test('createRecord - a payload with a new ID and data applies the updates (with legacy singular name)', async function (assert) { @@ -142,9 +142,9 @@ module('integration/adapter/rest_adapter - REST Adapter - createRecord', functio assert.strictEqual(passedVerb, 'POST'); assert.deepEqual(passedHash.data, { post: { name: 'The Parley Letter' } }); - assert.strictEqual(post.get('id'), '1', 'the post has the updated ID'); - assert.false(post.get('hasDirtyAttributes'), "the post isn't dirty anymore"); - assert.strictEqual(post.get('name'), 'Dat Parley Letter', 'the post was updated'); + assert.strictEqual(post.id, '1', 'the post has the updated ID'); + assert.false(post.hasDirtyAttributes, "the post isn't dirty anymore"); + assert.strictEqual(post.name, 'Dat Parley Letter', 'the post was updated'); }); test("createRecord - findMany doesn't overwrite owner", async function (assert) { @@ -185,15 +185,16 @@ module('integration/adapter/rest_adapter - REST Adapter - createRecord', functio const post = store.peekRecord('post', 1); const comment = store.createRecord('comment', { name: 'The Parley Letter' }); - post.get('comments').pushObject(comment); + const comments = await post.comments; + comments.pushObject(comment); - assert.strictEqual(comment.get('post'), post, 'the post has been set correctly'); + assert.strictEqual(comment.post, post, 'the post has been set correctly'); await comment.save(); - assert.false(comment.get('hasDirtyAttributes'), "the post isn't dirty anymore"); - assert.strictEqual(comment.get('name'), 'Dat Parley Letter', 'the post was updated'); - assert.strictEqual(comment.get('post'), post, 'the post is still set'); + assert.false(comment.hasDirtyAttributes, "the post isn't dirty anymore"); + assert.strictEqual(comment.name, 'Dat Parley Letter', 'the post was updated'); + assert.strictEqual(comment.post, post, 'the post is still set'); }); test("createRecord - a serializer's primary key and attributes are consulted when building the payload", async function (assert) { @@ -444,7 +445,7 @@ module('integration/adapter/rest_adapter - REST Adapter - createRecord', functio ], }); - store.push({ + const post = store.push({ data: { type: 'post', id: '1', @@ -458,7 +459,6 @@ module('integration/adapter/rest_adapter - REST Adapter - createRecord', functio }, }, }); - store.push({ data: { type: 'comment', @@ -474,20 +474,15 @@ module('integration/adapter/rest_adapter - REST Adapter - createRecord', functio }, }); - const post = store.peekRecord('post', 1); - const commentCount = post.get('comments.length'); - + const commentCount = post.comments.length; assert.strictEqual(commentCount, 1, 'the post starts life with a comment'); let comment = store.createRecord('comment', { name: 'Another Comment', post: post }); - await comment.save(); - - assert.strictEqual(comment.get('post'), post, 'the comment is related to the post'); + assert.strictEqual(comment.post, post, 'the comment is related to the post'); await post.reload(); - - assert.strictEqual(post.get('comments.length'), 2, 'Post comment count has been updated'); + assert.strictEqual(post.comments.length, 2, 'Post comment count has been updated'); }); test('createRecord - sideloaded belongsTo relationships are both marked as loaded', async function (assert) { @@ -518,9 +513,9 @@ module('integration/adapter/rest_adapter - REST Adapter - createRecord', functio const post = store.createRecord('post', { name: 'man' }); const record = await post.save(); - assert.true(store.peekRecord('post', '1').get('comment.isLoaded'), "post's comment isLoaded (via store)"); - assert.true(store.peekRecord('comment', '1').get('post.isLoaded'), "comment's post isLoaded (via store)"); - assert.true(record.get('comment.isLoaded'), "post's comment isLoaded (via record)"); + assert.true(store.peekRecord('post', '1').comment.isLoaded, "post's comment isLoaded (via store)"); + assert.true(store.peekRecord('comment', '1').post.isLoaded, "comment's post isLoaded (via store)"); + assert.true(record.comment.isLoaded, "post's comment isLoaded (via record)"); assert.true(record.get('comment.post.isLoaded'), "post's comment's post isLoaded (via record)"); }); @@ -604,11 +599,11 @@ module('integration/adapter/rest_adapter - REST Adapter - createRecord', functio await post.save(); - assert.strictEqual(post.get('comments.length'), 0, 'post has 0 comments'); + assert.strictEqual(post.comments.length, 0, 'post has 0 comments'); - post.get('comments').pushObject(comment); + post.comments.pushObject(comment); - assert.strictEqual(post.get('comments.length'), 1, 'post has 1 comment'); + assert.strictEqual(post.comments.length, 1, 'post has 1 comment'); ajaxResponse(adapter, { post: [{ id: '1', name: 'Rails is omakase', comments: [2] }], @@ -617,6 +612,6 @@ module('integration/adapter/rest_adapter - REST Adapter - createRecord', functio await post.save(); - assert.strictEqual(post.get('comments.length'), 1, 'post has 1 comment'); + assert.strictEqual(post.comments.length, 1, 'post has 1 comment'); }); }); diff --git a/packages/-ember-data/tests/integration/adapter/rest-adapter/find-record-test.js b/packages/-ember-data/tests/integration/adapter/rest-adapter/find-record-test.js index c6ee08bdb2f..588addd526d 100644 --- a/packages/-ember-data/tests/integration/adapter/rest-adapter/find-record-test.js +++ b/packages/-ember-data/tests/integration/adapter/rest-adapter/find-record-test.js @@ -49,8 +49,8 @@ module('integration/adapter/rest_adapter - REST Adapter - findRecord', function assert.strictEqual(passedVerb, 'GET'); assert.deepEqual(passedHash.data, {}); - assert.strictEqual(post.get('id'), '1'); - assert.strictEqual(post.get('name'), 'Rails is omakase'); + assert.strictEqual(post.id, '1'); + assert.strictEqual(post.name, 'Rails is omakase'); }); // Ok Identifier tests @@ -116,8 +116,8 @@ module('integration/adapter/rest_adapter - REST Adapter - findRecord', function assert.strictEqual(passedVerb, 'GET'); assert.deepEqual(passedHash.data, {}); - assert.strictEqual(post.get('id'), '1'); - assert.strictEqual(post.get('name'), 'Rails is omakase'); + assert.strictEqual(post.id, '1'); + assert.strictEqual(post.name, 'Rails is omakase'); // stress tests let peekPost = store.peekRecord(findRecordArgs); @@ -257,8 +257,8 @@ module('integration/adapter/rest_adapter - REST Adapter - findRecord', function assert.strictEqual(passedUrl, '/posts/1'); assert.strictEqual(passedVerb, 'GET'); assert.deepEqual(passedHash.data, {}); - assert.strictEqual(post.get('id'), '1'); - assert.strictEqual(post.get('name'), 'Rails is omakase'); + assert.strictEqual(post.id, '1'); + assert.strictEqual(post.name, 'Rails is omakase'); }); test('findRecord - payload with sideloaded records of the same type', async function (assert) { @@ -291,13 +291,13 @@ module('integration/adapter/rest_adapter - REST Adapter - findRecord', function assert.strictEqual(passedVerb, 'GET'); assert.deepEqual(passedHash.data, {}); - assert.strictEqual(post.get('id'), '1'); - assert.strictEqual(post.get('name'), 'Rails is omakase'); + assert.strictEqual(post.id, '1'); + assert.strictEqual(post.name, 'Rails is omakase'); const post2 = await store.peekRecord('post', '2'); - assert.strictEqual(post2.get('id'), '2'); - assert.strictEqual(post2.get('name'), 'The Parley Letter'); + assert.strictEqual(post2.id, '2'); + assert.strictEqual(post2.name, 'The Parley Letter'); }); test('findRecord - payload with sideloaded records of a different type', async function (assert) { @@ -327,12 +327,12 @@ module('integration/adapter/rest_adapter - REST Adapter - findRecord', function assert.strictEqual(passedUrl, '/posts/1'); assert.strictEqual(passedVerb, 'GET'); assert.deepEqual(passedHash.data, {}); - assert.strictEqual(post.get('id'), '1'); - assert.strictEqual(post.get('name'), 'Rails is omakase'); + assert.strictEqual(post.id, '1'); + assert.strictEqual(post.name, 'Rails is omakase'); const comment = await store.peekRecord('comment', '1'); - assert.strictEqual(comment.get('id'), '1'); + assert.strictEqual(comment.id, '1'); }); test('findRecord - payload with an serializer-specified primary key', async function (assert) { @@ -366,8 +366,8 @@ module('integration/adapter/rest_adapter - REST Adapter - findRecord', function assert.strictEqual(passedUrl, '/posts/1'); assert.strictEqual(passedVerb, 'GET'); assert.deepEqual(passedHash.data, {}); - assert.strictEqual(post.get('id'), '1'); - assert.strictEqual(post.get('name'), 'Rails is omakase'); + assert.strictEqual(post.id, '1'); + assert.strictEqual(post.name, 'Rails is omakase'); }); test('findRecord - payload with a serializer-specified attribute mapping', async function (assert) { @@ -407,9 +407,9 @@ module('integration/adapter/rest_adapter - REST Adapter - findRecord', function assert.strictEqual(passedUrl, '/posts/1'); assert.strictEqual(passedVerb, 'GET'); assert.deepEqual(passedHash.data, {}); - assert.strictEqual(post.get('id'), '1'); - assert.strictEqual(post.get('name'), 'Rails is omakase'); - assert.strictEqual(post.get('createdAt'), 2013); + assert.strictEqual(post.id, '1'); + assert.strictEqual(post.name, 'Rails is omakase'); + assert.strictEqual(post.createdAt, 2013); }); test('findRecord - passes `include` as a query parameter to ajax', async function (assert) { diff --git a/packages/-ember-data/tests/integration/adapter/store-adapter-test.js b/packages/-ember-data/tests/integration/adapter/store-adapter-test.js index 7e018b32ca2..9eec2afcca5 100644 --- a/packages/-ember-data/tests/integration/adapter/store-adapter-test.js +++ b/packages/-ember-data/tests/integration/adapter/store-adapter-test.js @@ -12,15 +12,18 @@ import RESTAdapter from '@ember-data/adapter/rest'; import Model, { attr, belongsTo, hasMany } from '@ember-data/model'; import JSONAPISerializer from '@ember-data/serializer/json-api'; import RESTSerializer from '@ember-data/serializer/rest'; +import { recordIdentifierFor } from '@ember-data/store'; import { Snapshot } from '@ember-data/store/-private'; import testInDebug from '@ember-data/unpublished-test-infra/test-support/test-in-debug'; function moveRecordOutOfInFlight(record) { // move record out of the inflight state so the tests can clean up // correctly - let { store, _internalModel } = record; + let { store } = record; + let identifier = recordIdentifierFor(record); + // TODO this would be made nicer by a cancellation API - let pending = store.getRequestStateService().getPendingRequestsForRecord(_internalModel.identifier); + let pending = store.getRequestStateService().getPendingRequestsForRecord(identifier); pending.splice(0, pending.length); } @@ -124,8 +127,16 @@ module('integration/adapter/store-adapter - DS.Store and DS.Adapter integration tom = records.tom; yehuda = records.yehuda; - assert.asyncEqual(tom, store.findRecord('person', '1'), 'Once an ID is in, findRecord returns the same object'); - assert.asyncEqual(yehuda, store.findRecord('person', '2'), 'Once an ID is in, findRecord returns the same object'); + assert.strictEqual( + tom, + await store.findRecord('person', '1'), + 'Once an ID is in, findRecord returns the same object' + ); + assert.strictEqual( + yehuda, + await store.findRecord('person', '2'), + 'Once an ID is in, findRecord returns the same object' + ); assert.strictEqual(tom.updatedAt, 'now', 'The new information is received'); assert.strictEqual(yehuda.updatedAt, 'now', 'The new information is received'); }); diff --git a/packages/-ember-data/tests/integration/application-test.js b/packages/-ember-data/tests/integration/application-test.js index 47fe3614a8d..99f4d8552c6 100644 --- a/packages/-ember-data/tests/integration/application-test.js +++ b/packages/-ember-data/tests/integration/application-test.js @@ -130,12 +130,12 @@ module('integration/application - Using the store as a service', function (hooks test('The store can be injected as a service', async function (assert) { let doodleService = this.owner.lookup('service:doodle'); - assert.ok(doodleService.get('store') instanceof Store, 'the store can be used as a service'); + assert.ok(doodleService.store instanceof Store, 'the store can be used as a service'); }); test('There can be multiple store services', function (assert) { let doodleService = this.owner.lookup('service:doodle'); - let store = doodleService.get('store'); + let store = doodleService.store; let secondService = this.owner.lookup('service:second-store'); assert.ok(secondService instanceof Store, 'the store can be used as a service'); @@ -196,7 +196,7 @@ module('integration/application - Attaching initializer', function (hooks) { let store = this.owner.lookup('service:store'); assert.ok( - store && store.get('isCustomStore'), + store && store.isCustomStore, 'ember-data initializer does not overwrite the previous registered service store' ); }); diff --git a/packages/-ember-data/tests/integration/backwards-compat/non-dasherized-lookups-test.js b/packages/-ember-data/tests/integration/backwards-compat/non-dasherized-lookups-test.js index f042aa0729a..bf20ebc7d08 100644 --- a/packages/-ember-data/tests/integration/backwards-compat/non-dasherized-lookups-test.js +++ b/packages/-ember-data/tests/integration/backwards-compat/non-dasherized-lookups-test.js @@ -151,7 +151,7 @@ module( }); }); - test('looks up belongsTo using under_scored strings', function (assert) { + test('looks up belongsTo using under_scored strings', async function (assert) { assert.expect(1); let store = this.owner.lookup('service:store'); @@ -181,13 +181,11 @@ module( }); }); - run(() => { - store.findRecord('long_model_name', 1).then((longModelName) => { - const postNotes = get(longModelName, 'postNotes').toArray(); + const longModel = await store.findRecord('long_model_name', '1'); + const postNotesRel = await longModel.postNotes; + const postNotes = postNotesRel.toArray(); - assert.deepEqual(postNotes, [store.peekRecord('postNote', 1)], 'inverse records found'); - }); - }); + assert.deepEqual(postNotes, [store.peekRecord('postNote', 1)], 'inverse records found'); }); } ); diff --git a/packages/-ember-data/tests/integration/inverse-test.js b/packages/-ember-data/tests/integration/inverse-test.js index 3295605f898..9e229b40822 100644 --- a/packages/-ember-data/tests/integration/inverse-test.js +++ b/packages/-ember-data/tests/integration/inverse-test.js @@ -280,7 +280,7 @@ module('integration/inverse_test - inverseFor', function (hooks) { }, }); reflexiveModel = store.peekRecord('reflexive-model', 1); - reflexiveModel.get('reflexiveProp'); + reflexiveModel.reflexiveProp; }, /Detected a reflexive relationship by the name of 'reflexiveProp'/); }); }); diff --git a/packages/-ember-data/tests/integration/polymorphic-belongs-to-test.js b/packages/-ember-data/tests/integration/polymorphic-belongs-to-test.js index 4140626adad..f3cd4e9f311 100644 --- a/packages/-ember-data/tests/integration/polymorphic-belongs-to-test.js +++ b/packages/-ember-data/tests/integration/polymorphic-belongs-to-test.js @@ -63,7 +63,7 @@ module('integration/polymorphic-belongs-to - Polymorphic BelongsTo', function (h store.push(payload); let book = store.peekRecord('book', 1); - assert.strictEqual(book.get('author.id'), '1'); + assert.strictEqual(book.author.id, '1'); let payloadThatResetsBelongToRelationship = { data: { @@ -79,7 +79,7 @@ module('integration/polymorphic-belongs-to - Polymorphic BelongsTo', function (h }; store.push(payloadThatResetsBelongToRelationship); - assert.strictEqual(book.get('author'), null); + assert.strictEqual(book.author, null); }); test('using store.push with a null value for a payload in relationships sets the Models relationship to null - async relationship', function (assert) { @@ -122,12 +122,11 @@ module('integration/polymorphic-belongs-to - Polymorphic BelongsTo', function (h }, }; - return book - .get('author') + return book.author .then((author) => { - assert.strictEqual(author.get('id'), '1'); + assert.strictEqual(author.id, '1'); store.push(payloadThatResetsBelongToRelationship); - return book.get('author'); + return book.author; }) .then((author) => { assert.strictEqual(author, null); 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 30fe12138bf..9f99f8f9652 100644 --- a/packages/-ember-data/tests/integration/record-array-manager-test.js +++ b/packages/-ember-data/tests/integration/record-array-manager-test.js @@ -97,12 +97,12 @@ module('integration/record_array_manager', function (hooks) { let adapterPopulated = manager.createAdapterPopulatedRecordArray('person', query); let allSummary = tap(all, 'willDestroy'); let adapterPopulatedSummary = tap(adapterPopulated, 'willDestroy'); - let internalPersonModel = person._internalModel; + let identifier = recordIdentifierFor(person); assert.strictEqual(allSummary.called.length, 0, 'initial: no calls to all.willDestroy'); assert.strictEqual(adapterPopulatedSummary.called.length, 0, 'initial: no calls to adapterPopulated.willDestroy'); assert.strictEqual( - manager.getRecordArraysForIdentifier(internalPersonModel.identifier).size, + manager.getRecordArraysForIdentifier(identifier).size, 1, 'initial: expected the person to be a member of 1 recordArrays' ); @@ -112,7 +112,7 @@ module('integration/record_array_manager', function (hooks) { await settled(); assert.strictEqual( - manager.getRecordArraysForIdentifier(internalPersonModel.identifier).size, + manager.getRecordArraysForIdentifier(identifier).size, 0, 'expected the person to be a member of no recordArrays' ); @@ -123,7 +123,7 @@ module('integration/record_array_manager', function (hooks) { await settled(); assert.strictEqual( - manager.getRecordArraysForIdentifier(internalPersonModel.identifier).size, + manager.getRecordArraysForIdentifier(identifier).size, 0, 'expected the person to be a member of no recordArrays' ); @@ -249,7 +249,7 @@ module('integration/record_array_manager', function (hooks) { assert.strictEqual(recordArray.modelName, 'foo'); assert.true(recordArray.isLoaded); assert.strictEqual(recordArray.manager, manager); - assert.deepEqual(recordArray.get('content'), []); + assert.deepEqual(recordArray.content, []); assert.deepEqual(recordArray.toArray(), []); }); @@ -271,7 +271,7 @@ module('integration/record_array_manager', function (hooks) { assert.strictEqual(recordArray.modelName, 'foo', 'has modelName'); assert.true(recordArray.isLoaded, 'isLoaded is true'); assert.strictEqual(recordArray.manager, manager, 'recordArray has manager'); - assert.deepEqual(recordArray.get('content'), [recordIdentifierFor(record)], 'recordArray has content'); + assert.deepEqual(recordArray.content, [recordIdentifierFor(record)], 'recordArray has content'); assert.deepEqual(recordArray.toArray(), [record], 'toArray works'); }); @@ -285,11 +285,11 @@ module('integration/record_array_manager', function (hooks) { let createRecordArrayCalled = 0; let superCreateRecordArray = manager.createRecordArray; - manager.createRecordArray = function (modelName, internalModels) { + manager.createRecordArray = function (modelName, identifiers) { createRecordArrayCalled++; assert.strictEqual(modelName, 'car'); - assert.strictEqual(internalModels.length, 1); - assert.strictEqual(internalModels[0].id, '1'); + assert.strictEqual(identifiers.length, 1); + assert.strictEqual(identifiers[0].id, '1'); return superCreateRecordArray.apply(this, arguments); }; diff --git a/packages/-ember-data/tests/integration/record-array-test.js b/packages/-ember-data/tests/integration/record-array-test.js index 62a19fefb09..a5e40dd05aa 100644 --- a/packages/-ember-data/tests/integration/record-array-test.js +++ b/packages/-ember-data/tests/integration/record-array-test.js @@ -134,7 +134,7 @@ module('unit/record-array - RecordArray', function (hooks) { await settled(); - assert.strictEqual(recordArray.get('length'), 0, 'Has no more records'); + assert.strictEqual(recordArray.length, 0, 'Has no more records'); store.push({ data: { type: 'person', @@ -147,8 +147,8 @@ module('unit/record-array - RecordArray', function (hooks) { await settled(); - assert.strictEqual(recordArray.get('length'), 0, 'length has not been updated'); - assert.strictEqual(recordArray.get('content'), null, 'content has not been updated'); + assert.strictEqual(recordArray.length, 0, 'length has not been updated'); + assert.strictEqual(recordArray.content, null, 'content has not been updated'); }); test('a loaded record is removed from a record array when it is deleted', async function (assert) { @@ -198,11 +198,11 @@ module('unit/record-array - RecordArray', function (hooks) { let scumbag = await store.findRecord('person', 1); let tag = await store.findRecord('tag', 1); - let recordArray = tag.get('people'); + let recordArray = tag.people; recordArray.addObject(scumbag); - assert.strictEqual(scumbag.get('tag'), tag, "precond - the scumbag's tag has been set"); + assert.strictEqual(scumbag.tag, tag, "precond - the scumbag's tag has been set"); assert.strictEqual(get(recordArray, 'length'), 1, 'precond - record array has one item'); assert.strictEqual(get(recordArray.objectAt(0), 'name'), 'Scumbag Dale', 'item at index 0 is record with id 1'); @@ -265,8 +265,8 @@ module('unit/record-array - RecordArray', function (hooks) { scumbag.deleteRecord(); - assert.strictEqual(tag.get('people.length'), 1, 'record is not removed from the record array'); - assert.strictEqual(tag.get('people').objectAt(0), scumbag, 'tag still has the scumbag'); + assert.strictEqual(tag.people.length, 1, 'record is not removed from the record array'); + assert.strictEqual(tag.people.objectAt(0), scumbag, 'tag still has the scumbag'); }); test("a loaded record is not removed from both the record array and from the belongs to, even if the belongsTo side isn't defined", async function (assert) { @@ -313,13 +313,13 @@ module('unit/record-array - RecordArray', function (hooks) { let tag = store.peekRecord('tag', 1); let tool = store.peekRecord('tool', 1); - assert.strictEqual(tag.get('people.length'), 1, 'record is in the record array'); - assert.strictEqual(tool.get('person'), scumbag, 'the tool belongs to the record'); + assert.strictEqual(tag.people.length, 1, 'record is in the record array'); + assert.strictEqual(tool.person, scumbag, 'the tool belongs to the record'); scumbag.deleteRecord(); - assert.strictEqual(tag.get('people.length'), 1, 'record is stil in the record array'); - assert.strictEqual(tool.get('person'), scumbag, 'the tool still belongs to the record'); + assert.strictEqual(tag.people.length, 1, 'record is stil in the record array'); + assert.strictEqual(tool.person, scumbag, 'the tool still belongs to the record'); }); // GitHub Issue #168 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 7cff8970f4e..55ff859dd4a 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 @@ -14,7 +14,7 @@ import { recordIdentifierFor } from '@ember-data/store'; const Person = Model.extend({ name: attr('string'), toString() { - return ``; + return ``; }, }); @@ -72,13 +72,13 @@ module('integration/record-arrays/adapter_populated_record_array - AdapterPopula payload ); - assert.strictEqual(recordArray.get('length'), 3, 'expected recordArray to contain exactly 3 records'); + assert.strictEqual(recordArray.length, 3, 'expected recordArray to contain exactly 3 records'); - recordArray.get('firstObject').destroyRecord(); + recordArray.firstObject.destroyRecord(); await settled(); - assert.strictEqual(recordArray.get('length'), 2, 'expected recordArray to contain exactly 2 records'); + assert.strictEqual(recordArray.length, 2, 'expected recordArray to contain exactly 2 records'); }); test('stores the metadata off the payload', async function (assert) { @@ -119,7 +119,7 @@ module('integration/record-arrays/adapter_populated_record_array - AdapterPopula results.map((r) => recordIdentifierFor(r)), payload ); - assert.strictEqual(recordArray.get('meta.foo'), 'bar', 'expected meta.foo to be bar from payload'); + assert.strictEqual(recordArray.meta.foo, 'bar', 'expected meta.foo to be bar from payload'); }); test('stores the links off the payload', async function (assert) { @@ -161,11 +161,7 @@ module('integration/record-arrays/adapter_populated_record_array - AdapterPopula payload ); - assert.strictEqual( - recordArray.get('links.first'), - '/foo?page=1', - 'expected links.first to be "/foo?page=1" from payload' - ); + assert.strictEqual(recordArray.links.first, '/foo?page=1', 'expected links.first to be "/foo?page=1" from payload'); }); test('recordArray.replace() throws error', async function (assert) { @@ -183,7 +179,8 @@ module('integration/record-arrays/adapter_populated_record_array - AdapterPopula ); }); - test('pass record array to adapter.query regardless of arity', async function (assert) { + test('pass record array to adapter.query regardless of its arity', async function (assert) { + assert.expect(2); let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); @@ -193,72 +190,27 @@ module('integration/record-arrays/adapter_populated_record_array - AdapterPopula { id: '2', type: 'person', attributes: { name: 'Scumbag Katz' } }, ], }; - - adapter.query = function (store, type, query) { - // Due to #6232, we now expect 5 arguments regardless of arity - assert.strictEqual(arguments.length, 5, 'expect 5 arguments in query'); - return payload; - }; - - await store.query('person', {}); - - adapter.query = function (store, type, query, recordArray) { - assert.strictEqual(arguments.length, 5); - return payload; - }; - store.query('person', {}); - }); - - test('pass record array to adapter.query regardless of arity', async function (assert) { - let store = this.owner.lookup('service:store'); - let adapter = store.adapterFor('application'); - - let payload = { - data: [ - { id: '1', type: 'person', attributes: { name: 'Scumbag Dale' } }, - { id: '2', type: 'person', attributes: { name: 'Scumbag Katz' } }, - ], - }; - let actualQuery = {}; - let superCreateAdapterPopulatedRecordArray = store.recordArrayManager.createAdapterPopulatedRecordArray; - - store.recordArrayManager.createStore = function (modelName, query, internalModels, _payload) { - assert.strictEqual(arguments.length, 4); - - assert.strictEqual(modelName, 'person'); - assert.strictEqual(query, actualQuery); - assert.strictEqual(_payload, payload); - assert.strictEqual(internalModels.length, 2); - return superCreateAdapterPopulatedRecordArray.apply(this, arguments); - }; - + // arity 3 adapter.query = function (store, type, query) { // Due to #6232, we now expect 5 arguments regardless of arity - assert.strictEqual(arguments.length, 5); + assert.strictEqual(arguments.length, 5, 'we receive 5 args to adapter query with arity 3'); return payload; }; await store.query('person', actualQuery); + // arity 4 adapter.query = function (store, type, query, _recordArray) { - assert.strictEqual(arguments.length, 5); + assert.strictEqual(arguments.length, 5, 'we receive 5 args to adapter query with arity 4'); return payload; }; - store.recordArrayManager.createStore = function (modelName, query) { - assert.strictEqual(arguments.length, 2); - - assert.strictEqual(modelName, 'person'); - assert.strictEqual(query, actualQuery); - return superCreateAdapterPopulatedRecordArray.apply(this, arguments); - }; - - store.query('person', actualQuery); + await store.query('person', actualQuery); }); - test('loadRecord re-syncs internalModels recordArrays', async function (assert) { + test('loadRecord re-syncs identifiers recordArrays', async function (assert) { let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); @@ -320,24 +272,24 @@ module('integration/record-arrays/adapter_populated_record_array - AdapterPopula queryArr = await store.query('person', { slice: 1 }); findArray = await store.findAll('person'); - assert.strictEqual(queryArr.get('length'), 0, 'No records for this query'); - assert.false(queryArr.get('isUpdating'), 'Record array isUpdating state updated'); - assert.strictEqual(findArray.get('length'), 1, 'All records are included in collection array'); + assert.strictEqual(queryArr.length, 0, 'No records for this query'); + assert.false(queryArr.isUpdating, 'Record array isUpdating state updated'); + assert.strictEqual(findArray.length, 1, 'All records are included in collection array'); // a new element gets pushed in record array array.push({ id: '2', type: 'person', attributes: { name: 'Scumbag Katz' } }); await queryArr.update(); - assert.strictEqual(queryArr.get('length'), 1, 'The new record is returned and added in adapter populated array'); - assert.false(queryArr.get('isUpdating'), 'Record array isUpdating state updated'); - assert.strictEqual(findArray.get('length'), 2, 'find returns 2 records'); + assert.strictEqual(queryArr.length, 1, 'The new record is returned and added in adapter populated array'); + assert.false(queryArr.isUpdating, 'Record array isUpdating state updated'); + assert.strictEqual(findArray.length, 2, 'find returns 2 records'); // element gets removed array.pop(0); await queryArr.update(); - assert.strictEqual(queryArr.get('length'), 0, 'Record removed from array'); + assert.strictEqual(queryArr.length, 0, 'Record removed from array'); // record not removed from the model collection - assert.strictEqual(findArray.get('length'), 2, 'Record still remains in collection array'); + assert.strictEqual(findArray.length, 2, 'Record still remains in collection array'); }); }); diff --git a/packages/-ember-data/tests/integration/record-arrays/peeked-records-test.js b/packages/-ember-data/tests/integration/record-arrays/peeked-records-test.js index 4bd1cc71e40..87f0c2e32bf 100644 --- a/packages/-ember-data/tests/integration/record-arrays/peeked-records-test.js +++ b/packages/-ember-data/tests/integration/record-arrays/peeked-records-test.js @@ -13,7 +13,7 @@ let store; const Person = Model.extend({ name: attr('string'), toString() { - return ``; + return ``; }, }); diff --git a/packages/-ember-data/tests/integration/record-data/record-data-errors-test.ts b/packages/-ember-data/tests/integration/record-data/record-data-errors-test.ts index 2283f3bbab4..39d904d4c38 100644 --- a/packages/-ember-data/tests/integration/record-data/record-data-errors-test.ts +++ b/packages/-ember-data/tests/integration/record-data/record-data-errors-test.ts @@ -286,13 +286,13 @@ module('integration/record-data - Custom RecordData Errors', function (hooks) { data: [personHash], }); let person = store.peekRecord('person', '1'); - let nameError = person.get('errors').errorsFor('name').get('firstObject'); + let nameError = person.errors.errorsFor('name').firstObject; assert.strictEqual(nameError.attribute, 'name', 'error shows up on name'); - assert.false(person.get('isValid'), 'person is not valid'); + assert.false(person.isValid, 'person is not valid'); errorsToReturn = []; storeWrapper.notifyErrorsChange('person', '1'); - assert.true(person.get('isValid'), 'person is valid'); - assert.strictEqual(person.get('errors').errorsFor('name').length, 0, 'no errors on name'); + assert.true(person.isValid, 'person is valid'); + assert.strictEqual(person.errors.errorsFor('name').length, 0, 'no errors on name'); errorsToReturn = [ { title: 'Invalid Attribute', @@ -303,9 +303,9 @@ module('integration/record-data - Custom RecordData Errors', function (hooks) { }, ]; storeWrapper.notifyErrorsChange('person', '1'); - assert.false(person.get('isValid'), 'person is valid'); - assert.strictEqual(person.get('errors').errorsFor('name').length, 0, 'no errors on name'); - let lastNameError = person.get('errors').errorsFor('lastName').get('firstObject'); + assert.false(person.isValid, 'person is valid'); + assert.strictEqual(person.errors.errorsFor('name').length, 0, 'no errors on name'); + let lastNameError = person.errors.errorsFor('lastName').firstObject; assert.strictEqual(lastNameError.attribute, 'lastName', 'error shows up on lastName'); }); }); diff --git a/packages/-ember-data/tests/integration/record-data/record-data-state-test.ts b/packages/-ember-data/tests/integration/record-data/record-data-state-test.ts index 911c09d8713..0ff1b53fd19 100644 --- a/packages/-ember-data/tests/integration/record-data/record-data-state-test.ts +++ b/packages/-ember-data/tests/integration/record-data/record-data-state-test.ts @@ -1,5 +1,5 @@ import EmberObject from '@ember/object'; -import Ember from 'ember'; +import { settled } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { Promise } from 'rsvp'; @@ -162,7 +162,9 @@ module('integration/record-data - Record Data State', function (hooks) { return isDeletionCommitted; } - setIsDeleted(identifier, isDeleted): void {} + setIsDeleted(): void { + isDeleted = true; + } } let TestStore = Store.extend({ @@ -215,7 +217,7 @@ module('integration/record-data - Record Data State', function (hooks) { }); test('Record Data state record flags', async function (assert) { - assert.expect(9); + assert.expect(14); let isDeleted, isNew, isDeletionCommitted; let calledSetIsDeleted = false; let storeWrapper; @@ -235,6 +237,10 @@ module('integration/record-data - Record Data State', function (hooks) { storeWrapper = sw; } + isEmpty(): boolean { + return !isNew && isDeleted; + } + isNew(): boolean { return isNew; } @@ -247,7 +253,8 @@ module('integration/record-data - Record Data State', function (hooks) { return isDeletionCommitted; } - setIsDeleted(identifier, isDeleted: boolean): void { + setIsDeleted(identifier, value: boolean): void { + isDeleted = true; calledSetIsDeleted = true; } } @@ -268,34 +275,42 @@ module('integration/record-data - Record Data State', function (hooks) { let person = store.peekRecord('person', '1'); let people = store.peekAll('person'); - isNew = true; + assert.strictEqual(people.length, 1, 'live array starting length is 1'); + isNew = true; storeWrapper.notifyStateChange('person', '1', null, 'isNew'); - assert.true(person.get('isNew'), 'person is new'); + await settled(); + assert.true(person.isNew, 'person is new'); + assert.strictEqual(people.length, 1, 'live array starting length is 1'); isNew = false; isDeleted = true; storeWrapper.notifyStateChange('person', '1', null, 'isDeleted'); storeWrapper.notifyStateChange('person', '1', null, 'isNew'); - - assert.false(person.get('isNew'), 'person is not new'); - assert.true(person.get('isDeleted'), 'person is deleted'); + await settled(); + assert.false(person.isNew, 'person is not new'); + assert.true(person.isDeleted, 'person is deleted'); + assert.strictEqual(people.length, 1, 'live array starting length is 1'); isNew = false; isDeleted = false; storeWrapper.notifyStateChange('person', '1', null, 'isDeleted'); - assert.false(person.get('isNew'), 'person is not new'); - assert.false(person.get('isDeleted'), 'person is not deleted'); - + await settled(); + assert.false(person.isNew, 'person is not new'); + assert.false(person.isDeleted, 'person is not deleted'); + assert.strictEqual(people.length, 1, 'live array starting length is 1'); person.deleteRecord(); - assert.false(person.get('isDeleted'), 'calling deleteRecord does not automatically set isDeleted flag to true'); + await settled(); + assert.strictEqual(people.length, 1, 'live array starting length is 1 after deleteRecord'); + assert.false(person.isDeleted, 'calling deleteRecord does not automatically set isDeleted flag to true'); assert.true(calledSetIsDeleted, 'called setIsDeleted'); - assert.strictEqual(people.get('length'), 1, 'live array starting length is 1'); + storeWrapper.notifyStateChange('person', '1', null); + assert.strictEqual(people.length, 1, 'live array starting length is 1'); + isDeletionCommitted = true; - Ember.run(() => { - storeWrapper.notifyStateChange('person', '1', null, 'isDeletionCommitted'); - }); - assert.strictEqual(people.get('length'), 0, 'commiting a deletion updates the live array'); + storeWrapper.notifyStateChange('person', '1', null, 'isDeletionCommitted'); + await settled(); + assert.strictEqual(people.length, 0, 'commiting a deletion updates the live array'); }); }); diff --git a/packages/-ember-data/tests/integration/record-data/record-data-test.ts b/packages/-ember-data/tests/integration/record-data/record-data-test.ts index d493bfecc07..dac1231495e 100644 --- a/packages/-ember-data/tests/integration/record-data/record-data-test.ts +++ b/packages/-ember-data/tests/integration/record-data/record-data-test.ts @@ -402,14 +402,14 @@ module('integration/record-data - Custom RecordData Implementations', function ( }); let person = store.peekRecord('person', '1'); - assert.strictEqual(person.get('name'), 'new attribute'); + assert.strictEqual(person.name, 'new attribute'); assert.strictEqual(calledGet, 1, 'called getAttr for initial get'); person.set('name', 'new value'); assert.strictEqual(calledGet, 2, 'called getAttr during set'); - assert.strictEqual(person.get('name'), 'new value'); + assert.strictEqual(person.name, 'new value'); assert.strictEqual(calledGet, 2, 'did not call getAttr after set'); person.notifyPropertyChange('name'); - assert.strictEqual(person.get('name'), 'new attribute'); + assert.strictEqual(person.name, 'new attribute'); assert.strictEqual(calledGet, 3, 'called getAttr after notifyPropertyChange'); assert.deepEqual( person.changedAttributes(), @@ -466,10 +466,10 @@ module('integration/record-data - Custom RecordData Implementations', function ( let house = store.peekRecord('house', '1'); let runspired = store.peekRecord('person', '2'); - assert.strictEqual(house.get('landlord.name'), 'David', 'belongsTo get correctly looked up'); + assert.strictEqual(house.landlord.name, 'David', 'belongsTo get correctly looked up'); house.set('landlord', runspired); - assert.strictEqual(house.get('landlord.name'), 'David', 'belongsTo does not change if RD did not notify'); + assert.strictEqual(house.landlord.name, 'David', 'belongsTo does not change if RD did not notify'); }); test('Record Data custom belongsTo', async function (assert) { @@ -518,13 +518,13 @@ module('integration/record-data - Custom RecordData Implementations', function ( }); let house = store.peekRecord('house', '1'); - assert.strictEqual(house.get('landlord.name'), 'David', 'belongsTo get correctly looked up'); + assert.strictEqual(house.landlord.name, 'David', 'belongsTo get correctly looked up'); let runspired = store.peekRecord('person', '2'); house.set('landlord', runspired); // This is intentionally !== runspired to test the custom RD implementation - assert.strictEqual(house.get('landlord.name'), 'Igor', 'RecordData sets the custom belongsTo value'); + assert.strictEqual(house.landlord.name, 'Igor', 'RecordData sets the custom belongsTo value'); }); test('Record Data controls hasMany notifications', async function (assert) { @@ -597,7 +597,7 @@ module('integration/record-data - Custom RecordData Implementations', function ( }); let house = store.peekRecord('house', '1'); - let people = house.get('tenants'); + let people = house.tenants; let david = store.peekRecord('person', '1'); let runspired = store.peekRecord('person', '2'); let igor = store.peekRecord('person', '3'); @@ -699,7 +699,7 @@ module('integration/record-data - Custom RecordData Implementations', function ( }); let house = store.peekRecord('house', '1'); - let people = house.get('tenants'); + let people = house.tenants; let david = store.peekRecord('person', '1'); let runspired = store.peekRecord('person', '2'); let igor = store.peekRecord('person', '3'); diff --git a/packages/-ember-data/tests/integration/record-data/store-wrapper-test.ts b/packages/-ember-data/tests/integration/record-data/store-wrapper-test.ts index 1c8409dcd80..8648bc3045c 100644 --- a/packages/-ember-data/tests/integration/record-data/store-wrapper-test.ts +++ b/packages/-ember-data/tests/integration/record-data/store-wrapper-test.ts @@ -283,8 +283,8 @@ module('integration/store-wrapper - RecordData StoreWrapper tests', function (ho assert.expect(3); let { owner } = this; let count = 0; - let internalModel; let recordData; + let newRecordData; class RecordDataForTest extends TestRecordData { id: string; @@ -298,7 +298,7 @@ module('integration/store-wrapper - RecordData StoreWrapper tests', function (ho if (count === 1) { recordData = storeWrapper.recordDataFor('house'); } else if (count === 2) { - internalModel = store._instanceCache._internalModelForResource({ type: 'house', lid }); + newRecordData = this; } } @@ -336,15 +336,15 @@ module('integration/store-wrapper - RecordData StoreWrapper tests', function (ho assert.ok(recordData._isNew, 'Our RecordData is new'); assert.ok( - internalModel.isNew(), - 'The internalModel for a RecordData created via Wrapper.recordDataFor(type) is in the "new" state' + newRecordData.isNew(), + 'The recordData for a RecordData created via Wrapper.recordDataFor(type) is in the "new" state' ); assert.strictEqual(count, 2, 'two TestRecordDatas have been created'); }); test('setRecordId', async function (assert) { - assert.expect(1); + assert.expect(2); let { owner } = this; class RecordDataForTest extends TestRecordData { @@ -371,8 +371,7 @@ module('integration/store-wrapper - RecordData StoreWrapper tests', function (ho store = owner.lookup('service:store'); let house = store.createRecord('house'); - // TODO there is a bug when setting id while creating the Record instance, preventing the id property lookup to work - // assert.strictEqual(house.get('id'), '17', 'setRecordId correctly set the id'); + assert.strictEqual(house.id, '17', 'setRecordId correctly set the id'); assert.strictEqual( store.peekRecord('house', 17), house, diff --git a/packages/-ember-data/tests/integration/record-data/unloading-record-data-test.js b/packages/-ember-data/tests/integration/record-data/unloading-record-data-test.js index aa77905639f..ea144bea3b5 100644 --- a/packages/-ember-data/tests/integration/record-data/unloading-record-data-test.js +++ b/packages/-ember-data/tests/integration/record-data/unloading-record-data-test.js @@ -149,10 +149,10 @@ module('RecordData Compatibility', function (hooks) { }, ], }); - let pets = chris.get('pets'); + let pets = chris.pets; let shen = pets.objectAt(0); - assert.strictEqual(shen.get('name'), 'Shen', 'We found Shen'); + assert.strictEqual(shen.name, 'Shen', 'We found Shen'); assert.ok(recordDataFor(chris) instanceof RecordData, 'We used the default record-data for person'); assert.ok(recordDataFor(shen) instanceof CustomRecordData, 'We used the custom record-data for pets'); @@ -207,10 +207,10 @@ module('RecordData Compatibility', function (hooks) { }, ], }); - let pets = chris.get('pets'); + let pets = chris.pets; let shen = pets.objectAt(0); - assert.strictEqual(shen.get('name'), 'Shen', 'We found Shen'); + assert.strictEqual(shen.name, 'Shen', 'We found Shen'); try { run(() => shen.unloadRecord()); diff --git a/packages/-ember-data/tests/integration/records/collection-save-test.js b/packages/-ember-data/tests/integration/records/collection-save-test.js index f9130400594..d665106c9eb 100644 --- a/packages/-ember-data/tests/integration/records/collection-save-test.js +++ b/packages/-ember-data/tests/integration/records/collection-save-test.js @@ -97,7 +97,7 @@ module('integration/records/collection_save - Save Collection of Records', funct .then((post) => { // the ID here is '2' because the second post saves on the first attempt, // while the first post saves on the second attempt - assert.strictEqual(posts.get('firstObject.id'), '2', 'The post ID made it through'); + assert.strictEqual(posts.firstObject.id, '2', 'The post ID made it through'); }); }); }); diff --git a/packages/-ember-data/tests/integration/records/create-record-test.js b/packages/-ember-data/tests/integration/records/create-record-test.js index 996d8b011ff..cd98b404a19 100644 --- a/packages/-ember-data/tests/integration/records/create-record-test.js +++ b/packages/-ember-data/tests/integration/records/create-record-test.js @@ -79,21 +79,15 @@ module('Store.createRecord() coverage', function (hooks) { }); // check that we are properly configured - assert.strictEqual(pet.get('owner'), chris, 'Precondition: Our owner is Chris'); + assert.strictEqual(pet.owner, chris, 'Precondition: Our owner is Chris'); - let pets = chris - .get('pets') - .toArray() - .map((pet) => pet.get('name')); + let pets = chris.pets.toArray().map((pet) => pet.name); assert.deepEqual(pets, ['Shen'], 'Precondition: Chris has Shen as a pet'); pet.unloadRecord(); - assert.strictEqual(pet.get('owner'), null, 'Shen no longer has an owner'); + assert.strictEqual(pet.owner, null, 'Shen no longer has an owner'); // check that the relationship has been dissolved - pets = chris - .get('pets') - .toArray() - .map((pet) => pet.get('name')); + pets = chris.pets.toArray().map((pet) => pet.name); assert.deepEqual(pets, [], 'Chris no longer has any pets'); }); @@ -119,23 +113,15 @@ module('Store.createRecord() coverage', function (hooks) { }); // check that we are properly configured - assert.strictEqual(pet.get('owner'), chris, 'Precondition: Our owner is Chris'); + assert.strictEqual(pet.owner, chris, 'Precondition: Our owner is Chris'); - let pets = chris - .get('pets') - .toArray() - .map((pet) => pet.get('name')); + let pets = chris.pets.toArray().map((pet) => pet.name); assert.deepEqual(pets, ['Shen'], 'Precondition: Chris has Shen as a pet'); - chris.unloadRecord(); - - assert.strictEqual(pet.get('owner'), null, 'Shen no longer has an owner'); + assert.strictEqual(pet.owner, null, 'Shen no longer has an owner'); // check that the relationship has been dissolved - pets = chris - .get('pets') - .toArray() - .map((pet) => pet.get('name')); + pets = chris.pets.toArray().map((pet) => pet.name); assert.deepEqual(pets, [], 'Chris no longer has any pets'); }); @@ -199,8 +185,8 @@ module('Store.createRecord() coverage', function (hooks) { bestHuman: chris, }); - let bestHuman = shen.get('bestHuman'); - let bestDog = await chris.get('bestDog'); + let bestHuman = shen.bestHuman; + let bestDog = await chris.bestDog; // check that we are properly configured assert.strictEqual(bestHuman, chris, 'Precondition: Shen has bestHuman as Chris'); @@ -208,8 +194,8 @@ module('Store.createRecord() coverage', function (hooks) { await shen.save(); - bestHuman = shen.get('bestHuman'); - bestDog = await chris.get('bestDog'); + bestHuman = shen.bestHuman; + bestDog = await chris.bestDog; // check that the relationship has remained established assert.strictEqual(bestHuman, chris, 'Shen bestHuman is still Chris'); diff --git a/packages/-ember-data/tests/integration/records/delete-record-test.js b/packages/-ember-data/tests/integration/records/delete-record-test.js index 2daac4b483e..132e6398fda 100644 --- a/packages/-ember-data/tests/integration/records/delete-record-test.js +++ b/packages/-ember-data/tests/integration/records/delete-record-test.js @@ -13,6 +13,7 @@ import Adapter from '@ember-data/adapter'; import { InvalidError } from '@ember-data/adapter/error'; import Model, { attr, belongsTo, hasMany } from '@ember-data/model'; import JSONAPISerializer from '@ember-data/serializer/json-api'; +import { recordIdentifierFor } from '@ember-data/store'; module('integration/deletedRecord - Deleting Records', function (hooks) { setupTest(hooks); @@ -58,15 +59,15 @@ module('integration/deletedRecord - Deleting Records', function (hooks) { let all = store.peekAll('person'); // pre-condition - assert.strictEqual(all.get('length'), 2, 'pre-condition: 2 records in array'); + assert.strictEqual(all.length, 2, 'pre-condition: 2 records in array'); - run(adam, 'deleteRecord'); + adam.deleteRecord(); - assert.strictEqual(all.get('length'), 2, '2 records in array after deleteRecord'); + assert.strictEqual(all.length, 2, '2 records in array after deleteRecord'); - run(adam, 'save'); + await adam.save(); - assert.strictEqual(all.get('length'), 1, '1 record in array after deleteRecord and save'); + assert.strictEqual(all.length, 1, '1 record in array after deleteRecord and save'); }); test('deleting a record that is part of a hasMany removes it from the hasMany recordArray', async function (assert) { @@ -121,12 +122,12 @@ module('integration/deletedRecord - Deleting Records', function (hooks) { let person = store.peekRecord('person', '1'); // Sanity Check we are in the correct state. - assert.strictEqual(group.get('people.length'), 2, 'expected 2 related records before delete'); - assert.strictEqual(person.get('name'), 'Adam Sunderland', 'expected related records to be loaded'); + assert.strictEqual(group.people.length, 2, 'expected 2 related records before delete'); + assert.strictEqual(person.name, 'Adam Sunderland', 'expected related records to be loaded'); await person.destroyRecord(); - assert.strictEqual(group.get('people.length'), 1, 'expected 1 related records after delete'); + assert.strictEqual(group.people.length, 1, 'expected 1 related records after delete'); }); test('records can be deleted during record array enumeration', async function (assert) { @@ -160,7 +161,7 @@ module('integration/deletedRecord - Deleting Records', function (hooks) { var all = store.peekAll('person'); // pre-condition - assert.strictEqual(all.get('length'), 2, 'expected 2 records'); + assert.strictEqual(all.length, 2, 'expected 2 records'); run(function () { all.forEach(function (record) { @@ -168,7 +169,7 @@ module('integration/deletedRecord - Deleting Records', function (hooks) { }); }); - assert.strictEqual(all.get('length'), 0, 'expected 0 records'); + assert.strictEqual(all.length, 0, 'expected 0 records'); assert.strictEqual(all.objectAt(0), undefined, "can't get any records"); }); @@ -202,11 +203,12 @@ module('integration/deletedRecord - Deleting Records', function (hooks) { ); assert.strictEqual(get(store.peekAll('person'), 'length'), 1, 'The new person should be in the store'); - let internalModel = record._internalModel; + let identifier = recordIdentifierFor(record); + let recordData = store._instanceCache.getRecordData(identifier); record.deleteRecord(); - assert.true(internalModel.isEmpty, 'new person state is empty'); + assert.true(recordData.isEmpty(), 'new person state is empty'); assert.strictEqual(get(store.peekAll('person'), 'length'), 0, 'The new person should be removed from the store'); }); @@ -244,11 +246,12 @@ module('integration/deletedRecord - Deleting Records', function (hooks) { ); assert.strictEqual(get(store.peekAll('person'), 'length'), 1, 'The new person should be in the store'); - let internalModel = record._internalModel; + let identifier = recordIdentifierFor(record); + let recordData = store._instanceCache.getRecordData(identifier); await record.destroyRecord(); - assert.true(internalModel.isEmpty, 'new person state is empty'); + assert.true(recordData.isEmpty(), 'new person state is empty'); assert.strictEqual(get(store.peekAll('person'), 'length'), 0, 'The new person should be removed from the store'); }); @@ -299,19 +302,14 @@ module('integration/deletedRecord - Deleting Records', function (hooks) { assert.strictEqual(get(store.peekAll('person'), 'length'), 1, 'The new person should be in the store'); - let internalModel = record._internalModel; + let identifier = recordIdentifierFor(record); + let recordData = store._instanceCache.getRecordData(identifier); record.deleteRecord(); - // it is uncertain that `root.empty` vs `root.deleted.saved` afterwards is correct - // but this is the expected result of `unloadRecord`. We may want a `root.deleted.saved.unloaded` state? - assert.true(internalModel.isEmpty, 'We reached the correct persisted saved state'); + assert.true(recordData.isEmpty(), 'We reached the correct persisted saved state'); assert.strictEqual(get(store.peekAll('person'), 'length'), 0, 'The new person should be removed from the store'); - - // let cache = store._identityMap._map.person._models; - - // assert.ok(cache.indexOf(internalModel) === -1, 'The internal model is removed from the cache'); - assert.true(internalModel.isDestroyed, 'The internal model is destroyed'); + assert.true(recordData.isDestroyed, 'The recordData is destroyed'); await record.save(); }); @@ -334,20 +332,15 @@ module('integration/deletedRecord - Deleting Records', function (hooks) { assert.strictEqual(get(store.peekAll('person'), 'length'), 1, 'The new person should be in the store'); - let internalModel = record._internalModel; + let identifier = recordIdentifierFor(record); + let recordData = store._instanceCache.getRecordData(identifier); record.deleteRecord(); await settled(); - // it is uncertain that `root.empty` vs `root.deleted.saved` afterwards is correct - // but this is the expected result of `unloadRecord`. We may want a `root.deleted.saved.unloaded` state? - assert.true(internalModel.isEmpty, 'We reached the correct persisted saved state'); + assert.true(recordData.isEmpty(), 'We reached the correct persisted saved state'); assert.strictEqual(get(store.peekAll('person'), 'length'), 0, 'The new person should be removed from the store'); - - // let cache = store._identityMap._map.person._models; - - // assert.ok(cache.indexOf(internalModel) === -1, 'The internal model is removed from the cache'); - assert.true(internalModel.isDestroyed, 'The internal model is destroyed'); + assert.true(recordData.isDestroyed, 'The internal model is destroyed'); record.unloadRecord(); await settled(); @@ -432,25 +425,27 @@ module('integration/deletedRecord - Deleting Records', function (hooks) { store.push(jsonGroup); group = store.peekRecord('group', '1'); + const groupCompany = await group.company; // Sanity Check assert.ok(group, 'expected group to be found'); - assert.strictEqual(group.get('company.name'), 'Inc.', 'group belongs to our company'); - assert.strictEqual(group.get('employees.length'), 1, 'expected 1 related record before delete'); - employee = group.get('employees').objectAt(0); - assert.strictEqual(employee.get('name'), 'Adam Sunderland', 'expected related records to be loaded'); + assert.strictEqual(groupCompany.name, 'Inc.', 'group belongs to our company'); + assert.strictEqual(group.employees.length, 1, 'expected 1 related record before delete'); + const employees = await group.employees; + employee = employees.objectAt(0); + assert.strictEqual(employee.name, 'Adam Sunderland', 'expected related records to be loaded'); await group.destroyRecord(); await employee.destroyRecord(); - assert.strictEqual(store.peekAll('employee').get('length'), 0, 'no employee record loaded'); - assert.strictEqual(store.peekAll('group').get('length'), 0, 'no group record loaded'); + assert.strictEqual(store.peekAll('employee').length, 0, 'no employee record loaded'); + assert.strictEqual(store.peekAll('group').length, 0, 'no group record loaded'); // Server pushes the same group and employee once more after they have been destroyed client-side. (The company is a long-lived record) store.push(jsonEmployee); store.push(jsonGroup); group = store.peekRecord('group', '1'); - assert.strictEqual(group.get('employees.length'), 1, 'expected 1 related record after delete and restore'); + assert.strictEqual(group.employees.length, 1, 'expected 1 related record after delete and restore'); }); }); diff --git a/packages/-ember-data/tests/integration/records/edit-record-test.js b/packages/-ember-data/tests/integration/records/edit-record-test.js index 87d15bdf012..69e2777f5bf 100644 --- a/packages/-ember-data/tests/integration/records/edit-record-test.js +++ b/packages/-ember-data/tests/integration/records/edit-record-test.js @@ -65,23 +65,17 @@ module('Editing a Record', function (hooks) { }); // check that we are properly configured - assert.strictEqual(pet.get('owner'), null, 'Precondition: Our owner is null'); + assert.strictEqual(pet.owner, null, 'Precondition: Our owner is null'); - let pets = chris - .get('pets') - .toArray() - .map((pet) => pet.get('name')); + let pets = chris.pets.toArray().map((pet) => pet.name); assert.deepEqual(pets, [], 'Precondition: Chris has no pets'); pet.set('owner', chris); - assert.strictEqual(pet.get('owner'), chris, 'Shen has Chris as an owner'); + assert.strictEqual(pet.owner, chris, 'Shen has Chris as an owner'); // check that the relationship has been established - pets = chris - .get('pets') - .toArray() - .map((pet) => pet.get('name')); + pets = chris.pets.toArray().map((pet) => pet.name); assert.deepEqual(pets, ['Shen'], 'Chris has Shen as a pet'); }); @@ -105,23 +99,17 @@ module('Editing a Record', function (hooks) { }); // check that we are properly configured - assert.strictEqual(pet.get('owner'), null, 'Precondition: Our owner is null'); + assert.strictEqual(pet.owner, null, 'Precondition: Our owner is null'); - let pets = chris - .get('pets') - .toArray() - .map((pet) => pet.get('name')); + let pets = chris.pets.toArray().map((pet) => pet.name); assert.deepEqual(pets, [], 'Precondition: Chris has no pets'); pet.set('owner', chris); - assert.strictEqual(pet.get('owner'), chris, 'Shen has Chris as an owner'); + assert.strictEqual(pet.owner, chris, 'Shen has Chris as an owner'); // check that the relationship has been established - pets = chris - .get('pets') - .toArray() - .map((pet) => pet.get('name')); + pets = chris.pets.toArray().map((pet) => pet.name); assert.deepEqual(pets, ['Shen'], 'Chris has Shen as a pet'); }); @@ -137,23 +125,17 @@ module('Editing a Record', function (hooks) { }); // check that we are properly configured - assert.strictEqual(pet.get('owner'), null, 'Precondition: Our owner is null'); + assert.strictEqual(pet.owner, null, 'Precondition: Our owner is null'); - let pets = chris - .get('pets') - .toArray() - .map((pet) => pet.get('name')); + let pets = chris.pets.toArray().map((pet) => pet.name); assert.deepEqual(pets, [], 'Precondition: Chris has no pets'); pet.set('owner', chris); - assert.strictEqual(pet.get('owner'), chris, 'Shen has Chris as an owner'); + assert.strictEqual(pet.owner, chris, 'Shen has Chris as an owner'); // check that the relationship has been established - pets = chris - .get('pets') - .toArray() - .map((pet) => pet.get('name')); + pets = chris.pets.toArray().map((pet) => pet.name); assert.deepEqual(pets, ['Shen'], 'Chris has Shen as a pet'); }); @@ -177,23 +159,17 @@ module('Editing a Record', function (hooks) { }); // check that we are properly configured - assert.strictEqual(pet.get('owner'), null, 'Precondition: Our owner is null'); + assert.strictEqual(pet.owner, null, 'Precondition: Our owner is null'); - let pets = chris - .get('pets') - .toArray() - .map((pet) => pet.get('name')); + let pets = chris.pets.toArray().map((pet) => pet.name); assert.deepEqual(pets, [], 'Precondition: Chris has no pets'); pet.set('owner', chris); - assert.strictEqual(pet.get('owner'), chris, 'Shen has Chris as an owner'); + assert.strictEqual(pet.owner, chris, 'Shen has Chris as an owner'); // check that the relationship has been established - pets = chris - .get('pets') - .toArray() - .map((pet) => pet.get('name')); + pets = chris.pets.toArray().map((pet) => pet.name); assert.deepEqual(pets, ['Shen'], 'Chris has Shen as a pet'); }); @@ -259,14 +235,14 @@ module('Editing a Record', function (hooks) { }, }); - assert.strictEqual(shen.get('owner'), chris, 'Precondition: Chris is the current owner'); - assert.strictEqual(rocky.get('owner'), chris, 'Precondition: Chris is the current owner'); + assert.strictEqual(shen.owner, chris, 'Precondition: Chris is the current owner'); + assert.strictEqual(rocky.owner, chris, 'Precondition: Chris is the current owner'); let pets = chris.pets.toArray().map((pet) => pet.name); assert.deepEqual(pets, ['Shen', 'Rocky'], 'Precondition: Chris has Shen and Rocky as pets'); shen.set('owner', john); - assert.strictEqual(shen.get('owner'), john, 'After Update: John is the new owner of Shen'); + assert.strictEqual(shen.owner, john, 'After Update: John is the new owner of Shen'); pets = chris.pets.toArray().map((pet) => pet.name); assert.deepEqual(pets, ['Rocky'], 'After Update: Chris has Rocky as a pet'); @@ -276,8 +252,8 @@ module('Editing a Record', function (hooks) { chris.unloadRecord(); - assert.strictEqual(rocky.get('owner'), null, 'After Unload: Rocky has no owner'); - assert.strictEqual(shen.get('owner'), john, 'After Unload: John should still be the owner of Shen'); + assert.strictEqual(rocky.owner, null, 'After Unload: Rocky has no owner'); + assert.strictEqual(shen.owner, john, 'After Unload: John should still be the owner of Shen'); pets = john.pets.toArray().map((pet) => pet.name); assert.deepEqual(pets, ['Shen'], 'After Unload: John still has Shen as a pet'); @@ -313,8 +289,8 @@ module('Editing a Record', function (hooks) { }); // check that we are properly configured - let chrisBestFriend = await chris.get('bestFriend'); - let jamesBestFriend = await james.get('bestFriend'); + let chrisBestFriend = await chris.bestFriend; + let jamesBestFriend = await james.bestFriend; assert.strictEqual(chrisBestFriend, null, 'Precondition: Chris has no best friend'); assert.strictEqual(jamesBestFriend, null, 'Precondition: James has no best friend'); @@ -322,8 +298,8 @@ module('Editing a Record', function (hooks) { chris.set('bestFriend', james); // check that the relationship has been established - chrisBestFriend = await chris.get('bestFriend'); - jamesBestFriend = await james.get('bestFriend'); + chrisBestFriend = await chris.bestFriend; + jamesBestFriend = await james.bestFriend; assert.strictEqual(chrisBestFriend, james, 'Chris has James as a best friend'); assert.strictEqual(jamesBestFriend, chris, 'James has Chris as a best friend'); @@ -349,8 +325,8 @@ module('Editing a Record', function (hooks) { }); // check that we are properly configured - let chrisBestFriend = await chris.get('bestFriend'); - let jamesBestFriend = await james.get('bestFriend'); + let chrisBestFriend = await chris.bestFriend; + let jamesBestFriend = await james.bestFriend; assert.strictEqual(chrisBestFriend, null, 'Precondition: Chris has no best friend'); assert.strictEqual(jamesBestFriend, null, 'Precondition: James has no best friend'); @@ -358,8 +334,8 @@ module('Editing a Record', function (hooks) { chris.set('bestFriend', james); // check that the relationship has been established - chrisBestFriend = await chris.get('bestFriend'); - jamesBestFriend = await james.get('bestFriend'); + chrisBestFriend = await chris.bestFriend; + jamesBestFriend = await james.bestFriend; assert.strictEqual(chrisBestFriend, james, 'Chris has James as a best friend'); assert.strictEqual(jamesBestFriend, chris, 'James has Chris as a best friend'); @@ -377,8 +353,8 @@ module('Editing a Record', function (hooks) { }); // check that we are properly configured - let chrisBestFriend = await chris.get('bestFriend'); - let jamesBestFriend = await james.get('bestFriend'); + let chrisBestFriend = await chris.bestFriend; + let jamesBestFriend = await james.bestFriend; assert.strictEqual(chrisBestFriend, null, 'Precondition: Chris has no best friend'); assert.strictEqual(jamesBestFriend, null, 'Precondition: James has no best friend'); @@ -386,8 +362,8 @@ module('Editing a Record', function (hooks) { chris.set('bestFriend', james); // check that the relationship has been established - chrisBestFriend = await chris.get('bestFriend'); - jamesBestFriend = await james.get('bestFriend'); + chrisBestFriend = await chris.bestFriend; + jamesBestFriend = await james.bestFriend; assert.strictEqual(chrisBestFriend, james, 'Chris has James as a best friend'); assert.strictEqual(jamesBestFriend, chris, 'James has Chris as a best friend'); @@ -413,8 +389,8 @@ module('Editing a Record', function (hooks) { }); // check that we are properly configured - let chrisBestFriend = await chris.get('bestFriend'); - let jamesBestFriend = await james.get('bestFriend'); + let chrisBestFriend = await chris.bestFriend; + let jamesBestFriend = await james.bestFriend; assert.strictEqual(chrisBestFriend, null, 'Precondition: Chris has no best friend'); assert.strictEqual(jamesBestFriend, null, 'Precondition: James has no best friend'); @@ -422,8 +398,8 @@ module('Editing a Record', function (hooks) { chris.set('bestFriend', james); // check that the relationship has been established - chrisBestFriend = await chris.get('bestFriend'); - jamesBestFriend = await james.get('bestFriend'); + chrisBestFriend = await chris.bestFriend; + jamesBestFriend = await james.bestFriend; assert.strictEqual(chrisBestFriend, james, 'Chris has James as a best friend'); assert.strictEqual(jamesBestFriend, chris, 'James has Chris as a best friend'); diff --git a/packages/-ember-data/tests/integration/records/load-test.js b/packages/-ember-data/tests/integration/records/load-test.js index dc1410b93ed..cfdfa7c98bc 100644 --- a/packages/-ember-data/tests/integration/records/load-test.js +++ b/packages/-ember-data/tests/integration/records/load-test.js @@ -11,6 +11,16 @@ import JSONAPISerializer from '@ember-data/serializer/json-api'; import Store from '@ember-data/store'; import testInDebug from '@ember-data/unpublished-test-infra/test-support/test-in-debug'; +function _isLoading(cache, identifier) { + const req = cache.store.getRequestStateService(); + const fulfilled = req.getLastRequestForRecord(identifier); + const isLoaded = cache.recordIsLoaded(identifier); + + return ( + !isLoaded && fulfilled === null && req.getPendingRequestsForRecord(identifier).some((req) => req.type === 'query') + ); +} + class Person extends Model { @attr() name; @@ -143,31 +153,35 @@ module('integration/load - Loading Records', function (hooks) { }) ); - let internalModel = store._instanceCache._internalModelForResource({ type: 'person', id: '1' }); + let identifier = store.identifierCache.getOrCreateRecordIdentifier({ type: 'person', id: '1' }); + let cache = store._instanceCache; + let recordData = cache.peek({ identifier, bucket: 'recordData' }); // test that our initial state is correct - assert.true(internalModel.isEmpty, 'We begin in the empty state'); - assert.false(internalModel.isLoading, 'We have not triggered a load'); + assert.strictEqual(recordData, undefined, 'We begin in the empty state'); + assert.false(_isLoading(cache, identifier), 'We have not triggered a load'); let recordPromise = store.findRecord('person', '1'); // test that during the initial load our state is correct - assert.true(internalModel.isEmpty, 'awaiting first fetch: We remain in the empty state'); - assert.true(internalModel.isLoading, 'awaiting first fetch: We have now triggered a load'); + recordData = cache.peek({ identifier, bucket: 'recordData' }); + assert.strictEqual(recordData, undefined, 'awaiting first fetch: We remain in the empty state'); + assert.true(_isLoading(cache, identifier), 'awaiting first fetch: We have now triggered a load'); let record = await recordPromise; // test that after the initial load our state is correct - assert.false(internalModel.isEmpty, 'after first fetch: We are no longer empty'); - assert.false(internalModel.isLoading, 'after first fetch: We have loaded'); + recordData = cache.peek({ identifier, bucket: 'recordData' }); + assert.false(recordData.isEmpty(), 'after first fetch: We are no longer empty'); + assert.false(_isLoading(cache, identifier), 'after first fetch: We have loaded'); assert.false(record.isReloading, 'after first fetch: We are not reloading'); - let bestFriend = await record.get('bestFriend'); - let trueBestFriend = await bestFriend.get('bestFriend'); + let bestFriend = await record.bestFriend; + let trueBestFriend = await bestFriend.bestFriend; // shen is our retainer for the record we are testing // that ensures unloadRecord later in this test does not fully - // discard the internalModel + // discard the identifier let shen = store.peekRecord('person', '2'); assert.strictEqual(bestFriend, shen, 'Precond: bestFriend is correct'); @@ -176,37 +190,41 @@ module('integration/load - Loading Records', function (hooks) { recordPromise = record.reload(); // test that during a reload our state is correct - assert.false(internalModel.isEmpty, 'awaiting reload: We remain non-empty'); - assert.false(internalModel.isLoading, 'awaiting reload: We are not loading again'); + assert.false(recordData.isEmpty(), 'awaiting reload: We remain non-empty'); + assert.false(_isLoading(cache, identifier), 'awaiting reload: We are not loading again'); assert.true(record.isReloading, 'awaiting reload: We are reloading'); await recordPromise; // test that after a reload our state is correct - assert.false(internalModel.isEmpty, 'after reload: We remain non-empty'); - assert.false(internalModel.isLoading, 'after reload: We have loaded'); + assert.false(recordData.isEmpty(), 'after reload: We remain non-empty'); + assert.false(_isLoading(cache, identifier), 'after reload: We have loaded'); assert.false(record.isReloading, 'after reload:: We are not reloading'); run(() => record.unloadRecord()); // test that after an unload our state is correct - assert.true(internalModel.isEmpty, 'after unload: We are empty again'); - assert.false(internalModel.isLoading, 'after unload: We are not loading'); + assert.true(recordData.isEmpty(), 'after unload: We are empty again'); + assert.false(_isLoading(cache, identifier), 'after unload: We are not loading'); assert.false(record.isReloading, 'after unload:: We are not reloading'); recordPromise = store.findRecord('person', '1'); // test that during a reload-due-to-unload our state is correct // This requires a retainer (the async bestFriend relationship) - assert.true(internalModel.isEmpty, 'awaiting second find: We remain empty'); - assert.true(internalModel.isLoading, 'awaiting second find: We are loading again'); + assert.true(recordData.isEmpty(), 'awaiting second find: We remain empty'); + let newRecordData = cache.peek({ identifier, bucket: 'recordData' }); + assert.strictEqual(newRecordData, undefined, 'We have no recordData during second find'); + assert.true(_isLoading(cache, identifier), 'awaiting second find: We are loading again'); assert.false(record.isReloading, 'awaiting second find: We are not reloading'); await recordPromise; // test that after the reload-due-to-unload our state is correct - assert.false(internalModel.isEmpty, 'after second find: We are no longer empty'); - assert.false(internalModel.isLoading, 'after second find: We have loaded'); + newRecordData = cache.peek({ identifier, bucket: 'recordData' }); + assert.true(recordData.isEmpty(), 'after second find: Our original recordData is still empty'); + assert.false(newRecordData.isEmpty(), 'after second find: We are no longer empty'); + assert.false(_isLoading(cache, identifier), 'after second find: We have loaded'); assert.false(record.isReloading, 'after second find: We are not reloading'); }); }); diff --git a/packages/-ember-data/tests/integration/records/relationship-changes-test.js b/packages/-ember-data/tests/integration/records/relationship-changes-test.js index 3396170747f..bf37c550a48 100644 --- a/packages/-ember-data/tests/integration/records/relationship-changes-test.js +++ b/packages/-ember-data/tests/integration/records/relationship-changes-test.js @@ -10,6 +10,7 @@ import { setupTest } from 'ember-qunit'; import Adapter from '@ember-data/adapter'; import Model, { attr, belongsTo, hasMany } from '@ember-data/model'; import JSONAPISerializer from '@ember-data/serializer/json-api'; +import { deprecatedTest } from '@ember-data/unpublished-test-infra/test-support/deprecated-test'; const Author = Model.extend({ name: attr('string'), @@ -108,13 +109,69 @@ module('integration/records/relationship-changes - Relationship changes', functi }); if (!gte('4.0.0')) { - test('Calling push with relationship triggers observers once if the relationship was empty and is added to', function (assert) { + test('Calling push with relationship triggers observers once if the relationship was empty and is added to', async function (assert) { assert.expect(1); let store = this.owner.lookup('service:store'); let person = null; let observerCount = 0; + store.push({ + data: { + type: 'person', + id: 'wat', + attributes: { + firstName: 'Yehuda', + lastName: 'Katz', + }, + relationships: { + siblings: { + data: [], + }, + }, + }, + }); + person = store.peekRecord('person', 'wat'); + + person.addObserver('siblings.[]', function () { + observerCount++; + }); + // prime the pump + await person.siblings; + + store.push({ + data: { + type: 'person', + id: 'wat', + attributes: {}, + relationships: { + siblings: { + data: [sibling1Ref], + }, + }, + }, + included: [sibling1], + }); + + assert.ok(observerCount >= 1, 'siblings observer should be triggered at least once'); + }); + } + + deprecatedTest( + 'Calling push with relationship recalculates computed alias property if the relationship was empty and is added to', + { id: 'ember-data:deprecate-promise-many-array-behaviors', until: '5.0', count: 1 }, + function (assert) { + assert.expect(1); + + let store = this.owner.lookup('service:store'); + + let Obj = EmberObject.extend({ + person: null, + siblings: alias('person.siblings'), + }); + + const obj = Obj.create(); + run(() => { store.push({ data: { @@ -131,15 +188,7 @@ module('integration/records/relationship-changes - Relationship changes', functi }, }, }); - person = store.peekRecord('person', 'wat'); - }); - - run(() => { - person.addObserver('siblings.[]', function () { - observerCount++; - }); - // prime the pump - person.get('siblings'); + set(obj, 'person', store.peekRecord('person', 'wat')); }); run(() => { @@ -159,118 +208,70 @@ module('integration/records/relationship-changes - Relationship changes', functi }); run(() => { - assert.ok(observerCount >= 1, 'siblings observer should be triggered at least once'); + let cpResult = get(obj, 'siblings').toArray(); + assert.strictEqual(cpResult.length, 1, 'siblings cp should have recalculated'); + obj.destroy(); }); - }); - } + } + ); - test('Calling push with relationship recalculates computed alias property if the relationship was empty and is added to', function (assert) { - assert.expect(1); + deprecatedTest( + 'Calling push with relationship recalculates computed alias property to firstObject if the relationship was empty and is added to', + { id: 'ember-data:deprecate-promise-many-array-behaviors', until: '5.0', count: 1 }, + function (assert) { + assert.expect(1); - let store = this.owner.lookup('service:store'); + let store = this.owner.lookup('service:store'); - let Obj = EmberObject.extend({ - person: null, - siblings: alias('person.siblings'), - }); + let Obj = EmberObject.extend({ + person: null, + firstSibling: alias('person.siblings.firstObject'), + }); - const obj = Obj.create(); + const obj = Obj.create(); - run(() => { - store.push({ - data: { - type: 'person', - id: 'wat', - attributes: { - firstName: 'Yehuda', - lastName: 'Katz', - }, - relationships: { - siblings: { - data: [], + run(() => { + store.push({ + data: { + type: 'person', + id: 'wat', + attributes: { + firstName: 'Yehuda', + lastName: 'Katz', }, - }, - }, - }); - set(obj, 'person', store.peekRecord('person', 'wat')); - }); - - run(() => { - store.push({ - data: { - type: 'person', - id: 'wat', - attributes: {}, - relationships: { - siblings: { - data: [sibling1Ref], + relationships: { + siblings: { + data: [], + }, }, }, - }, - included: [sibling1], + }); + set(obj, 'person', store.peekRecord('person', 'wat')); }); - }); - run(() => { - let cpResult = get(obj, 'siblings').toArray(); - assert.strictEqual(cpResult.length, 1, 'siblings cp should have recalculated'); - obj.destroy(); - }); - }); - - test('Calling push with relationship recalculates computed alias property to firstObject if the relationship was empty and is added to', function (assert) { - assert.expect(1); - - let store = this.owner.lookup('service:store'); - - let Obj = EmberObject.extend({ - person: null, - firstSibling: alias('person.siblings.firstObject'), - }); - - const obj = Obj.create(); - - run(() => { - store.push({ - data: { - type: 'person', - id: 'wat', - attributes: { - firstName: 'Yehuda', - lastName: 'Katz', - }, - relationships: { - siblings: { - data: [], + run(() => { + store.push({ + data: { + type: 'person', + id: 'wat', + attributes: {}, + relationships: { + siblings: { + data: [sibling1Ref], + }, }, }, - }, + included: [sibling1], + }); }); - set(obj, 'person', store.peekRecord('person', 'wat')); - }); - run(() => { - store.push({ - data: { - type: 'person', - id: 'wat', - attributes: {}, - relationships: { - siblings: { - data: [sibling1Ref], - }, - }, - }, - included: [sibling1], + run(() => { + let cpResult = get(obj, 'firstSibling'); + assert.strictEqual(get(cpResult, 'id'), '1', 'siblings cp should have recalculated'); + obj.destroy(); }); - }); - - run(() => { - let cpResult = get(obj, 'firstSibling'); - assert.strictEqual(get(cpResult, 'id'), '1', 'siblings cp should have recalculated'); - obj.destroy(); - }); - }); + } + ); test('Calling push with relationship triggers observers once if the relationship was not empty and was added to', function (assert) { assert.expect(1); @@ -304,7 +305,7 @@ module('integration/records/relationship-changes - Relationship changes', functi observerCount++; }); // prime the pump - person.get('siblings'); + person.siblings; }); run(() => { @@ -360,7 +361,7 @@ module('integration/records/relationship-changes - Relationship changes', functi observerCount++; }); // prime the pump - person.get('siblings'); + person.siblings; }); run(() => { @@ -416,7 +417,7 @@ module('integration/records/relationship-changes - Relationship changes', functi observerCount++; }); // prime the pump - person.get('siblings'); + person.siblings; }); run(() => { @@ -473,7 +474,7 @@ module('integration/records/relationship-changes - Relationship changes', functi run(() => { // prime the pump - person.get('siblings'); + person.siblings; person.addObserver('siblings.[]', observerMethod); }); @@ -576,323 +577,325 @@ module('integration/records/relationship-changes - Relationship changes', functi { id: 'array-observers', count: 2, when: { ember: '>=3.26.0' } } ); }); - test('Calling push with relationship triggers willChange and didChange with detail when truncating', async function (assert) { - assert.expectDeprecation( - async () => { - let store = this.owner.lookup('service:store'); + deprecatedTest( + 'Calling push with relationship triggers willChange and didChange with detail when truncating', + { id: 'ember-data:deprecate-promise-many-array-behaviors', until: '5.0', count: 2 }, + async function (assert) { + let store = this.owner.lookup('service:store'); - let willChangeCount = 0; - let didChangeCount = 0; + let willChangeCount = 0; + let didChangeCount = 0; - store.push({ - data: { - type: 'person', - id: 'wat', - attributes: { - firstName: 'Yehuda', - lastName: 'Katz', - }, - relationships: { - siblings: { - data: [sibling1Ref, sibling2Ref], - }, + store.push({ + data: { + type: 'person', + id: 'wat', + attributes: { + firstName: 'Yehuda', + lastName: 'Katz', + }, + relationships: { + siblings: { + data: [sibling1Ref, sibling2Ref], }, }, - included: [sibling1, sibling2], - }); + }, + included: [sibling1, sibling2], + }); - let person = store.peekRecord('person', 'wat'); - let siblingsPromise = person.siblings; + let person = store.peekRecord('person', 'wat'); + let siblingsPromise = person.siblings; - await siblingsPromise; + await siblingsPromise; - // flush initial state since - // nothing is consuming us. - // else the test will fail because we will - // (correctly) not notify the array observer - // as there is still a pending notification - siblingsPromise.length; + // flush initial state since + // nothing is consuming us. + // else the test will fail because we will + // (correctly) not notify the array observer + // as there is still a pending notification + siblingsPromise.length; - let observer = { - arrayWillChange(array, start, removing, adding) { - willChangeCount++; - assert.strictEqual(start, 1); - assert.strictEqual(removing, 1); - assert.strictEqual(adding, 0); - }, + let observer = { + arrayWillChange(array, start, removing, adding) { + willChangeCount++; + assert.strictEqual(start, 1); + assert.strictEqual(removing, 1); + assert.strictEqual(adding, 0); + }, - arrayDidChange(array, start, removed, added) { - didChangeCount++; - assert.strictEqual(start, 1); - assert.strictEqual(removed, 1); - assert.strictEqual(added, 0); - }, - }; + arrayDidChange(array, start, removed, added) { + didChangeCount++; + assert.strictEqual(start, 1); + assert.strictEqual(removed, 1); + assert.strictEqual(added, 0); + }, + }; - siblingsPromise.addArrayObserver(observer); + siblingsPromise.addArrayObserver(observer); - store.push({ - data: { - type: 'person', - id: 'wat', - attributes: {}, - relationships: { - siblings: { - data: [sibling1Ref], - }, + store.push({ + data: { + type: 'person', + id: 'wat', + attributes: {}, + relationships: { + siblings: { + data: [sibling1Ref], }, }, - included: [], - }); - - assert.strictEqual(willChangeCount, 1, 'willChange observer should be triggered once'); - assert.strictEqual(didChangeCount, 1, 'didChange observer should be triggered once'); - - siblingsPromise.removeArrayObserver(observer); - }, - { id: 'array-observers', count: 2, when: { ember: '>=3.26.0' } } - ); - }); - - test('Calling push with relationship triggers willChange and didChange with detail when inserting at front', async function (assert) { - assert.expectDeprecation( - async () => { - let store = this.owner.lookup('service:store'); - - let willChangeCount = 0; - let didChangeCount = 0; - - run(() => { - store.push({ - data: { - type: 'person', - id: 'wat', - attributes: { - firstName: 'Yehuda', - lastName: 'Katz', - }, - relationships: { - siblings: { - data: [sibling2Ref], - }, - }, - }, - included: [sibling2], - }); - }); - let person = store.peekRecord('person', 'wat'); + }, + included: [], + }); - let observer = { - arrayWillChange(array, start, removing, adding) { - willChangeCount++; - assert.strictEqual(start, 0, 'change will start at the beginning'); - assert.strictEqual(removing, 0, 'we have no removals'); - assert.strictEqual(adding, 1, 'we have one insertion'); - }, + assert.strictEqual(willChangeCount, 1, 'willChange observer should be triggered once'); + assert.strictEqual(didChangeCount, 1, 'didChange observer should be triggered once'); - arrayDidChange(array, start, removed, added) { - didChangeCount++; - assert.strictEqual(start, 0, 'change did start at the beginning'); - assert.strictEqual(removed, 0, 'change had no removals'); - assert.strictEqual(added, 1, 'change had one insertion'); - }, - }; + siblingsPromise.removeArrayObserver(observer); - const siblingsProxy = person.siblings; - const siblings = await siblingsProxy; + assert.expectDeprecation({ id: 'array-observers', count: 2, when: { ember: '>=3.26.0' } }); + } + ); - // flush initial state since - // nothing is consuming us. - // else the test will fail because we will - // (correctly) not notify the array observer - // as there is still a pending notification - siblingsProxy.length; + deprecatedTest( + 'Calling push with relationship triggers willChange and didChange with detail when inserting at front', + { id: 'ember-data:deprecate-promise-many-array-behaviors', until: '5.0', count: 2 }, + async function (assert) { + let store = this.owner.lookup('service:store'); - siblingsProxy.addArrayObserver(observer); + let willChangeCount = 0; + let didChangeCount = 0; + run(() => { store.push({ data: { type: 'person', id: 'wat', - attributes: {}, + attributes: { + firstName: 'Yehuda', + lastName: 'Katz', + }, relationships: { siblings: { - data: [sibling1Ref, sibling2Ref], + data: [sibling2Ref], }, }, }, - included: [sibling1], + included: [sibling2], }); + }); + let person = store.peekRecord('person', 'wat'); + + let observer = { + arrayWillChange(array, start, removing, adding) { + willChangeCount++; + assert.strictEqual(start, 0, 'change will start at the beginning'); + assert.strictEqual(removing, 0, 'we have no removals'); + assert.strictEqual(adding, 1, 'we have one insertion'); + }, - assert.strictEqual(willChangeCount, 1, 'willChange observer should be triggered once'); - assert.strictEqual(didChangeCount, 1, 'didChange observer should be triggered once'); - assert.deepEqual( - siblings.map((i) => i.id), - ['1', '2'], - 'We have the correct siblings' - ); + arrayDidChange(array, start, removed, added) { + didChangeCount++; + assert.strictEqual(start, 0, 'change did start at the beginning'); + assert.strictEqual(removed, 0, 'change had no removals'); + assert.strictEqual(added, 1, 'change had one insertion'); + }, + }; - siblingsProxy.removeArrayObserver(observer); - }, - { id: 'array-observers', count: 2, when: { ember: '>=3.26.0' } } - ); - }); + const siblingsProxy = person.siblings; + const siblings = await siblingsProxy; - test('Calling push with relationship triggers willChange and didChange with detail when inserting in middle', function (assert) { - assert.expectDeprecation( - async () => { - let store = this.owner.lookup('service:store'); + // flush initial state since + // nothing is consuming us. + // else the test will fail because we will + // (correctly) not notify the array observer + // as there is still a pending notification + siblingsProxy.length; - let willChangeCount = 0; - let didChangeCount = 0; + siblingsProxy.addArrayObserver(observer); - run(() => { - store.push({ - data: { - type: 'person', - id: 'wat', - attributes: { - firstName: 'Yehuda', - lastName: 'Katz', - }, - relationships: { - siblings: { - data: [sibling1Ref, sibling3Ref], - }, - }, + store.push({ + data: { + type: 'person', + id: 'wat', + attributes: {}, + relationships: { + siblings: { + data: [sibling1Ref, sibling2Ref], }, - included: [sibling1, sibling3], - }); - }); - let person = store.peekRecord('person', 'wat'); - let observer = { - arrayWillChange(array, start, removing, adding) { - willChangeCount++; - assert.strictEqual(start, 1); - assert.strictEqual(removing, 0); - assert.strictEqual(adding, 1); - }, - arrayDidChange(array, start, removed, added) { - didChangeCount++; - assert.strictEqual(start, 1); - assert.strictEqual(removed, 0); - assert.strictEqual(added, 1); }, - }; - - let siblings = run(() => person.get('siblings')); - - // flush initial state since - // nothing is consuming us. - // else the test will fail because we will - // (correctly) not notify the array observer - // as there is still a pending notification - siblings.length; - - siblings.addArrayObserver(observer); + }, + included: [sibling1], + }); - run(() => { - store.push({ - data: { - type: 'person', - id: 'wat', - attributes: {}, - relationships: { - siblings: { - data: [sibling1Ref, sibling2Ref, sibling3Ref], + assert.strictEqual(willChangeCount, 1, 'willChange observer should be triggered once'); + assert.strictEqual(didChangeCount, 1, 'didChange observer should be triggered once'); + assert.deepEqual( + siblings.map((i) => i.id), + ['1', '2'], + 'We have the correct siblings' + ); + + siblingsProxy.removeArrayObserver(observer); + + assert.expectDeprecation({ id: 'array-observers', count: 2, when: { ember: '>=3.26.0' } }); + } + ); + + deprecatedTest( + 'Calling push with relationship triggers willChange and didChange with detail when inserting in middle', + { id: 'ember-data:deprecate-promise-many-array-behaviors', until: '5.0', count: 2 }, + function (assert) { + assert.expectDeprecation( + async () => { + let store = this.owner.lookup('service:store'); + + let willChangeCount = 0; + let didChangeCount = 0; + + run(() => { + store.push({ + data: { + type: 'person', + id: 'wat', + attributes: { + firstName: 'Yehuda', + lastName: 'Katz', + }, + relationships: { + siblings: { + data: [sibling1Ref, sibling3Ref], + }, }, }, + included: [sibling1, sibling3], + }); + }); + let person = store.peekRecord('person', 'wat'); + let observer = { + arrayWillChange(array, start, removing, adding) { + willChangeCount++; + assert.strictEqual(start, 1); + assert.strictEqual(removing, 0); + assert.strictEqual(adding, 1); + }, + arrayDidChange(array, start, removed, added) { + didChangeCount++; + assert.strictEqual(start, 1); + assert.strictEqual(removed, 0); + assert.strictEqual(added, 1); }, - included: [sibling2], + }; + + let siblings = run(() => person.siblings); + + // flush initial state since + // nothing is consuming us. + // else the test will fail because we will + // (correctly) not notify the array observer + // as there is still a pending notification + siblings.length; + + siblings.addArrayObserver(observer); + + run(() => { + store.push({ + data: { + type: 'person', + id: 'wat', + attributes: {}, + relationships: { + siblings: { + data: [sibling1Ref, sibling2Ref, sibling3Ref], + }, + }, + }, + included: [sibling2], + }); }); - }); - assert.strictEqual(willChangeCount, 1, 'willChange observer should be triggered once'); - assert.strictEqual(didChangeCount, 1, 'didChange observer should be triggered once'); + assert.strictEqual(willChangeCount, 1, 'willChange observer should be triggered once'); + assert.strictEqual(didChangeCount, 1, 'didChange observer should be triggered once'); - siblings.removeArrayObserver(observer); - }, - { id: 'array-observers', count: 2, when: { ember: '>=3.26.0' } } - ); - }); + siblings.removeArrayObserver(observer); + }, + { id: 'array-observers', count: 2, when: { ember: '>=3.26.0' } } + ); + } + ); - test('Calling push with relationship triggers willChange and didChange with detail when replacing different length in middle', function (assert) { - assert.expectDeprecation( - async () => { - let store = this.owner.lookup('service:store'); + deprecatedTest( + 'Calling push with relationship triggers willChange and didChange with detail when replacing different length in middle', + { id: 'ember-data:deprecate-promise-many-array-behaviors', until: '5.0', count: 2 }, + async function (assert) { + let store = this.owner.lookup('service:store'); - let willChangeCount = 0; - let didChangeCount = 0; + let willChangeCount = 0; + let didChangeCount = 0; - run(() => { - store.push({ - data: { - type: 'person', - id: 'wat', - attributes: { - firstName: 'Yehuda', - lastName: 'Katz', - }, - relationships: { - siblings: { - data: [sibling1Ref, sibling2Ref, sibling3Ref], - }, - }, + store.push({ + data: { + type: 'person', + id: 'wat', + attributes: { + firstName: 'Yehuda', + lastName: 'Katz', + }, + relationships: { + siblings: { + data: [sibling1Ref, sibling2Ref, sibling3Ref], }, - included: [sibling1, sibling2, sibling3], - }); - }); - - let person = store.peekRecord('person', 'wat'); - let observer = { - arrayWillChange(array, start, removing, adding) { - willChangeCount++; - assert.strictEqual(start, 1); - assert.strictEqual(removing, 1); - assert.strictEqual(adding, 2); }, + }, + included: [sibling1, sibling2, sibling3], + }); - arrayDidChange(array, start, removed, added) { - didChangeCount++; - assert.strictEqual(start, 1); - assert.strictEqual(removed, 1); - assert.strictEqual(added, 2); - }, - }; + let person = store.peekRecord('person', 'wat'); + let observer = { + arrayWillChange(array, start, removing, adding) { + willChangeCount++; + assert.strictEqual(start, 1); + assert.strictEqual(removing, 1); + assert.strictEqual(adding, 2); + }, - let siblings = run(() => person.get('siblings')); - // flush initial state since - // nothing is consuming us. - // else the test will fail because we will - // (correctly) not notify the array observer - // as there is still a pending notification - siblings.length; - siblings.addArrayObserver(observer); + arrayDidChange(array, start, removed, added) { + didChangeCount++; + assert.strictEqual(start, 1); + assert.strictEqual(removed, 1); + assert.strictEqual(added, 2); + }, + }; - run(() => { - store.push({ - data: { - type: 'person', - id: 'wat', - attributes: {}, - relationships: { - siblings: { - data: [sibling1Ref, sibling4Ref, sibling5Ref, sibling3Ref], - }, - }, + let siblings = run(() => person.siblings); + // flush initial state since + // nothing is consuming us. + // else the test will fail because we will + // (correctly) not notify the array observer + // as there is still a pending notification + siblings.length; + siblings.addArrayObserver(observer); + + store.push({ + data: { + type: 'person', + id: 'wat', + attributes: {}, + relationships: { + siblings: { + data: [sibling1Ref, sibling4Ref, sibling5Ref, sibling3Ref], }, - included: [sibling4, sibling5], - }); - }); + }, + }, + included: [sibling4, sibling5], + }); - assert.strictEqual(willChangeCount, 1, 'willChange observer should be triggered once'); - assert.strictEqual(didChangeCount, 1, 'didChange observer should be triggered once'); + assert.strictEqual(willChangeCount, 1, 'willChange observer should be triggered once'); + assert.strictEqual(didChangeCount, 1, 'didChange observer should be triggered once'); - siblings.removeArrayObserver(observer); - }, - { id: 'array-observers', count: 2, when: { ember: '>=3.26.0' } } - ); - }); + siblings.removeArrayObserver(observer); + assert.expectDeprecation({ id: 'array-observers', count: 2, when: { ember: '>=3.26.0' } }); + } + ); test('Calling push with updated belongsTo relationship trigger observer', function (assert) { assert.expect(1); @@ -919,7 +922,7 @@ module('integration/records/relationship-changes - Relationship changes', functi ], }); - post.get('author'); + post.author; post.addObserver('author', function () { observerCount++; diff --git a/packages/-ember-data/tests/integration/records/reload-test.js b/packages/-ember-data/tests/integration/records/reload-test.js index 6f688b4257c..e6787d2b00a 100644 --- a/packages/-ember-data/tests/integration/records/reload-test.js +++ b/packages/-ember-data/tests/integration/records/reload-test.js @@ -114,7 +114,7 @@ module('integration/reload - Reloading Records', function (hooks) { }, findRecord() { - assert.true(tom.get('isReloading'), 'Tom is reloading'); + assert.true(tom.isReloading, 'Tom is reloading'); if (count++ === 0) { return reject(); } else { @@ -130,15 +130,15 @@ module('integration/reload - Reloading Records', function (hooks) { assert.ok(true, 'we throw an error'); }); - assert.true(tom.get('isError'), 'Tom is now errored'); - assert.false(tom.get('isReloading'), 'Tom is no longer reloading'); + assert.true(tom.isError, 'Tom is now errored'); + assert.false(tom.isReloading, 'Tom is no longer reloading'); let person = await tom.reload(); assert.strictEqual(person, tom, 'The resolved value is the record'); - assert.false(tom.get('isError'), 'Tom is no longer errored'); - assert.false(tom.get('isReloading'), 'Tom is no longer reloading'); - assert.strictEqual(tom.get('name'), 'Thomas Dale', 'the updates apply'); + assert.false(tom.isError, 'Tom is no longer errored'); + assert.false(tom.isReloading, 'Tom is no longer reloading'); + assert.strictEqual(tom.name, 'Thomas Dale', 'the updates apply'); }); test('When a record is loaded a second time, isLoaded stays true', async function (assert) { @@ -245,16 +245,16 @@ module('integration/reload - Reloading Records', function (hooks) { let person = await store.findRecord('person', '1'); tom = person; - assert.strictEqual(person.get('name'), 'Tom', 'precond'); + assert.strictEqual(person.name, 'Tom', 'precond'); - let tags = await person.get('tags'); + let tags = await person.tags; assert.deepEqual(tags.mapBy('name'), ['hipster', 'hair']); person = await tom.reload(); - assert.strictEqual(person.get('name'), 'Tom', 'precond'); + assert.strictEqual(person.name, 'Tom', 'precond'); - tags = await person.get('tags'); + tags = await person.tags; assert.deepEqual(tags.mapBy('name'), ['hipster', 'hair'], 'The tags are still there'); }); @@ -311,7 +311,7 @@ module('integration/reload - Reloading Records', function (hooks) { }); let ownerRef = shen.belongsTo('owner'); - let owner = shen.get('owner'); + let owner = shen.owner; let ownerViaRef = await ownerRef.reload(); assert.strictEqual(owner, ownerViaRef, 'We received the same reference via reload'); @@ -360,7 +360,7 @@ module('integration/reload - Reloading Records', function (hooks) { let ownerRef = shen.belongsTo('owner'); let ownerViaRef = await ownerRef.reload(); - let owner = shen.get('owner'); + let owner = shen.owner; assert.strictEqual(owner, ownerViaRef, 'We received the same reference via reload'); }); @@ -416,7 +416,7 @@ module('integration/reload - Reloading Records', function (hooks) { }); let ownersRef = shen.hasMany('owners'); - let owners = shen.get('owners'); + let owners = shen.owners; let ownersViaRef = await ownersRef.reload(); assert.strictEqual(owners.objectAt(0), ownersViaRef.objectAt(0), 'We received the same reference via reload'); @@ -465,7 +465,7 @@ module('integration/reload - Reloading Records', function (hooks) { let ownersRef = shen.hasMany('owners'); let ownersViaRef = await ownersRef.reload(); - let owners = shen.get('owners'); + let owners = shen.owners; assert.strictEqual(owners.objectAt(0), ownersViaRef.objectAt(0), 'We received the same reference via reload'); }); @@ -526,7 +526,7 @@ module('integration/reload - Reloading Records', function (hooks) { }); let ownerRef = shen.belongsTo('owner'); - let owner = shen.get('owner'); + let owner = shen.owner; let ownerViaRef = await ownerRef.reload(); assert.strictEqual(owner, ownerViaRef, 'We received the same reference via reload'); @@ -578,7 +578,7 @@ module('integration/reload - Reloading Records', function (hooks) { let ownerRef = shen.belongsTo('owner'); let ownerViaRef = await ownerRef.reload(); - let owner = shen.get('owner'); + let owner = shen.owner; assert.strictEqual(owner, ownerViaRef, 'We received the same reference via reload'); }); @@ -639,7 +639,7 @@ module('integration/reload - Reloading Records', function (hooks) { }); let ownersRef = shen.hasMany('owners'); - let owners = shen.get('owners'); + let owners = shen.owners; let ownersViaRef = await ownersRef.reload(); assert.strictEqual(owners.objectAt(0), ownersViaRef.objectAt(0), 'We received the same reference via reload'); @@ -693,7 +693,7 @@ module('integration/reload - Reloading Records', function (hooks) { let ownersRef = shen.hasMany('owners'); let ownersViaRef = await ownersRef.reload(); - let owners = shen.get('owners'); + let owners = shen.owners; assert.strictEqual(owners.objectAt(0), ownersViaRef.objectAt(0), 'We received the same reference via reload'); }); diff --git a/packages/-ember-data/tests/integration/records/rematerialize-test.js b/packages/-ember-data/tests/integration/records/rematerialize-test.js index 2bdf32e3e4d..1327d69ea48 100644 --- a/packages/-ember-data/tests/integration/records/rematerialize-test.js +++ b/packages/-ember-data/tests/integration/records/rematerialize-test.js @@ -82,7 +82,7 @@ module('integration/unload - Rematerializing Unloaded Records', function (hooks) }); let person = store.peekRecord('person', 1); - assert.strictEqual(person.get('cars.length'), 1, 'The inital length of cars is correct'); + assert.strictEqual(person.cars.length, 1, 'The inital length of cars is correct'); assert.notStrictEqual(store.peekRecord('person', '1'), null, 'The person is in the store'); assert.true( @@ -115,9 +115,9 @@ module('integration/unload - Rematerializing Unloaded Records', function (hooks) }); }); - let rematerializedPerson = bob.get('person'); - assert.strictEqual(rematerializedPerson.get('id'), '1'); - assert.strictEqual(rematerializedPerson.get('name'), 'Adam Sunderland'); + let rematerializedPerson = bob.person; + assert.strictEqual(rematerializedPerson.id, '1'); + assert.strictEqual(rematerializedPerson.name, 'Adam Sunderland'); // the person is rematerialized; the previous person is *not* re-used assert.notEqual(rematerializedPerson, adam, 'the person is rematerialized, not recycled'); }); @@ -221,11 +221,11 @@ module('integration/unload - Rematerializing Unloaded Records', function (hooks) assert.notStrictEqual(store.peekRecord('boat', '1'), null, 'The boat is in the store'); assert.true(!!store.identifierCache.peekRecordIdentifier({ lid: '@lid:boat-1' }), 'The boat identifier is loaded'); - let boats = await adam.get('boats'); - assert.strictEqual(boats.get('length'), 2, 'Before unloading boats.length is correct'); + let boats = await adam.boats; + assert.strictEqual(boats.length, 2, 'Before unloading boats.length is correct'); boaty.unloadRecord(); - assert.strictEqual(boats.get('length'), 1, 'after unloading boats.length is correct'); + assert.strictEqual(boats.length, 1, 'after unloading boats.length is correct'); // assert our new cache state assert.strictEqual(store.peekRecord('boat', '1'), null, 'The boat is unloaded'); @@ -235,13 +235,13 @@ module('integration/unload - Rematerializing Unloaded Records', function (hooks) ); // cause a rematerialization, this should also cause us to fetch boat '1' again - boats = run(() => adam.get('boats')); + boats = await adam.boats; let rematerializedBoaty = boats.objectAt(1); assert.ok(!!rematerializedBoaty, 'We have a boat!'); - assert.strictEqual(adam.get('boats.length'), 2, 'boats.length correct after rematerialization'); - assert.strictEqual(rematerializedBoaty.get('id'), '1', 'Rematerialized boat has the right id'); - assert.strictEqual(rematerializedBoaty.get('name'), 'Boaty McBoatface', 'Rematerialized boat has the right name'); + assert.strictEqual(adam.boats.length, 2, 'boats.length correct after rematerialization'); + assert.strictEqual(rematerializedBoaty.id, '1', 'Rematerialized boat has the right id'); + assert.strictEqual(rematerializedBoaty.name, 'Boaty McBoatface', 'Rematerialized boat has the right name'); assert.notStrictEqual(rematerializedBoaty, boaty, 'the boat is rematerialized, not recycled'); assert.notStrictEqual(store.peekRecord('boat', '1'), null, 'The boat is loaded'); diff --git a/packages/-ember-data/tests/integration/records/save-test.js b/packages/-ember-data/tests/integration/records/save-test.js index 9e5111f574c..51a5cb40176 100644 --- a/packages/-ember-data/tests/integration/records/save-test.js +++ b/packages/-ember-data/tests/integration/records/save-test.js @@ -39,6 +39,7 @@ module('integration/records/save - Save Record', function (hooks) { if (DEPRECATE_SAVE_PROMISE_ACCESS) { // `save` returns a PromiseObject which allows to call get on it assert.strictEqual(saved.get('id'), undefined); + assert.strictEqual(saved.id, undefined); } deferred.resolve({ data: { id: 123, type: 'post' } }); @@ -47,8 +48,10 @@ module('integration/records/save - Save Record', function (hooks) { if (DEPRECATE_SAVE_PROMISE_ACCESS) { assert.strictEqual(saved.get('id'), '123'); assert.strictEqual(saved.id, undefined); + assert.strictEqual(model.id, '123'); } else { assert.strictEqual(saved.id, undefined); + assert.strictEqual(model.id, '123'); } assert.strictEqual(model, post, 'resolves with the model'); if (DEPRECATE_SAVE_PROMISE_ACCESS) { @@ -56,7 +59,7 @@ module('integration/records/save - Save Record', function (hooks) { // should not throw an error and only show a deprecation. assert.strictEqual(saved.__ec_cancel__, undefined); - assert.expectDeprecation({ id: 'ember-data:model-save-promise', count: 4 }); + assert.expectDeprecation({ id: 'ember-data:model-save-promise', count: 5 }); } }); @@ -107,7 +110,7 @@ module('integration/records/save - Save Record', function (hooks) { } ) .then(function (post) { - assert.strictEqual(post.get('id'), '123', 'The post ID made it through'); + assert.strictEqual(post.id, '123', 'The post ID made it through'); }); }); }); @@ -125,12 +128,12 @@ module('integration/records/save - Save Record', function (hooks) { run(function () { post.save().then(null, function () { - assert.ok(post.get('isError')); - assert.strictEqual(post.get('currentState.stateName'), 'root.loaded.created.uncommitted'); + assert.ok(post.isError); + assert.strictEqual(post.currentState.stateName, 'root.loaded.created.uncommitted'); post.save().then(null, function () { - assert.ok(post.get('isError')); - assert.strictEqual(post.get('currentState.stateName'), 'root.loaded.created.uncommitted'); + assert.ok(post.isError); + assert.strictEqual(post.currentState.stateName, 'root.loaded.created.uncommitted'); }); }); }); @@ -156,10 +159,10 @@ module('integration/records/save - Save Record', function (hooks) { run(function () { post.save().then(null, function () { - assert.false(post.get('isValid')); + assert.false(post.isValid); post.save().then(null, function () { - assert.false(post.get('isValid')); + assert.false(post.isValid); }); }); }); @@ -179,10 +182,10 @@ module('integration/records/save - Save Record', function (hooks) { run(function () { post.save().then(null, function () { - assert.false(post.get('isValid')); + assert.false(post.isValid); post.save().then(null, function () { - assert.false(post.get('isValid')); + assert.false(post.isValid); }); }); }); diff --git a/packages/-ember-data/tests/integration/records/unload-test.js b/packages/-ember-data/tests/integration/records/unload-test.js index 6e6b47b674b..5aa9fa176a8 100644 --- a/packages/-ember-data/tests/integration/records/unload-test.js +++ b/packages/-ember-data/tests/integration/records/unload-test.js @@ -2,6 +2,7 @@ import { get } from '@ember/object'; import { run } from '@ember/runloop'; +import { settled } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { all, resolve } from 'rsvp'; @@ -11,6 +12,7 @@ import { setupTest } from 'ember-qunit'; import JSONAPIAdapter from '@ember-data/adapter/json-api'; import Model, { attr, belongsTo, hasMany } from '@ember-data/model'; import JSONAPISerializer from '@ember-data/serializer/json-api'; +import { recordIdentifierFor } from '@ember-data/store'; function idsFromArr(arr) { return arr.map((i) => i.id); @@ -142,46 +144,44 @@ module('integration/unload - Unloading Records', function (hooks) { adapter = store.adapterFor('application'); }); - test('can unload a single record', function (assert) { - let adam; - run(function () { - store.push({ - data: { - type: 'person', - id: '1', - attributes: { - name: 'Adam Sunderland', + test('can unload a single record', async function (assert) { + let adam = store.push({ + data: { + type: 'person', + id: '1', + attributes: { + name: 'Adam Sunderland', + }, + relationships: { + cars: { + data: [ + { + id: 1, + type: 'car', + }, + ], }, - relationships: { - cars: { - data: [ - { - id: 1, - type: 'car', - }, - ], - }, - boats: { - data: [ - { - id: 2, - type: 'boat', - }, - ], - }, + boats: { + data: [ + { + id: 2, + type: 'boat', + }, + ], }, }, - }); - adam = store.peekRecord('person', 1); + }, }); - assert.strictEqual(store.peekAll('person').get('length'), 1, 'one person record loaded'); + const people = store.peekAll('person'); + assert.strictEqual(people.length, 1, 'one person record loaded in our live array'); - run(function () { - adam.unloadRecord(); - }); + adam.unloadRecord(); + await settled(); - assert.strictEqual(store.peekAll('person').get('length'), 0, 'no person records'); + assert.strictEqual(people.length, 0, 'no person records in our live array'); + adam = store.peekRecord('person', '1'); + assert.strictEqual(adam, null, 'we have no person'); }); test('can unload all records for a given type', function (assert) { @@ -228,16 +228,16 @@ module('integration/unload - Unloading Records', function (hooks) { bob = store.peekRecord('car', 1); }); - assert.strictEqual(store.peekAll('person').get('length'), 2, 'two person records loaded'); - assert.strictEqual(store.peekAll('car').get('length'), 1, 'one car record loaded'); + assert.strictEqual(store.peekAll('person').length, 2, 'two person records loaded'); + assert.strictEqual(store.peekAll('car').length, 1, 'one car record loaded'); run(function () { - car.get('person'); + car.person; store.unloadAll('person'); }); - assert.strictEqual(store.peekAll('person').get('length'), 0); - assert.strictEqual(store.peekAll('car').get('length'), 1); + assert.strictEqual(store.peekAll('person').length, 0); + assert.strictEqual(store.peekAll('car').length, 1); run(function () { store.push({ @@ -252,7 +252,7 @@ module('integration/unload - Unloading Records', function (hooks) { }); car = store.peekRecord('car', 1); - let person = car.get('person'); + let person = car.person; assert.ok(!!car, 'We have a car'); assert.notOk(person, 'We dont have a person'); @@ -301,15 +301,15 @@ module('integration/unload - Unloading Records', function (hooks) { bob = store.peekRecord('car', 1); }); - assert.strictEqual(store.peekAll('person').get('length'), 2, 'two person records loaded'); - assert.strictEqual(store.peekAll('car').get('length'), 1, 'one car record loaded'); + assert.strictEqual(store.peekAll('person').length, 2, 'two person records loaded'); + assert.strictEqual(store.peekAll('car').length, 1, 'one car record loaded'); run(function () { store.unloadAll(); }); - assert.strictEqual(store.peekAll('person').get('length'), 0); - assert.strictEqual(store.peekAll('car').get('length'), 0); + assert.strictEqual(store.peekAll('person').length, 0); + assert.strictEqual(store.peekAll('car').length, 0); }); test('removes findAllCache after unloading all records', function (assert) { @@ -338,14 +338,14 @@ module('integration/unload - Unloading Records', function (hooks) { let bob = store.peekRecord('person', 2); }); - assert.strictEqual(store.peekAll('person').get('length'), 2, 'two person records loaded'); + assert.strictEqual(store.peekAll('person').length, 2, 'two person records loaded'); run(function () { store.peekAll('person'); store.unloadAll('person'); }); - assert.strictEqual(store.peekAll('person').get('length'), 0, 'zero person records loaded'); + assert.strictEqual(store.peekAll('person').length, 0, 'zero person records loaded'); }); test('unloading all records also updates record array from peekAll()', function (assert) { @@ -373,12 +373,12 @@ module('integration/unload - Unloading Records', function (hooks) { }); let all = store.peekAll('person'); - assert.strictEqual(all.get('length'), 2); + assert.strictEqual(all.length, 2); run(function () { store.unloadAll('person'); }); - assert.strictEqual(all.get('length'), 0); + assert.strictEqual(all.length, 0); }); function makeBoatOneForPersonOne() { @@ -423,8 +423,8 @@ module('integration/unload - Unloading Records', function (hooks) { assert.notStrictEqual(store.peekRecord('boat', '1'), null); // ensure the relationship was established (we reach through the async proxy here) - let peopleBoats = await person.get('boats'); - let boatPerson = await boat.get('person'); + let peopleBoats = await person.boats; + let boatPerson = await boat.person; assert.strictEqual(relationshipState.canonicalState.length, 1, 'canonical member size should be 1'); assert.strictEqual(relationshipState.members.size, 1, 'members size should be 1'); @@ -492,8 +492,8 @@ module('integration/unload - Unloading Records', function (hooks) { assert.notStrictEqual(store.peekRecord('boat', '1'), null); // ensure the relationship was established (we reach through the async proxy here) - let peopleBoats = run(() => person.get('boats.content')); - let boatPerson = run(() => boat.get('person.content')); + let peopleBoats = run(() => person.boats.content); + let boatPerson = run(() => boat.person.content); assert.strictEqual(relationshipState.canonicalState.length, 1, 'canonical member size should be 1'); assert.strictEqual(relationshipState.members.size, 1, 'members size should be 1'); @@ -510,7 +510,7 @@ module('integration/unload - Unloading Records', function (hooks) { assert.strictEqual(relationshipState.members.size, 1, 'members size should still be 1'); assert.strictEqual(get(peopleBoats, 'length'), 0, 'Our person thinks they have no boats'); - run(() => person.get('boats')); + run(() => person.boats); store.peekRecord('boat', '1'); @@ -546,8 +546,8 @@ module('integration/unload - Unloading Records', function (hooks) { assert.notStrictEqual(store.peekRecord('boat', '1'), null); // ensure the relationship was established (we reach through the async proxy here) - let peopleBoats = run(() => person.get('boats.content')); - let boatPerson = run(() => boat.get('person.content')); + let peopleBoats = run(() => person.boats.content); + let boatPerson = run(() => boat.person.content); assert.deepEqual(idsFromArr(relationshipState.canonicalState), ['1'], 'canonical member size should be 1'); assert.deepEqual(idsFromArr(relationshipState.currentState), ['1'], 'members size should be 1'); @@ -621,129 +621,74 @@ module('integration/unload - Unloading Records', function (hooks) { assert.strictEqual(relatedPerson, newPerson, 'we have a new related record'); }); - test('Unloading a record twice only schedules destroy once', function (assert) { - let record; - - // populate initial record - run(function () { - record = store.push({ - data: { - type: 'person', - id: '1', - attributes: { - name: 'Adam Sunderland', - }, - }, + test('after unloading a record, the record can be fetched again immediately', async function (assert) { + let resolver; + // stub findRecord + adapter.findRecord = () => { + return new Promise((resolve) => { + resolver = resolve; }); - }); - - const internalModel = record._internalModel; - - run(function () { - store.unloadRecord(record); - store.unloadRecord(record); - internalModel.cancelDestroy(); - }); - - assert.false(internalModel.isDestroyed, 'We cancelled destroy'); - }); - - test('Cancelling destroy leaves the record in the empty state', function (assert) { - let record; + }; // populate initial record - run(function () { - record = store.push({ - data: { - type: 'person', - id: '1', - attributes: { - name: 'Adam Sunderland', - }, + let record = store.push({ + data: { + type: 'person', + id: '1', + attributes: { + name: 'Adam Sunderland', }, - }); - }); - - const internalModel = record._internalModel; - assert.strictEqual(record.currentState.stateName, 'root.loaded.saved', 'We are loaded initially'); - - run(function () { - store.unloadRecord(record); - assert.true(record.isDestroying, 'the record is destroying'); - assert.false(internalModel.isDestroyed, 'the internal model is not destroyed'); - assert.true(internalModel._isDematerializing, 'the internal model is dematerializing'); - internalModel.cancelDestroy(); - assert.true(internalModel.isEmpty, 'We are unloaded after unloadRecord'); - }); - - assert.false(internalModel.isDestroyed, 'the internal model was not destroyed'); - assert.false(internalModel._isDematerializing, 'the internal model is no longer dematerializing'); - assert.true(internalModel.isEmpty, 'We are still unloaded after unloadRecord'); - }); - - test('after unloading a record, the record can be fetched again immediately', function (assert) { - // stub findRecord - adapter.findRecord = () => { - return { - data: { - type: 'person', - id: '1', - attributes: { - name: 'Adam Sunderland', + relationships: { + cars: { + data: [ + { + id: 1, + type: 'car', + }, + ], }, }, - }; - }; - - // populate initial record - let record = run(() => { - return store.push({ - data: { - type: 'person', - id: '1', + }, + included: [ + { + type: 'car', + id: 1, attributes: { - name: 'Adam Sunderland', - }, - relationships: { - cars: { - data: [ - { - id: 1, - type: 'car', - }, - ], - }, + make: 'jeep', + model: 'wrangler', }, }, - included: [ - { - type: 'car', - id: 1, - attributes: { - make: 'jeep', - model: 'wrangler', - }, - }, - ], - }); + ], }); + store.DISABLE_WAITER = true; - const internalModel = record._internalModel; assert.strictEqual(record.currentState.stateName, 'root.loaded.saved', 'We are loaded initially'); // we test that we can sync call unloadRecord followed by findRecord - return run(() => { - store.unloadRecord(record); - assert.true(record.isDestroying, 'the record is destroying'); - assert.true(internalModel.isEmpty, 'We are unloaded after unloadRecord'); - return store.findRecord('person', '1').then((newRecord) => { - assert.strictEqual(internalModel, newRecord._internalModel, 'the old internalModel is reused'); - assert.strictEqual(newRecord.currentState.stateName, 'root.loaded.saved', 'We are loaded after findRecord'); - }); + const identifier = recordIdentifierFor(record); + store.unloadRecord(record); + const promise = store.findRecord('person', '1'); + assert.true(record.isDestroying, 'the record is destroying'); + + await settled(); + assert.strictEqual(store.peekRecord('person', '1'), null, 'We are unloaded after unloadRecord'); + + resolver({ + data: { + type: 'person', + id: '1', + attributes: { + name: 'Adam Sunderland', + }, + }, }); + const newRecord = await promise; + const newIdentifier = recordIdentifierFor(newRecord); + assert.notStrictEqual(identifier, newIdentifier, 'the identifier is not reused'); + assert.strictEqual(newRecord.currentState.stateName, 'root.loaded.saved', 'We are loaded after findRecord'); }); - test('after unloading a record, the record can be fetched again immediately (purge relationship)', function (assert) { + test('after unloading a record, the record can be fetched again immediately (purge relationship)', async function (assert) { // stub findRecord adapter.findRecord = () => { return { @@ -763,58 +708,54 @@ module('integration/unload - Unloading Records', function (hooks) { }; // populate initial record - let record = run(() => { - return store.push({ - data: { - type: 'person', + let record = store.push({ + data: { + type: 'person', + id: '1', + attributes: { + name: 'Adam Sunderland', + }, + relationships: { + cars: { + data: [ + { + id: '1', + type: 'car', + }, + ], + }, + }, + }, + included: [ + { + type: 'car', id: '1', attributes: { - name: 'Adam Sunderland', - }, - relationships: { - cars: { - data: [ - { - id: '1', - type: 'car', - }, - ], - }, + make: 'jeep', + model: 'wrangler', }, }, - included: [ - { - type: 'car', - id: '1', - attributes: { - make: 'jeep', - model: 'wrangler', - }, - }, - ], - }); + ], }); - const internalModel = record._internalModel; + let identifier = recordIdentifierFor(record); + let recordData = store._instanceCache.getRecordData(identifier); assert.strictEqual(record.currentState.stateName, 'root.loaded.saved', 'We are loaded initially'); // we test that we can sync call unloadRecord followed by findRecord - return run(() => { - assert.strictEqual(record.get('cars.firstObject.make'), 'jeep'); - store.unloadRecord(record); - assert.true(record.isDestroying, 'the record is destroying'); - assert.true(internalModel.isEmpty, 'Expected the previous internal model tobe unloaded'); - - return store.findRecord('person', '1').then((record) => { - assert.strictEqual(record.get('cars.length'), 0, 'Expected relationship to be cleared by the new push'); - assert.strictEqual(internalModel, record._internalModel, 'the old internalModel is reused'); - assert.strictEqual( - record.currentState.stateName, - 'root.loaded.saved', - 'Expected the NEW internal model to be loaded' - ); - }); - }); + assert.strictEqual(record.get('cars.firstObject.make'), 'jeep'); + store.unloadRecord(record); + assert.true(record.isDestroying, 'the record is destroying'); + assert.true(recordData.isEmpty(), 'Expected the previous data to be unloaded'); + + const recordAgain = await store.findRecord('person', '1'); + assert.strictEqual(recordAgain.cars.length, 0, 'Expected relationship to be cleared by the new push'); + assert.notStrictEqual(identifier, recordIdentifierFor(recordAgain), 'the old identifier is not reused'); + assert.strictEqual( + record.currentState.stateName, + 'root.loaded.saved', + 'Expected the NEW internal model to be loaded' + ); }); test('after unloading a record, the record can be fetched again immediately (with relationships)', function (assert) { @@ -856,22 +797,23 @@ module('integration/unload - Unloading Records', function (hooks) { }); }); - const internalModel = record._internalModel; + let identifier = recordIdentifierFor(record); + let recordData = store._instanceCache.getRecordData(identifier); const bike = store.peekRecord('bike', '1'); assert.strictEqual(record.currentState.stateName, 'root.loaded.saved', 'We are loaded initially'); - assert.strictEqual(record.get('bike.name'), 'mr bike'); + assert.strictEqual(record.bike.name, 'mr bike'); // we test that we can sync call unloadRecord followed by findRecord let wait = run(() => { store.unloadRecord(record); assert.true(record.isDestroying, 'the record is destroying'); assert.false(record.isDestroyed, 'the record is NOT YET destroyed'); - assert.true(internalModel.isEmpty, 'We are unloaded after unloadRecord'); + assert.true(recordData.isEmpty(), 'We are unloaded after unloadRecord'); let wait = store.findRecord('person', '1').then((newRecord) => { assert.false(record.isDestroyed, 'the record is NOT YET destroyed'); - assert.strictEqual(newRecord.get('bike'), bike, 'the newRecord should retain knowledge of the bike'); + assert.strictEqual(newRecord.bike, bike, 'the newRecord should retain knowledge of the bike'); }); assert.false(record.isDestroyed, 'the record is NOT YET destroyed'); @@ -911,13 +853,14 @@ module('integration/unload - Unloading Records', function (hooks) { }); }); - let internalModel = record._internalModel; + let identifier = recordIdentifierFor(record); + let recordData = store._instanceCache.getRecordData(identifier); assert.strictEqual(record.currentState.stateName, 'root.loaded.saved', 'We are loaded initially'); run(function () { store.unloadRecord(record); assert.true(record.isDestroying, 'the record is destroying'); - assert.true(internalModel.isEmpty, 'We are unloaded after unloadRecord'); + assert.true(recordData.isEmpty(), 'We are unloaded after unloadRecord'); }); run(function () { @@ -925,7 +868,6 @@ module('integration/unload - Unloading Records', function (hooks) { }); record = store.peekRecord('person', '1'); - internalModel = record._internalModel; assert.strictEqual(record.currentState.stateName, 'root.loaded.saved', 'We are loaded after findRecord'); }); @@ -991,16 +933,16 @@ module('integration/unload - Unloading Records', function (hooks) { }); let adam = store.peekRecord('person', 1); - assert.strictEqual(adam.get('cars.length'), 0, 'cars hasMany starts off empty'); + assert.strictEqual(adam.cars.length, 0, 'cars hasMany starts off empty'); run(() => pushCar()); - assert.strictEqual(adam.get('cars.length'), 1, 'pushing car setups inverse relationship'); + assert.strictEqual(adam.cars.length, 1, 'pushing car setups inverse relationship'); - run(() => adam.get('cars.firstObject').unloadRecord()); - assert.strictEqual(adam.get('cars.length'), 0, 'unloading car cleaned up hasMany'); + run(() => adam.cars.firstObject.unloadRecord()); + assert.strictEqual(adam.cars.length, 0, 'unloading car cleaned up hasMany'); run(() => pushCar()); - assert.strictEqual(adam.get('cars.length'), 1, 'pushing car again setups inverse relationship'); + assert.strictEqual(adam.cars.length, 1, 'pushing car again setups inverse relationship'); }); test('1:1 sync unload', function (assert) { @@ -1030,12 +972,12 @@ module('integration/unload - Unloading Records', function (hooks) { let person = store.peekRecord('person', 1); let house = store.peekRecord('house', 2); - assert.strictEqual(person.get('house.id'), '2', 'initially relationship established lhs'); - assert.strictEqual(house.get('person.id'), '1', 'initially relationship established rhs'); + assert.strictEqual(person.house.id, '2', 'initially relationship established lhs'); + assert.strictEqual(house.person.id, '1', 'initially relationship established rhs'); run(() => house.unloadRecord()); - assert.strictEqual(person.get('house'), null, 'unloading acts as a delete for sync relationships'); + assert.strictEqual(person.house, null, 'unloading acts as a delete for sync relationships'); assert.strictEqual(store.peekRecord('house', '2'), null, 'unloaded record gone from store'); house = run(() => @@ -1048,8 +990,8 @@ module('integration/unload - Unloading Records', function (hooks) { ); assert.notStrictEqual(store.peekRecord('house', '2'), null, 'unloaded record can be restored'); - assert.strictEqual(person.get('house'), null, 'restoring unloaded record does not restore relationship'); - assert.strictEqual(house.get('person'), null, 'restoring unloaded record does not restore relationship'); + assert.strictEqual(person.house, null, 'restoring unloaded record does not restore relationship'); + assert.strictEqual(house.person, null, 'restoring unloaded record does not restore relationship'); run(() => store.push({ @@ -1068,8 +1010,8 @@ module('integration/unload - Unloading Records', function (hooks) { }) ); - assert.strictEqual(person.get('house.id'), '2', 'after unloading, relationship can be restored'); - assert.strictEqual(house.get('person.id'), '1', 'after unloading, relationship can be restored'); + assert.strictEqual(person.house.id, '2', 'after unloading, relationship can be restored'); + assert.strictEqual(house.person.id, '1', 'after unloading, relationship can be restored'); }); test('1:many sync unload 1 side', function (assert) { @@ -1109,19 +1051,19 @@ module('integration/unload - Unloading Records', function (hooks) { let person = store.peekRecord('person', 1); let car2 = store.peekRecord('car', 2); let car3 = store.peekRecord('car', 3); - let cars = person.get('cars'); + let cars = person.cars; assert.false(cars.isDestroyed, 'ManyArray not destroyed'); - assert.deepEqual(person.get('cars').mapBy('id'), ['2', '3'], 'initialy relationship established lhs'); - assert.strictEqual(car2.get('person.id'), '1', 'initially relationship established rhs'); - assert.strictEqual(car3.get('person.id'), '1', 'initially relationship established rhs'); + assert.deepEqual(person.cars.mapBy('id'), ['2', '3'], 'initialy relationship established lhs'); + assert.strictEqual(car2.person.id, '1', 'initially relationship established rhs'); + assert.strictEqual(car3.person.id, '1', 'initially relationship established rhs'); run(() => person.unloadRecord()); assert.strictEqual(store.peekRecord('person', '1'), null, 'unloaded record gone from store'); - assert.strictEqual(car2.get('person'), null, 'unloading acts as delete for sync relationships'); - assert.strictEqual(car3.get('person'), null, 'unloading acts as delete for sync relationships'); + assert.strictEqual(car2.person, null, 'unloading acts as delete for sync relationships'); + assert.strictEqual(car3.person, null, 'unloading acts as delete for sync relationships'); assert.true(cars.isDestroyed, 'ManyArray destroyed'); person = run(() => @@ -1134,9 +1076,9 @@ module('integration/unload - Unloading Records', function (hooks) { ); assert.notStrictEqual(store.peekRecord('person', '1'), null, 'unloaded record can be restored'); - assert.deepEqual(person.get('cars').mapBy('id'), [], 'restoring unloaded record does not restore relationship'); - assert.strictEqual(car2.get('person'), null, 'restoring unloaded record does not restore relationship'); - assert.strictEqual(car3.get('person'), null, 'restoring unloaded record does not restore relationship'); + assert.deepEqual(person.cars.mapBy('id'), [], 'restoring unloaded record does not restore relationship'); + assert.strictEqual(car2.person, null, 'restoring unloaded record does not restore relationship'); + assert.strictEqual(car3.person, null, 'restoring unloaded record does not restore relationship'); run(() => store.push({ @@ -1161,9 +1103,9 @@ module('integration/unload - Unloading Records', function (hooks) { }) ); - assert.strictEqual(car2.get('person.id'), '1', 'after unloading, relationship can be restored'); - assert.strictEqual(car3.get('person.id'), '1', 'after unloading, relationship can be restored'); - assert.deepEqual(person.get('cars').mapBy('id'), ['2', '3'], 'after unloading, relationship can be restored'); + assert.strictEqual(car2.person.id, '1', 'after unloading, relationship can be restored'); + assert.strictEqual(car3.person.id, '1', 'after unloading, relationship can be restored'); + assert.deepEqual(person.cars.mapBy('id'), ['2', '3'], 'after unloading, relationship can be restored'); }); test('1:many sync unload many side', function (assert) { @@ -1203,20 +1145,20 @@ module('integration/unload - Unloading Records', function (hooks) { let person = store.peekRecord('person', 1); let car2 = store.peekRecord('car', 2); let car3 = store.peekRecord('car', 3); - let cars = person.get('cars'); + let cars = person.cars; assert.false(cars.isDestroyed, 'ManyArray not destroyed'); - assert.deepEqual(person.get('cars').mapBy('id'), ['2', '3'], 'initialy relationship established lhs'); - assert.strictEqual(car2.get('person.id'), '1', 'initially relationship established rhs'); - assert.strictEqual(car3.get('person.id'), '1', 'initially relationship established rhs'); + assert.deepEqual(person.cars.mapBy('id'), ['2', '3'], 'initialy relationship established lhs'); + assert.strictEqual(car2.person.id, '1', 'initially relationship established rhs'); + assert.strictEqual(car3.person.id, '1', 'initially relationship established rhs'); run(() => car2.unloadRecord()); assert.strictEqual(store.peekRecord('car', '2'), null, 'unloaded record gone from store'); assert.false(cars.isDestroyed, 'ManyArray not destroyed'); - assert.deepEqual(person.get('cars').mapBy('id'), ['3'], 'unload sync relationship acts as delete'); - assert.strictEqual(car3.get('person.id'), '1', 'unloading one of a sync hasMany does not affect the rest'); + assert.deepEqual(person.cars.mapBy('id'), ['3'], 'unload sync relationship acts as delete'); + assert.strictEqual(car3.person.id, '1', 'unloading one of a sync hasMany does not affect the rest'); car2 = run(() => store.push({ @@ -1228,8 +1170,8 @@ module('integration/unload - Unloading Records', function (hooks) { ); assert.notStrictEqual(store.peekRecord('car', '2'), null, 'unloaded record can be restored'); - assert.deepEqual(person.get('cars').mapBy('id'), ['3'], 'restoring unloaded record does not restore relationship'); - assert.strictEqual(car2.get('person'), null, 'restoring unloaded record does not restore relationship'); + assert.deepEqual(person.cars.mapBy('id'), ['3'], 'restoring unloaded record does not restore relationship'); + assert.strictEqual(car2.person, null, 'restoring unloaded record does not restore relationship'); run(() => store.push({ @@ -1254,8 +1196,8 @@ module('integration/unload - Unloading Records', function (hooks) { }) ); - assert.strictEqual(car2.get('person.id'), '1', 'after unloading, relationship can be restored'); - assert.deepEqual(person.get('cars').mapBy('id'), ['2', '3'], 'after unloading, relationship can be restored'); + assert.strictEqual(car2.person.id, '1', 'after unloading, relationship can be restored'); + assert.deepEqual(person.cars.mapBy('id'), ['2', '3'], 'after unloading, relationship can be restored'); }); test('many:many sync unload', function (assert) { @@ -1316,13 +1258,13 @@ module('integration/unload - Unloading Records', function (hooks) { let person2 = store.peekRecord('person', 2); let group3 = store.peekRecord('group', 3); let group4 = store.peekRecord('group', 4); - let p2groups = person2.get('groups'); - let g3people = group3.get('people'); + let p2groups = person2.groups; + let g3people = group3.people; - assert.deepEqual(person1.get('groups').mapBy('id'), ['3', '4'], 'initially established relationship lhs'); - assert.deepEqual(person2.get('groups').mapBy('id'), ['3', '4'], 'initially established relationship lhs'); - assert.deepEqual(group3.get('people').mapBy('id'), ['1', '2'], 'initially established relationship lhs'); - assert.deepEqual(group4.get('people').mapBy('id'), ['1', '2'], 'initially established relationship lhs'); + assert.deepEqual(person1.groups.mapBy('id'), ['3', '4'], 'initially established relationship lhs'); + assert.deepEqual(person2.groups.mapBy('id'), ['3', '4'], 'initially established relationship lhs'); + assert.deepEqual(group3.people.mapBy('id'), ['1', '2'], 'initially established relationship lhs'); + assert.deepEqual(group4.people.mapBy('id'), ['1', '2'], 'initially established relationship lhs'); assert.false(p2groups.isDestroyed, 'groups is not destroyed'); assert.false(g3people.isDestroyed, 'people is not destroyed'); @@ -1333,12 +1275,12 @@ module('integration/unload - Unloading Records', function (hooks) { assert.false(g3people.isDestroyed, 'people (inverse) is not destroyed'); assert.deepEqual( - person1.get('groups').mapBy('id'), + person1.groups.mapBy('id'), ['3', '4'], 'unloaded record in many:many does not affect inverse of inverse' ); - assert.deepEqual(group3.get('people').mapBy('id'), ['1'], 'unloading acts as delete for sync relationships'); - assert.deepEqual(group4.get('people').mapBy('id'), ['1'], 'unloading acts as delete for sync relationships'); + assert.deepEqual(group3.people.mapBy('id'), ['1'], 'unloading acts as delete for sync relationships'); + assert.deepEqual(group4.people.mapBy('id'), ['1'], 'unloading acts as delete for sync relationships'); assert.strictEqual(store.peekRecord('person', '2'), null, 'unloading removes record from store'); @@ -1352,17 +1294,9 @@ module('integration/unload - Unloading Records', function (hooks) { ); assert.notStrictEqual(store.peekRecord('person', '2'), null, 'unloaded record can be restored'); - assert.deepEqual(person2.get('groups').mapBy('id'), [], 'restoring unloaded record does not restore relationship'); - assert.deepEqual( - group3.get('people').mapBy('id'), - ['1'], - 'restoring unloaded record does not restore relationship' - ); - assert.deepEqual( - group4.get('people').mapBy('id'), - ['1'], - 'restoring unloaded record does not restore relationship' - ); + assert.deepEqual(person2.groups.mapBy('id'), [], 'restoring unloaded record does not restore relationship'); + assert.deepEqual(group3.people.mapBy('id'), ['1'], 'restoring unloaded record does not restore relationship'); + assert.deepEqual(group4.people.mapBy('id'), ['1'], 'restoring unloaded record does not restore relationship'); run(() => store.push({ @@ -1387,9 +1321,9 @@ module('integration/unload - Unloading Records', function (hooks) { }) ); - assert.deepEqual(person2.get('groups').mapBy('id'), ['3', '4'], 'after unloading, relationship can be restored'); - assert.deepEqual(group3.get('people').mapBy('id'), ['1', '2'], 'after unloading, relationship can be restored'); - assert.deepEqual(group4.get('people').mapBy('id'), ['1', '2'], 'after unloading, relationship can be restored'); + assert.deepEqual(person2.groups.mapBy('id'), ['3', '4'], 'after unloading, relationship can be restored'); + assert.deepEqual(group3.people.mapBy('id'), ['1', '2'], 'after unloading, relationship can be restored'); + assert.deepEqual(group4.people.mapBy('id'), ['1', '2'], 'after unloading, relationship can be restored'); }); test('1:1 async unload', function (assert) { @@ -1427,11 +1361,10 @@ module('integration/unload - Unloading Records', function (hooks) { let mortgage; return run(() => - person - .get('mortgage') + person.mortgage .then((asyncRecord) => { mortgage = asyncRecord; - return mortgage.get('person'); + return mortgage.person; }) .then(() => { assert.strictEqual(mortgage.belongsTo('person').id(), '1', 'initially relationship established lhs'); @@ -1441,7 +1374,7 @@ module('integration/unload - Unloading Records', function (hooks) { assert.strictEqual(person.belongsTo('mortgage').id(), '2', 'unload async is not treated as delete'); - return person.get('mortgage'); + return person.mortgage; }) .then((refetchedMortgage) => { assert.notEqual(mortgage, refetchedMortgage, 'the previously loaded record is not reused'); @@ -1516,12 +1449,11 @@ module('integration/unload - Unloading Records', function (hooks) { let boats, boat2, boat3; return run(() => - person - .get('boats') + person.boats .then((asyncRecords) => { boats = asyncRecords; [boat2, boat3] = boats.toArray(); - return all([boat2, boat3].map((b) => b.get('person'))); + return all([boat2, boat3].map((b) => b.person)); }) .then(() => { assert.deepEqual(person.hasMany('boats').ids(), ['2', '3'], 'initially relationship established lhs'); @@ -1536,7 +1468,7 @@ module('integration/unload - Unloading Records', function (hooks) { assert.strictEqual(boat2.belongsTo('person').id(), '1', 'unload async is not treated as delete'); assert.strictEqual(boat3.belongsTo('person').id(), '1', 'unload async is not treated as delete'); - return boat2.get('person'); + return boat2.person; }) .then((refetchedPerson) => { assert.notEqual(person, refetchedPerson, 'the previously loaded record is not reused'); @@ -1727,79 +1659,70 @@ module('integration/unload - Unloading Records', function (hooks) { assert.strictEqual(findManyCalls, 2, 'findMany called as expected'); }); - test('1 sync : 1 async unload sync side', function (assert) { - run(() => - store.push({ - data: { - id: 1, - type: 'person', - relationships: { - favoriteBook: { - data: { - id: 2, - type: 'book', - }, + test('1 sync : 1 async unload sync side', async function (assert) { + let person = store.push({ + data: { + id: '1', + type: 'person', + relationships: { + favoriteBook: { + data: { + id: '2', + type: 'book', }, }, }, - included: [ - { - id: 2, - type: 'book', - }, - ], - }) - ); + }, + included: [ + { + id: '2', + type: 'book', + }, + ], + }); - let person = store.peekRecord('person', 1); - let book = store.peekRecord('book', 2); + let book = store.peekRecord('book', '2'); + await book.person; - return book.get('person').then(() => { - assert.strictEqual(person.get('favoriteBook.id'), '2', 'initially relationship established lhs'); - assert.strictEqual(book.belongsTo('person').id(), '1', 'initially relationship established rhs'); + assert.strictEqual(person.favoriteBook.id, '2', 'initially relationship established lhs'); + assert.strictEqual(book.belongsTo('person').id(), '1', 'initially relationship established rhs'); - run(() => book.unloadRecord()); + book.unloadRecord(); + await settled(); - assert.strictEqual(person.get('book'), undefined, 'unloading acts as a delete for sync relationships'); - assert.strictEqual(store.peekRecord('book', '2'), null, 'unloaded record gone from store'); + assert.strictEqual(person.book, undefined, 'unloading acts as a delete for sync relationships'); + assert.strictEqual(store.peekRecord('book', '2'), null, 'unloaded record gone from store'); - book = run(() => - store.push({ - data: { - id: 2, - type: 'book', - }, - }) - ); - - assert.notStrictEqual(store.peekRecord('book', '2'), null, 'unloaded record can be restored'); - assert.strictEqual(person.get('book'), undefined, 'restoring unloaded record does not restore relationship'); - assert.strictEqual( - book.belongsTo('person').id(), - null, - 'restoring unloaded record does not restore relationship' - ); - - run(() => - store.push({ - data: { - id: 2, - type: 'book', - relationships: { - person: { - data: { - id: 1, - type: 'person', - }, - }, + store.push({ + data: { + id: '2', + type: 'book', + }, + }); + + book = store.peekRecord('book', '2'); + assert.notStrictEqual(book, null, 'unloaded record can be restored'); + assert.strictEqual(person.book, undefined, 'restoring unloaded record does not restore relationship'); + assert.strictEqual(book.belongsTo('person').id(), null, 'restoring unloaded record does not restore relationship'); + + store.push({ + data: { + id: '2', + type: 'book', + relationships: { + person: { + data: { + id: 1, + type: 'person', }, }, - }) - ); - - assert.strictEqual(person.get('favoriteBook.id'), '2', 'after unloading, relationship can be restored'); - assert.strictEqual(book.get('person.id'), '1', 'after unloading, relationship can be restored'); + }, + }, }); + + const bookPerson = await book.person; + assert.strictEqual(person.favoriteBook.id, '2', 'after unloading, relationship can be restored'); + assert.strictEqual(bookPerson?.id, '1', 'after unloading, relationship can be restored'); }); test('1 sync : 1 async unload async side', function (assert) { @@ -1845,23 +1768,22 @@ module('integration/unload - Unloading Records', function (hooks) { let book = store.peekRecord('book', 2); return run(() => - book - .get('person') + book.person .then(() => { - assert.strictEqual(person.get('favoriteBook.id'), '2', 'initially relationship established lhs'); + assert.strictEqual(person.favoriteBook.id, '2', 'initially relationship established lhs'); assert.strictEqual(book.belongsTo('person').id(), '1', 'initially relationship established rhs'); run(() => person.unloadRecord()); assert.strictEqual(book.belongsTo('person').id(), '1', 'unload async is not treated as delete'); - return book.get('person'); + return book.person; }) .then((refetchedPerson) => { assert.notEqual(person, refetchedPerson, 'the previously loaded record is not reused'); assert.strictEqual(book.belongsTo('person').id(), '1', 'unload async is not treated as delete'); - assert.strictEqual(refetchedPerson.get('favoriteBook.id'), '2', 'unload async is not treated as delete'); + assert.strictEqual(refetchedPerson.favoriteBook.id, '2', 'unload async is not treated as delete'); assert.strictEqual(findRecordCalls, 1); }) ); @@ -1904,10 +1826,10 @@ module('integration/unload - Unloading Records', function (hooks) { let person = store.peekRecord('person', 1); let spoon2 = store.peekRecord('spoon', 2); let spoon3 = store.peekRecord('spoon', 3); - let spoons = person.get('favoriteSpoons'); + let spoons = person.favoriteSpoons; assert.false(spoons.isDestroyed, 'ManyArray not destroyed'); - assert.deepEqual(person.get('favoriteSpoons').mapBy('id'), ['2', '3'], 'initialy relationship established lhs'); + assert.deepEqual(person.favoriteSpoons.mapBy('id'), ['2', '3'], 'initialy relationship established lhs'); assert.strictEqual(spoon2.belongsTo('person').id(), '1', 'initially relationship established rhs'); assert.strictEqual(spoon3.belongsTo('person').id(), '1', 'initially relationship established rhs'); @@ -1916,7 +1838,7 @@ module('integration/unload - Unloading Records', function (hooks) { assert.strictEqual(store.peekRecord('spoon', '2'), null, 'unloaded record gone from store'); assert.false(spoons.isDestroyed, 'ManyArray not destroyed'); - assert.deepEqual(person.get('favoriteSpoons').mapBy('id'), ['3'], 'unload sync relationship acts as delete'); + assert.deepEqual(person.favoriteSpoons.mapBy('id'), ['3'], 'unload sync relationship acts as delete'); assert.strictEqual( spoon3.belongsTo('person').id(), '1', @@ -1934,7 +1856,7 @@ module('integration/unload - Unloading Records', function (hooks) { assert.notStrictEqual(store.peekRecord('spoon', '2'), null, 'unloaded record can be restored'); assert.deepEqual( - person.get('favoriteSpoons').mapBy('id'), + person.favoriteSpoons.mapBy('id'), ['3'], 'restoring unloaded record does not restore relationship' ); @@ -1968,11 +1890,7 @@ module('integration/unload - Unloading Records', function (hooks) { ); assert.strictEqual(spoon2.belongsTo('person').id(), '1', 'after unloading, relationship can be restored'); - assert.deepEqual( - person.get('favoriteSpoons').mapBy('id'), - ['2', '3'], - 'after unloading, relationship can be restored' - ); + assert.deepEqual(person.favoriteSpoons.mapBy('id'), ['2', '3'], 'after unloading, relationship can be restored'); }); test('1 async : many sync unload async side', async function (assert) { @@ -1987,65 +1905,64 @@ module('integration/unload - Unloading Records', function (hooks) { return { data: { - id: 1, + id: '1', type: 'person', }, }; }; - let person = run(() => - store.push({ - data: { - id: 1, - type: 'person', - relationships: { - favoriteSpoons: { - data: [ - { - id: 2, - type: 'spoon', - }, - { - id: 3, - type: 'spoon', - }, - ], - }, + let person = store.push({ + data: { + id: 1, + type: 'person', + relationships: { + favoriteSpoons: { + data: [ + { + id: '2', + type: 'spoon', + }, + { + id: '3', + type: 'spoon', + }, + ], }, }, - included: [ - { - id: 2, - type: 'spoon', - }, - { - id: 3, - type: 'spoon', - }, - ], - }) - ); - let spoon2 = store.peekRecord('spoon', 2); - let spoon3 = store.peekRecord('spoon', 3); - let spoons = person.get('favoriteSpoons'); + }, + included: [ + { + id: '2', + type: 'spoon', + }, + { + id: '3', + type: 'spoon', + }, + ], + }); + let spoon2 = store.peekRecord('spoon', '2'); + let spoon3 = store.peekRecord('spoon', '3'); + let spoons = person.favoriteSpoons; - assert.deepEqual(person.get('favoriteSpoons').mapBy('id'), ['2', '3'], 'initially relationship established lhs'); + assert.deepEqual(person.favoriteSpoons.mapBy('id'), ['2', '3'], 'initially relationship established lhs'); assert.strictEqual(spoon2.belongsTo('person').id(), '1', 'initially relationship established rhs'); assert.strictEqual(spoon3.belongsTo('person').id(), '1', 'initially relationship established rhs'); assert.false(spoons.isDestroyed, 'ManyArray is not destroyed'); - run(() => person.unloadRecord()); + person.unloadRecord(); + await settled(); assert.true(spoons.isDestroyed, 'ManyArray is destroyed when 1 side is unloaded'); assert.strictEqual(spoon2.belongsTo('person').id(), '1', 'unload async is not treated as delete'); assert.strictEqual(spoon3.belongsTo('person').id(), '1', 'unload async is not treated as delete'); - const refetchedPerson = await spoon2.get('person'); + const refetchedPerson = await spoon2.person; assert.notEqual(person, refetchedPerson, 'the previously loaded record is not reused'); - assert.deepEqual(person.get('favoriteSpoons').mapBy('id'), ['2', '3'], 'unload async is not treated as delete'); + assert.deepEqual(refetchedPerson.favoriteSpoons.mapBy('id'), ['2', '3'], 'unload async is not treated as delete'); assert.strictEqual(spoon2.belongsTo('person').id(), '1', 'unload async is not treated as delete'); assert.strictEqual(spoon3.belongsTo('person').id(), '1', 'unload async is not treated as delete'); @@ -2101,8 +2018,8 @@ module('integration/unload - Unloading Records', function (hooks) { const [show2, show3] = shows.toArray(); assert.deepEqual(person.hasMany('favoriteShows').ids(), ['2', '3'], 'initially relationship established lhs'); - assert.strictEqual(show2.get('person.id'), '1', 'initially relationship established rhs'); - assert.strictEqual(show3.get('person.id'), '1', 'initially relationship established rhs'); + assert.strictEqual(show2.person.id, '1', 'initially relationship established rhs'); + assert.strictEqual(show3.person.id, '1', 'initially relationship established rhs'); assert.deepEqual(shows.mapBy('id'), ['2', '3'], 'many array is initially set up correctly'); show2.unloadRecord(); @@ -2123,7 +2040,7 @@ module('integration/unload - Unloading Records', function (hooks) { assert.strictEqual(findManyCalls, 2, 'findMany called as expected'); }); - test('1 sync : many async unload sync side', function (assert) { + test('1 sync : many async unload sync side', async function (assert) { let findManyCalls = 0; adapter.coalesceFindRequests = true; @@ -2147,111 +2064,91 @@ module('integration/unload - Unloading Records', function (hooks) { }; }; - let person = run(() => - store.push({ - data: { - id: 1, - type: 'person', - relationships: { - favoriteShows: { - data: [ - { - id: 2, - type: 'show', - }, - { - id: 3, - type: 'show', - }, - ], - }, + let person = store.push({ + data: { + id: '1', + type: 'person', + relationships: { + favoriteShows: { + data: [ + { + id: '2', + type: 'show', + }, + { + id: '3', + type: 'show', + }, + ], }, }, - }) - ); - + }, + }); let shows, show2, show3; - return run(() => - person - .get('favoriteShows') - .then((asyncRecords) => { - shows = asyncRecords; - [show2, show3] = shows.toArray(); + const asyncRecords = await person.favoriteShows; + shows = asyncRecords; + [show2, show3] = shows.toArray(); - assert.deepEqual(person.hasMany('favoriteShows').ids(), ['2', '3'], 'initially relationship established lhs'); - assert.strictEqual(show2.get('person.id'), '1', 'initially relationship established rhs'); - assert.strictEqual(show3.get('person.id'), '1', 'initially relationship established rhs'); - assert.deepEqual(shows.mapBy('id'), ['2', '3'], 'many array is initially set up correctly'); + assert.deepEqual(person.hasMany('favoriteShows').ids(), ['2', '3'], 'initially relationship established lhs'); + assert.strictEqual(show2.person.id, '1', 'initially relationship established rhs'); + assert.strictEqual(show3.person.id, '1', 'initially relationship established rhs'); + assert.deepEqual(shows.mapBy('id'), ['2', '3'], 'many array is initially set up correctly'); - run(() => person.unloadRecord()); + person.unloadRecord(); + await settled(); - assert.strictEqual(store.peekRecord('person', '1'), null, 'unloaded record gone from store'); + assert.strictEqual(store.peekRecord('person', '1'), null, 'unloaded record gone from store'); - assert.true(shows.isDestroyed, 'previous manyarray immediately destroyed'); - assert.strictEqual(show2.get('person.id'), undefined, 'unloading acts as delete for sync relationships'); - assert.strictEqual(show3.get('person.id'), undefined, 'unloading acts as delete for sync relationships'); + assert.true(shows.isDestroyed, 'previous manyarray immediately destroyed'); + assert.strictEqual(show2.person?.id, undefined, 'unloading acts as delete for sync relationships'); + assert.strictEqual(show3.person?.id, undefined, 'unloading acts as delete for sync relationships'); - person = run(() => - store.push({ - data: { - id: 1, - type: 'person', + person = store.push({ + data: { + id: '1', + type: 'person', + }, + }); + + assert.notStrictEqual(store.peekRecord('person', '1'), null, 'unloaded record can be restored'); + assert.deepEqual( + person.hasMany('favoriteShows').ids(), + [], + 'restoring unloaded record does not restore relationship' + ); + assert.strictEqual(show2.person?.id, undefined, 'restoring unloaded record does not restore relationship'); + assert.strictEqual(show3.person?.id, undefined, 'restoring unloaded record does not restore relationship'); + + store.push({ + data: { + id: '1', + type: 'person', + relationships: { + favoriteShows: { + data: [ + { + id: '2', + type: 'show', }, - }) - ); - - assert.notStrictEqual(store.peekRecord('person', '1'), null, 'unloaded record can be restored'); - assert.deepEqual( - person.hasMany('favoriteShows').ids(), - [], - 'restoring unloaded record does not restore relationship' - ); - assert.strictEqual( - show2.get('person.id'), - undefined, - 'restoring unloaded record does not restore relationship' - ); - assert.strictEqual( - show3.get('person.id'), - undefined, - 'restoring unloaded record does not restore relationship' - ); - - run(() => - store.push({ - data: { - id: 1, - type: 'person', - relationships: { - favoriteShows: { - data: [ - { - id: 2, - type: 'show', - }, - { - id: 3, - type: 'show', - }, - ], - }, - }, + { + id: '3', + type: 'show', }, - }) - ); + ], + }, + }, + }, + }); - assert.deepEqual(person.hasMany('favoriteShows').ids(), ['2', '3'], 'relationship can be restored'); + assert.deepEqual(person.hasMany('favoriteShows').ids(), ['2', '3'], 'relationship can be restored'); - return person.get('favoriteShows'); - }) - .then((refetchedShows) => { - assert.notEqual(refetchedShows, shows, 'ManyArray not reused'); - assert.deepEqual(refetchedShows.mapBy('id'), ['2', '3'], 'unload async not treated as a delete'); + const refetchedShows = await person.favoriteShows; - assert.strictEqual(findManyCalls, 1, 'findMany calls as expected'); - }) - ); + assert.notEqual(refetchedShows, shows, 'ManyArray not reused'); + assert.deepEqual(refetchedShows.mapBy('id'), ['2', '3'], 'unload async not treated as a delete'); + + assert.strictEqual(findManyCalls, 1, 'findMany calls as expected'); }); test('unload invalidates link promises', function (assert) { @@ -2312,8 +2209,7 @@ module('integration/unload - Unloading Records', function (hooks) { let boats, boat2, boat3; return run(() => - person - .get('boats') + person.boats .then((asyncRecords) => { boats = asyncRecords; [boat2, boat3] = boats.toArray(); @@ -2326,13 +2222,13 @@ module('integration/unload - Unloading Records', function (hooks) { isUnloaded = true; run(() => { boat2.unloadRecord(); - person.get('boats'); + person.boats; }); assert.deepEqual(boats.mapBy('id'), ['3'], 'unloaded boat is removed from ManyArray'); }) .then(() => { - return run(() => person.get('boats')); + return run(() => person.boats); }) .then((newBoats) => { assert.strictEqual(newBoats.length, 1, 'new ManyArray has only 1 boat after unload'); diff --git a/packages/-ember-data/tests/integration/references/has-many-test.js b/packages/-ember-data/tests/integration/references/has-many-test.js index ade9edae091..459608d8a83 100755 --- a/packages/-ember-data/tests/integration/references/has-many-test.js +++ b/packages/-ember-data/tests/integration/references/has-many-test.js @@ -292,8 +292,8 @@ module('integration/references/has-many', function (hooks) { personsReference.push(data).then(function (records) { assert.ok(records instanceof DS.ManyArray, 'push resolves with the referenced records'); assert.strictEqual(get(records, 'length'), 2); - assert.strictEqual(records.objectAt(0).get('name'), 'Vito'); - assert.strictEqual(records.objectAt(1).get('name'), 'Michael'); + assert.strictEqual(records.objectAt(0).name, 'Vito'); + assert.strictEqual(records.objectAt(1).name, 'Michael'); done(); }); @@ -326,7 +326,7 @@ module('integration/references/has-many', function (hooks) { personsReference.push(data).then(function (records) { assert.ok(records instanceof DS.ManyArray, 'push resolves with the referenced records'); assert.strictEqual(get(records, 'length'), 1); - assert.strictEqual(records.objectAt(0).get('name'), 'Vito'); + assert.strictEqual(records.objectAt(0).name, 'Vito'); done(); }); @@ -384,8 +384,8 @@ module('integration/references/has-many', function (hooks) { personsReference.push(payload).then(function (records) { assert.ok(records instanceof DS.ManyArray, 'push resolves with the referenced records'); assert.strictEqual(get(records, 'length'), 2); - assert.strictEqual(records.objectAt(0).get('name'), 'Vito'); - assert.strictEqual(records.objectAt(1).get('name'), 'Michael'); + assert.strictEqual(records.objectAt(0).name, 'Vito'); + assert.strictEqual(records.objectAt(1).name, 'Michael'); done(); }); @@ -427,8 +427,8 @@ module('integration/references/has-many', function (hooks) { const records = await pushResult; assert.ok(records instanceof DS.ManyArray, 'push resolves with the referenced records'); assert.strictEqual(get(records, 'length'), 2); - assert.strictEqual(records.objectAt(0).get('name'), 'Vito'); - assert.strictEqual(records.objectAt(1).get('name'), 'Michael'); + assert.strictEqual(records.objectAt(0).name, 'Vito'); + assert.strictEqual(records.objectAt(1).name, 'Michael'); }); test('push valid json:api', async function (assert) { @@ -461,8 +461,8 @@ module('integration/references/has-many', function (hooks) { const records = await pushResult; assert.ok(records instanceof DS.ManyArray, 'push resolves with the referenced records'); assert.strictEqual(get(records, 'length'), 2); - assert.strictEqual(records.objectAt(0).get('name'), 'Vito'); - assert.strictEqual(records.objectAt(1).get('name'), 'Michael'); + assert.strictEqual(records.objectAt(0).name, 'Vito'); + assert.strictEqual(records.objectAt(1).name, 'Michael'); }); test('value() returns null when reference is not yet loaded', function (assert) { @@ -613,8 +613,8 @@ module('integration/references/has-many', function (hooks) { personsReference.load({ adapterOptions }).then(function (records) { assert.ok(records instanceof DS.ManyArray, 'push resolves with the referenced records'); assert.strictEqual(get(records, 'length'), 2); - assert.strictEqual(records.objectAt(0).get('name'), 'Vito'); - assert.strictEqual(records.objectAt(1).get('name'), 'Michael'); + assert.strictEqual(records.objectAt(0).name, 'Vito'); + assert.strictEqual(records.objectAt(1).name, 'Michael'); done(); }); @@ -663,8 +663,8 @@ module('integration/references/has-many', function (hooks) { personsReference.load({ adapterOptions }).then(function (records) { assert.ok(records instanceof DS.ManyArray, 'push resolves with the referenced records'); assert.strictEqual(get(records, 'length'), 2); - assert.strictEqual(records.objectAt(0).get('name'), 'Vito'); - assert.strictEqual(records.objectAt(1).get('name'), 'Michael'); + assert.strictEqual(records.objectAt(0).name, 'Vito'); + assert.strictEqual(records.objectAt(1).name, 'Michael'); done(); }); @@ -806,8 +806,8 @@ module('integration/references/has-many', function (hooks) { personsReference.reload({ adapterOptions }).then(function (records) { assert.ok(records instanceof DS.ManyArray, 'push resolves with the referenced records'); assert.strictEqual(get(records, 'length'), 2); - assert.strictEqual(records.objectAt(0).get('name'), 'Vito Coreleone'); - assert.strictEqual(records.objectAt(1).get('name'), 'Michael Coreleone'); + assert.strictEqual(records.objectAt(0).name, 'Vito Coreleone'); + assert.strictEqual(records.objectAt(1).name, 'Michael Coreleone'); done(); }); @@ -872,8 +872,8 @@ module('integration/references/has-many', function (hooks) { .then(function (records) { assert.ok(records instanceof DS.ManyArray, 'push resolves with the referenced records'); assert.strictEqual(get(records, 'length'), 2); - assert.strictEqual(records.objectAt(0).get('name'), 'Vito Coreleone'); - assert.strictEqual(records.objectAt(1).get('name'), 'Michael Coreleone'); + assert.strictEqual(records.objectAt(0).name, 'Vito Coreleone'); + assert.strictEqual(records.objectAt(1).name, 'Michael Coreleone'); done(); }); diff --git a/packages/-ember-data/tests/integration/relationships/belongs-to-test.js b/packages/-ember-data/tests/integration/relationships/belongs-to-test.js index 09728cb189f..90706e1c922 100644 --- a/packages/-ember-data/tests/integration/relationships/belongs-to-test.js +++ b/packages/-ember-data/tests/integration/relationships/belongs-to-test.js @@ -191,7 +191,7 @@ module('integration/relationship/belongs-to BelongsTo Relationships (new-style)' let person = await store.findRecord('person', '1'); let petRequest = store.findRecord('pet', '1'); - let personPetRequest = person.get('bestDog'); + let personPetRequest = person.bestDog; let personPet = await personPetRequest; let pet = await petRequest; @@ -236,31 +236,31 @@ module('integration/relationship/belongs-to BelongsTo Relationships (new-style)' let shen = store.peekRecord('pet', '1'); let pirate = store.peekRecord('pet', '2'); - let bestDog = await chris.get('bestDog'); + let bestDog = await chris.bestDog; - assert.strictEqual(shen.get('bestHuman'), null, 'precond - Shen has no best human'); - assert.strictEqual(pirate.get('bestHuman'), null, 'precond - pirate has no best human'); + assert.strictEqual(shen.bestHuman, null, 'precond - Shen has no best human'); + assert.strictEqual(pirate.bestHuman, null, 'precond - pirate has no best human'); assert.strictEqual(bestDog, null, 'precond - Chris has no best dog'); chris.set('bestDog', shen); - bestDog = await chris.get('bestDog'); + bestDog = await chris.bestDog; - assert.strictEqual(shen.get('bestHuman'), chris, "scene 1 - Chris is Shen's best human"); - assert.strictEqual(pirate.get('bestHuman'), null, 'scene 1 - pirate has no best human'); + assert.strictEqual(shen.bestHuman, chris, "scene 1 - Chris is Shen's best human"); + assert.strictEqual(pirate.bestHuman, null, 'scene 1 - pirate has no best human'); assert.strictEqual(bestDog, shen, "scene 1 - Shen is Chris's best dog"); chris.set('bestDog', pirate); - bestDog = await chris.get('bestDog'); + bestDog = await chris.bestDog; - assert.strictEqual(shen.get('bestHuman'), null, "scene 2 - Chris is no longer Shen's best human"); - assert.strictEqual(pirate.get('bestHuman'), chris, 'scene 2 - pirate now has Chris as best human'); + assert.strictEqual(shen.bestHuman, null, "scene 2 - Chris is no longer Shen's best human"); + assert.strictEqual(pirate.bestHuman, chris, 'scene 2 - pirate now has Chris as best human'); assert.strictEqual(bestDog, pirate, "scene 2 - Pirate is now Chris's best dog"); chris.set('bestDog', null); - bestDog = await chris.get('bestDog'); + bestDog = await chris.bestDog; - assert.strictEqual(shen.get('bestHuman'), null, "scene 3 - Chris remains no longer Shen's best human"); - assert.strictEqual(pirate.get('bestHuman'), null, 'scene 3 - pirate no longer has Chris as best human'); + assert.strictEqual(shen.bestHuman, null, "scene 3 - Chris remains no longer Shen's best human"); + assert.strictEqual(pirate.bestHuman, null, 'scene 3 - pirate no longer has Chris as best human'); assert.strictEqual(bestDog, null, 'scene 3 - Chris has no best dog'); }); }); @@ -336,7 +336,7 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function ); }); - test('returning a null relationship from payload sets the relationship to null on both sides', function (assert) { + test('returning a null relationship from payload sets the relationship to null on both sides', async function (assert) { this.owner.register( 'model:app', Model.extend({ @@ -354,43 +354,43 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); - run(() => { - store.push({ - data: { - id: '1', - type: 'app', - relationships: { - team: { - data: { - id: '1', - type: 'team', - }, + store.push({ + data: { + id: '1', + type: 'app', + relationships: { + team: { + data: { + id: '1', + type: 'team', }, }, }, - included: [ - { - id: '1', - type: 'team', - relationships: { - apps: { - data: [ - { - id: '1', - type: 'app', - }, - ], - }, + }, + included: [ + { + id: '1', + type: 'team', + relationships: { + apps: { + data: [ + { + id: '1', + type: 'app', + }, + ], }, }, - ], - }); + }, + ], }); const app = store.peekRecord('app', '1'); const team = store.peekRecord('team', '1'); - assert.strictEqual(app.get('team.id'), team.get('id'), 'sets team correctly on app'); - assert.deepEqual(team.get('apps').toArray().mapBy('id'), ['1'], 'sets apps correctly on team'); + let appTeam = await app.team; + assert.strictEqual(appTeam.id, team.id, 'sets team correctly on app'); + const apps = await team.apps; + assert.deepEqual(apps.toArray().mapBy('id'), ['1'], 'sets apps correctly on team'); adapter.shouldBackgroundReloadRecord = () => false; adapter.updateRecord = (store, type, snapshot) => { @@ -410,13 +410,11 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function }); }; - return run(() => { - app.set('name', 'Hello'); - return app.save().then(() => { - assert.strictEqual(app.get('team.id'), undefined, 'team removed from app relationship'); - assert.deepEqual(team.get('apps').toArray().mapBy('id'), [], 'app removed from team apps relationship'); - }); - }); + app.set('name', 'Hello'); + await app.save(); + appTeam = await app.team; + assert.strictEqual(appTeam?.id, undefined, 'team removed from app relationship'); + assert.deepEqual(apps.toArray().mapBy('id'), [], 'app removed from team apps relationship'); }); test('The store can materialize a non loaded monomorphic belongsTo association', function (assert) { @@ -466,7 +464,7 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function return run(() => { return store.findRecord('post', 1).then((post) => { - post.get('user'); + post.user; }); }); }); @@ -493,7 +491,7 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function }, }, }); - post.get('user'); + post.user; }); }, `Assertion Failed: Encountered a relationship identifier without an id for the belongsTo relationship 'user' on , expected a json-api identifier but found '{"id":null,"type":"user"}'. Please check your serializer and make sure it is serializing the relationship payload into a JSON API format.`); @@ -514,7 +512,7 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function }, }, }); - post.get('user'); + post.user; }); }, `Assertion Failed: Encountered a relationship identifier without a type for the belongsTo relationship 'user' on , expected a json-api identifier with type 'user' but found '{"id":"1","type":null}'. Please check your serializer and make sure it is serializing the relationship payload into a JSON API format.`); }); @@ -652,7 +650,7 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function message: store.findRecord('post', 1), comment: store.findRecord('comment', 2), }).then((records) => { - assert.strictEqual(records.comment.get('message'), records.message); + assert.strictEqual(records.comment.message, records.message); }); }); }); @@ -760,11 +758,11 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function return store .findRecord('person', 1) .then((person) => { - return person.get('group'); + return person.group; }) .then((group) => { assert.ok(group instanceof Group, 'A group object is loaded'); - assert.strictEqual(group.get('id'), '1', 'It is the group we are expecting'); + assert.strictEqual(group.id, '1', 'It is the group we are expecting'); }); }); }); @@ -812,11 +810,11 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function return run(() => { return store.findRecord('person', 1).then((person) => { - return person.get('seat').then((seat) => { + return person.seat.then((seat) => { // this assertion fails too - // ok(seat.get('person') === person, 'parent relationship should be populated'); + // ok(seat.person === person, 'parent relationship should be populated'); seat.set('person', person); - assert.ok(person.get('seat').then, 'seat should be a PromiseObject'); + assert.ok(person.seat.then, 'seat should be a PromiseObject'); }); }); }); @@ -868,7 +866,7 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function return store .findRecord('person', '1') .then((person) => { - return person.get('group'); + return person.group; }) .then((group) => { assert.strictEqual(group, null, 'group should be null'); @@ -908,7 +906,7 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function let person = store.createRecord('person', { group: groupPromise, }); - assert.strictEqual(person.get('group.content'), group); + assert.strictEqual(person.group.content, group); }); }); @@ -923,7 +921,7 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function igor.set('favouriteMessage', post); - assert.strictEqual(igor.get('favouriteMessage.title'), "Igor's unimaginative blog post"); + assert.strictEqual(igor.favouriteMessage.title, "Igor's unimaginative blog post"); }); }); @@ -1132,7 +1130,7 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function }); assert.expectAssertion(() => { - message.get('user'); + message.user; }, /You looked up the 'user' relationship on a 'message' with id 1 but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async \(`belongsTo\({ async: true }\)`\)/); }); @@ -1178,7 +1176,7 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function author.deleteRecord(); author.rollbackAttributes(); - return book.get('author').then((fetchedAuthor) => { + return book.author.then((fetchedAuthor) => { assert.strictEqual(fetchedAuthor, author, 'Book has an author after rollback attributes'); }); }); @@ -1223,7 +1221,7 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function author.rollbackAttributes(); }); - assert.strictEqual(book.get('author'), author, 'Book has an author after rollback attributes'); + assert.strictEqual(book.author, author, 'Book has an author after rollback attributes'); }); testInDebug('Passing a model as type to belongsTo should not work', function (assert) { @@ -1460,7 +1458,7 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function run(() => { user = store.createRecord('user'); - user.get('favouriteMessage'); + user.favouriteMessage; assert.ok( hasRelationshipForRecord(user, 'favouriteMessage'), 'Newly created record with relationships in params passed in its constructor should have relationships' @@ -1507,8 +1505,8 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function }, }); - return book.get('author').then((author) => { - assert.strictEqual(author.get('name'), 'This is author', 'author name is correct'); + return book.author.then((author) => { + assert.strictEqual(author.name, 'This is author', 'author name is correct'); }); }); }); @@ -1556,9 +1554,9 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function }, }); - const author = await book.get('author'); + const author = await book.author; - assert.strictEqual(author.get('name'), 'This is author', 'author name is correct'); + assert.strictEqual(author.name, 'This is author', 'author name is correct'); }); test('Relationship data should take precedence over related link when local record data is available', function (assert) { @@ -1608,8 +1606,8 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function ], }); - return book.get('author').then((author) => { - assert.strictEqual(author.get('name'), 'This is author', 'author name is correct'); + return book.author.then((author) => { + assert.strictEqual(author.name, 'This is author', 'author name is correct'); }); }); }); @@ -1673,8 +1671,8 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function }, }); - book.get('author').then((author) => { - assert.strictEqual(author.get('name'), 'This is author', 'author name is correct'); + book.author.then((author) => { + assert.strictEqual(author.name, 'This is author', 'author name is correct'); }); }); }); @@ -1735,10 +1733,9 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function ], }); - return book - .get('author') + return book.author .then((author) => { - assert.strictEqual(author.get('name'), 'This is author', 'author name is correct'); + assert.strictEqual(author.name, 'This is author', 'author name is correct'); }) .then(() => { store.push({ @@ -1755,8 +1752,8 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function }, }); - return book.get('author').then((author) => { - assert.strictEqual(author.get('name'), 'This is updated author', 'author name is correct'); + return book.author.then((author) => { + assert.strictEqual(author.name, 'This is updated author', 'author name is correct'); }); }); }); @@ -1808,10 +1805,9 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function ], }); - return book - .get('author') + return book.author .then((author) => { - assert.strictEqual(author.get('name'), 'This is author', 'author name is correct'); + assert.strictEqual(author.name, 'This is author', 'author name is correct'); }) .then(() => { store.push({ @@ -1828,8 +1824,8 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function }, }); - return book.get('author').then((author) => { - assert.strictEqual(author.get('name'), 'This is author', 'author name is correct'); + return book.author.then((author) => { + assert.strictEqual(author.name, 'This is author', 'author name is correct'); }); }); }); @@ -1877,10 +1873,10 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function .then((_chapter) => { chapter = _chapter; - return chapter.get('book'); + return chapter.book; }) .then((book) => { - assert.strictEqual(book.get('name'), 'book title'); + assert.strictEqual(book.name, 'book title'); adapter.findBelongsTo = function () { return resolve({ @@ -1895,7 +1891,7 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function return chapter.belongsTo('book').reload(); }) .then((book) => { - assert.strictEqual(book.get('name'), 'updated book title'); + assert.strictEqual(book.name, 'updated book title'); }); }); }); @@ -1939,14 +1935,14 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function }; return run(() => { - let book = chapter.get('book'); - assert.strictEqual(book.get('name'), 'book title'); + let book = chapter.book; + assert.strictEqual(book.name, 'book title'); return chapter .belongsTo('book') .reload() .then(function (book) { - assert.strictEqual(book.get('name'), 'updated book title'); + assert.strictEqual(book.name, 'updated book title'); }); }); }); @@ -1987,10 +1983,9 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function }; return run(() => { - return chapter - .get('book') + return chapter.book .then((book) => { - assert.strictEqual(book.get('name'), 'book title'); + assert.strictEqual(book.name, 'book title'); adapter.findRecord = function () { return resolve({ @@ -2005,7 +2000,7 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function return chapter.belongsTo('book').reload(); }) .then((book) => { - assert.strictEqual(book.get('name'), 'updated book title'); + assert.strictEqual(book.name, 'updated book title'); }); }); }); @@ -2026,7 +2021,7 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function }, }, }); - chapter.get('book'); + chapter.book; }); }, /Encountered a relationship identifier without a type for the belongsTo relationship 'book' on , expected a json-api identifier with type 'book'/); }); @@ -2064,7 +2059,7 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function }); run(() => { - chapter.get('book'); + chapter.book; }); assert.strictEqual(count, 0); diff --git a/packages/-ember-data/tests/integration/relationships/has-many-test.js b/packages/-ember-data/tests/integration/relationships/has-many-test.js index f3294d653e2..839e58c78ce 100644 --- a/packages/-ember-data/tests/integration/relationships/has-many-test.js +++ b/packages/-ember-data/tests/integration/relationships/has-many-test.js @@ -15,6 +15,7 @@ import RESTAdapter from '@ember-data/adapter/rest'; import Model, { attr, belongsTo, hasMany } from '@ember-data/model'; import JSONAPISerializer from '@ember-data/serializer/json-api'; import RESTSerializer from '@ember-data/serializer/rest'; +import { deprecatedTest } from '@ember-data/unpublished-test-infra/test-support/deprecated-test'; import testInDebug from '@ember-data/unpublished-test-infra/test-support/test-in-debug'; import { getRelationshipStateForRecord, hasRelationshipForRecord } from '../../helpers/accessors'; @@ -165,7 +166,7 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }, }); - post.get('comments'); + post.comments; }); }, `Assertion Failed: Encountered a relationship identifier without an id for the hasMany relationship 'comments' on , expected a json-api identifier but found '{"id":null,"type":"comment"}'. Please check your serializer and make sure it is serializing the relationship payload into a JSON API format.`); @@ -183,7 +184,7 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }, }, }); - post.get('comments'); + post.comments; }); }, `Assertion Failed: Encountered a relationship identifier without a type for the hasMany relationship 'comments' on , expected a json-api identifier with type 'comment' but found '{"id":"1","type":null}'. Please check your serializer and make sure it is serializing the relationship payload into a JSON API format.`); }); @@ -224,7 +225,7 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }); return store.findRecord('post', 1).then((post) => { - return post.get('comments'); + return post.comments; }); }); }); @@ -260,47 +261,41 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }, }; - run(() => { - store.push({ - data: postData, - included: [ - { - type: 'user', - id: '2', - }, - { - type: 'user', - id: '3', - }, - { - type: 'user', - id: '4', - }, - ], - }); + let user = store.push({ + data: postData, + included: [ + { + type: 'user', + id: '2', + }, + { + type: 'user', + id: '3', + }, + { + type: 'user', + id: '4', + }, + ], }); - let user = store.peekRecord('user', 1); - let contacts = user.get('contacts'); - store.adapterFor('user').deleteRecord = function () { - return { data: { type: 'user', id: 2 } }; + return { data: { type: 'user', id: '2' } }; }; + let contacts = user.contacts; assert.deepEqual( - contacts.map((c) => c.get('id')), + contacts.map((c) => c.id), ['2', '3', '4'], 'user should have expected contacts' ); - run(() => { - contacts.addObject(store.createRecord('user', { id: 5 })); - contacts.addObject(store.createRecord('user', { id: 6 })); - contacts.addObject(store.createRecord('user', { id: 7 })); - }); + contacts.addObject(store.createRecord('user', { id: '5', name: 'chris' })); + contacts.addObject(store.createRecord('user', { id: '6' })); + contacts.addObject(store.createRecord('user', { id: '7' })); assert.deepEqual( - contacts.map((c) => c.get('id')), + contacts.map((c) => c.id), ['2', '3', '4', '5', '6', '7'], 'user should have expected contacts' ); @@ -309,11 +304,11 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( await store.peekRecord('user', 6).destroyRecord(); assert.deepEqual( - contacts.map((c) => c.get('id')), + contacts.map((c) => c.id), ['3', '4', '5', '7'], `user's contacts should have expected contacts` ); - assert.strictEqual(contacts, user.get('contacts')); + assert.strictEqual(contacts, user.contacts); assert.ok(!user.contacts.initialState || !user.contacts.initialState.find((model) => model.id === '2')); @@ -322,11 +317,11 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }); assert.deepEqual( - contacts.map((c) => c.get('id')), + contacts.map((c) => c.id), ['3', '4', '5', '7', '8'], `user's contacts should have expected contacts` ); - assert.strictEqual(contacts, user.get('contacts')); + assert.strictEqual(contacts, user.contacts); }); test('hasMany + canonical vs currentState + unloadRecord', function (assert) { @@ -360,35 +355,31 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }, }; - run(() => { - store.push({ - data: postData, - included: [ - { - type: 'user', - id: 2, - }, - { - type: 'user', - id: 3, - }, - { - type: 'user', - id: 4, - }, - ], - }); + let user = store.push({ + data: postData, + included: [ + { + type: 'user', + id: 2, + }, + { + type: 'user', + id: 3, + }, + { + type: 'user', + id: 4, + }, + ], }); - - let user = store.peekRecord('user', 1); - let contacts = user.get('contacts'); + let contacts = user.contacts; store.adapterFor('user').deleteRecord = function () { return { data: { type: 'user', id: 2 } }; }; assert.deepEqual( - contacts.map((c) => c.get('id')), + contacts.map((c) => c.id), ['2', '3', '4'], 'user should have expected contacts' ); @@ -400,33 +391,28 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }); assert.deepEqual( - contacts.map((c) => c.get('id')), + contacts.map((c) => c.id), ['2', '3', '4', '5', '6', '7'], 'user should have expected contacts' ); - run(() => { - store.peekRecord('user', 2).unloadRecord(); - store.peekRecord('user', 6).unloadRecord(); - }); + store.peekRecord('user', 2).unloadRecord(); + store.peekRecord('user', 6).unloadRecord(); assert.deepEqual( - contacts.map((c) => c.get('id')), + contacts.map((c) => c.id), ['3', '4', '5', '7'], `user's contacts should have expected contacts` ); - assert.strictEqual(contacts, user.get('contacts')); - - run(() => { - contacts.addObject(store.createRecord('user', { id: 8 })); - }); + assert.strictEqual(contacts, user.contacts); + contacts.addObject(store.createRecord('user', { id: 8 })); assert.deepEqual( - contacts.map((c) => c.get('id')), + contacts.map((c) => c.id), ['3', '4', '5', '7', '8'], `user's contacts should have expected contacts` ); - assert.strictEqual(contacts, user.get('contacts')); + assert.strictEqual(contacts, user.contacts); }); test('adapter.findMany only gets unique IDs even if duplicate IDs are present in the hasMany relationship', function (assert) { @@ -472,13 +458,13 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }); return store.findRecord('book', 1).then((book) => { - return book.get('chapters'); + return book.chapters; }); }); }); // This tests the case where a serializer materializes a has-many - // relationship as a internalModel that it can fetch lazily. The most + // relationship as an identifier that it can fetch lazily. The most // common use case of this is to provide a URL to a collection that // is loaded later. test("A serializer can materialize a hasMany as an opaque token that can be lazily fetched via the adapter's findHasMany hook", function (assert) { @@ -545,17 +531,17 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( return store .findRecord('post', 1) .then((post) => { - return post.get('comments'); + return post.comments; }) .then((comments) => { - assert.true(comments.get('isLoaded'), 'comments are loaded'); - assert.strictEqual(comments.get('length'), 2, 'comments have 2 length'); - assert.strictEqual(comments.objectAt(0).get('body'), 'First', 'comment loaded successfully'); + assert.true(comments.isLoaded, 'comments are loaded'); + assert.strictEqual(comments.length, 2, 'comments have 2 length'); + assert.strictEqual(comments.objectAt(0).body, 'First', 'comment loaded successfully'); }); }); }); - test('Accessing a hasMany backed by a link multiple times triggers only one request', function (assert) { + test('Accessing a hasMany backed by a link multiple times triggers only one request', async function (assert) { assert.expect(2); class Message extends Model { @attr('date') created_at; @@ -578,23 +564,18 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); - let post; - - run(() => { - store.push({ - data: { - type: 'post', - id: '1', - relationships: { - comments: { - links: { - related: '/posts/1/comments', - }, + let post = store.push({ + data: { + type: 'post', + id: '1', + relationships: { + comments: { + links: { + related: '/posts/1/comments', }, }, }, - }); - post = store.peekRecord('post', 1); + }, }); let count = 0; @@ -610,32 +591,29 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( ], }; resolve(value); - }, 100); + }, 1); }); }; let promise1, promise2; - run(() => { - promise1 = post.get('comments'); - //Invalidate the post.comments CP - store.push({ - data: { - type: 'comment', - id: '1', - relationships: { - message: { - data: { type: 'post', id: '1' }, - }, + promise1 = post.comments; + //Invalidate the post.comments CP + store.push({ + data: { + type: 'comment', + id: '1', + relationships: { + message: { + data: { type: 'post', id: '1' }, }, }, - }); - promise2 = post.get('comments'); + }, }); + promise2 = post.comments; - return all([promise1, promise2]).then(() => { - assert.strictEqual(promise1.get('promise'), promise2.get('promise'), 'Same promise is returned both times'); - }); + await all([promise1, promise2]); + assert.strictEqual(promise1.promise, promise2.promise, 'Same promise is returned both times'); }); test('A hasMany backed by a link remains a promise after a record has been added to it', function (assert) { @@ -689,7 +667,7 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }); return run(() => { - return post.get('comments').then(() => { + return post.comments.then(() => { store.push({ data: { type: 'comment', @@ -702,7 +680,7 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }, }); - return post.get('comments').then(() => { + return post.comments.then(() => { assert.ok(true, 'Promise was called'); }); }); @@ -752,16 +730,15 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( let post = store.createRecord('post', {}); store.createRecord('comment', { message: post }); - return post - .get('comments') + return post.comments .then((comments) => { - assert.strictEqual(comments.get('length'), 1, 'initially we have one comment'); + assert.strictEqual(comments.length, 1, 'initially we have one comment'); return post.save(); }) - .then(() => post.get('comments')) + .then(() => post.comments) .then((comments) => { - assert.strictEqual(comments.get('length'), 1, 'after saving, we still have one comment'); + assert.strictEqual(comments.length, 1, 'after saving, we still have one comment'); }); }); }); @@ -812,15 +789,14 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( let post = store.createRecord('post', {}); store.createRecord('comment', { message: post }); - return post - .get('comments') + return post.comments .then((comments) => { - assert.strictEqual(comments.get('length'), 1); + assert.strictEqual(comments.length, 1); return post.save(); }) - .then(() => post.get('comments')) + .then(() => post.comments) .then((comments) => { - assert.strictEqual(comments.get('length'), 2); + assert.strictEqual(comments.length, 2); }); }); }); @@ -858,11 +834,10 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( // relationship of post let localComment = store.createRecord('comment', { id: 'local', message: post }); - return post - .get('comments') + return post.comments .then((comments) => { - assert.strictEqual(comments.get('length'), 1); - assert.true(localComment.get('isNew')); + assert.strictEqual(comments.length, 1); + assert.true(localComment.isNew); return post.save(); }) @@ -891,10 +866,10 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }, }); }) - .then(() => post.get('comments')) + .then(() => post.comments) .then((comments) => { - assert.strictEqual(comments.get('length'), 1); - assert.true(localComment.get('isNew')); + assert.strictEqual(comments.length, 1); + assert.true(localComment.isNew); }); }); }); @@ -953,8 +928,8 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( let post = await store.findRecord('post', 1); let comments = await post.comments; - assert.true(comments.get('isLoaded'), 'comments are loaded'); - assert.strictEqual(comments.get('length'), 2, 'comments have 2 length'); + assert.true(comments.isLoaded, 'comments are loaded'); + assert.strictEqual(comments.length, 2, 'comments have 2 length'); adapter.findHasMany = function (store, snapshot, link, relationship) { assert.strictEqual(relationship.type, 'comment', 'findHasMany relationship type was Comment'); @@ -1022,9 +997,9 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( store .findRecord('post', '1') .then(function (post) { - let comments = post.get('comments'); - assert.true(comments.get('isLoaded'), 'comments are loaded'); - assert.strictEqual(comments.get('length'), 2, 'comments have a length of 2'); + let comments = post.comments; + assert.true(comments.isLoaded, 'comments are loaded'); + assert.strictEqual(comments.length, 2, 'comments have a length of 2'); adapter.findMany = function (store, type, ids, snapshots) { return resolve({ @@ -1038,7 +1013,7 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( return comments.reload(); }) .then(function (newComments) { - assert.strictEqual(newComments.get('firstObject.body'), 'FirstUpdated', 'Record body was correctly updated'); + assert.strictEqual(newComments.firstObject.body, 'FirstUpdated', 'Record body was correctly updated'); }); }); }); @@ -1098,11 +1073,11 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( store .findRecord('post', 1) .then(function (post) { - return post.get('comments'); + return post.comments; }) .then(function (comments) { - assert.true(comments.get('isLoaded'), 'comments are loaded'); - assert.strictEqual(comments.get('length'), 2, 'comments have 2 length'); + assert.true(comments.isLoaded, 'comments are loaded'); + assert.strictEqual(comments.length, 2, 'comments have 2 length'); adapter.findMany = function (store, type, ids, snapshots) { return resolve({ @@ -1116,7 +1091,7 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( return comments.reload(); }) .then(function (newComments) { - assert.strictEqual(newComments.get('firstObject.body'), 'FirstUpdated', 'Record body was correctly updated'); + assert.strictEqual(newComments.firstObject.body, 'FirstUpdated', 'Record body was correctly updated'); }); }); }); @@ -1147,7 +1122,7 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( adapter.findRecord = function () { return resolve({ data: { - id: 1, + id: '1', type: 'post', relationships: { comments: { @@ -1173,19 +1148,19 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( } }; - let post = await store.findRecord('post', 1); - let comments = post.get('comments'); + let post = await store.findRecord('post', '1'); + let commentsPromiseArray = post.comments; let manyArray; try { - manyArray = await comments; + manyArray = await commentsPromiseArray; assert.ok(false, 'Expected exception to be raised'); } catch (e) { assert.ok(true, `An error was thrown on the first reload of comments: ${e.message}`); - manyArray = await comments.reload(); + manyArray = await commentsPromiseArray.reload(); } - assert.true(manyArray.get('isLoaded'), 'the reload worked, comments are now loaded'); + assert.true(manyArray.isLoaded, 'the reload worked, comments are now loaded'); try { await manyArray.reload(); @@ -1194,11 +1169,11 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( assert.ok(true, `An error was thrown on the second reload via manyArray: ${e.message}`); } - assert.true(manyArray.get('isLoaded'), 'the second reload failed, comments are still loaded though'); + assert.true(manyArray.isLoaded, 'the second reload failed, comments are still loaded though'); let reloadedManyArray = await manyArray.reload(); - assert.true(reloadedManyArray.get('isLoaded'), 'the third reload worked, comments are loaded again'); + assert.true(reloadedManyArray.isLoaded, 'the third reload worked, comments are loaded again'); assert.strictEqual(reloadedManyArray, manyArray, 'the many array stays the same'); assert.strictEqual(loadingCount, 4, 'We only fired 4 requests'); }); @@ -1255,14 +1230,11 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }; run(function () { store.findRecord('post', 1).then(function (post) { - return post - .get('comments') - .reload() - .then(function (comments) { - assert.true(comments.get('isLoaded'), 'comments are loaded'); - assert.strictEqual(comments.get('length'), 2, 'comments have 2 length'); - assert.strictEqual(comments.get('firstObject.body'), 'FirstUpdated', 'Record body was correctly updated'); - }); + return post.comments.reload().then(function (comments) { + assert.true(comments.isLoaded, 'comments are loaded'); + assert.strictEqual(comments.length, 2, 'comments have 2 length'); + assert.strictEqual(comments.firstObject.body, 'FirstUpdated', 'Record body was correctly updated'); + }); }); }); }); @@ -1318,7 +1290,7 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }; run(function () { store.findRecord('post', 1).then(function (post) { - post.get('comments').then(function (comments) { + post.comments.then(function (comments) { all([comments.reload(), comments.reload(), comments.reload()]).then(function (comments) { assert.strictEqual( count, @@ -1385,14 +1357,11 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( run(function () { store.findRecord('post', 1).then(function (post) { - return post - .get('comments') - .reload() - .then(function (comments) { - assert.true(comments.get('isLoaded'), 'comments are loaded'); - assert.strictEqual(comments.get('length'), 2, 'comments have 2 length'); - assert.strictEqual(comments.get('firstObject.body'), 'FirstUpdated', 'Record body was correctly updated'); - }); + return post.comments.reload().then(function (comments) { + assert.true(comments.isLoaded, 'comments are loaded'); + assert.strictEqual(comments.length, 2, 'comments have 2 length'); + assert.strictEqual(comments.firstObject.body, 'FirstUpdated', 'Record body was correctly updated'); + }); }); }); }); @@ -1452,7 +1421,7 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( run(function () { store.findRecord('post', 1).then(function (post) { - post.get('comments').then(function (comments) { + post.comments.then(function (comments) { all([comments.reload(), comments.reload(), comments.reload()]).then(function (comments) { assert.strictEqual( count, @@ -1466,67 +1435,71 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }); }); - test('PromiseArray proxies createRecord to its ManyArray once the hasMany is loaded', function (assert) { - assert.expect(4); - class Message extends Model { - @attr('date') created_at; - @belongsTo('user', { async: false }) iser; - } + deprecatedTest( + 'PromiseArray proxies createRecord to its ManyArray once the hasMany is loaded', + { id: 'ember-data:deprecate-promise-many-array-behaviors', until: '5.0', count: 1 }, + function (assert) { + assert.expect(4); + class Message extends Model { + @attr('date') created_at; + @belongsTo('user', { async: false }) iser; + } - class Post extends Message { - @attr title; - @hasMany('comment', { async: true, inverse: 'message' }) comments; - } + class Post extends Message { + @attr title; + @hasMany('comment', { async: true, inverse: 'message' }) comments; + } - class Comment extends Message { - @attr body; - @belongsTo('post', { async: false, polymorphic: true, inverse: 'comments' }) message; - } - this.owner.register('model:post', Post); - this.owner.register('model:comment', Comment); - this.owner.register('model:message', Message); + class Comment extends Message { + @attr body; + @belongsTo('post', { async: false, polymorphic: true, inverse: 'comments' }) message; + } + this.owner.register('model:post', Post); + this.owner.register('model:comment', Comment); + this.owner.register('model:message', Message); - let store = this.owner.lookup('service:store'); - let adapter = store.adapterFor('application'); + let store = this.owner.lookup('service:store'); + let adapter = store.adapterFor('application'); - adapter.findHasMany = function (store, snapshot, link, relationship) { - return resolve({ - data: [ - { id: 1, type: 'comment', attributes: { body: 'First' } }, - { id: 2, type: 'comment', attributes: { body: 'Second' } }, - ], - }); - }; - let post; + adapter.findHasMany = function (store, snapshot, link, relationship) { + return resolve({ + data: [ + { id: 1, type: 'comment', attributes: { body: 'First' } }, + { id: 2, type: 'comment', attributes: { body: 'Second' } }, + ], + }); + }; + let post; - run(function () { - store.push({ - data: { - type: 'post', - id: '1', - relationships: { - comments: { - links: { - related: 'someLink', + run(function () { + store.push({ + data: { + type: 'post', + id: '1', + relationships: { + comments: { + links: { + related: 'someLink', + }, }, }, }, - }, + }); + post = store.peekRecord('post', 1); }); - post = store.peekRecord('post', 1); - }); - run(function () { - post.get('comments').then(function (comments) { - assert.true(comments.get('isLoaded'), 'comments are loaded'); - assert.strictEqual(comments.get('length'), 2, 'comments have 2 length'); + run(function () { + post.comments.then(function (comments) { + assert.true(comments.isLoaded, 'comments are loaded'); + assert.strictEqual(comments.length, 2, 'comments have 2 length'); - let newComment = post.get('comments').createRecord({ body: 'Third' }); - assert.strictEqual(newComment.get('body'), 'Third', 'new comment is returned'); - assert.strictEqual(comments.get('length'), 3, 'comments have 3 length, including new record'); + let newComment = post.comments.createRecord({ body: 'Third' }); + assert.strictEqual(newComment.body, 'Third', 'new comment is returned'); + assert.strictEqual(comments.length, 3, 'comments have 3 length, including new record'); + }); }); - }); - }); + } + ); test('An updated `links` value should invalidate a relationship cache', async function (assert) { assert.expect(8); @@ -1590,10 +1563,10 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( post = store.peekRecord('post', 1); }); - const comments = await post.get('comments'); - assert.true(comments.get('isLoaded'), 'comments are loaded'); - assert.strictEqual(comments.get('length'), 2, 'comments have 2 length'); - assert.strictEqual(comments.objectAt(0).get('body'), 'First', 'comment 1 successfully loaded'); + const comments = await post.comments; + assert.true(comments.isLoaded, 'comments are loaded'); + assert.strictEqual(comments.length, 2, 'comments have 2 length'); + assert.strictEqual(comments.objectAt(0).body, 'First', 'comment 1 successfully loaded'); store.push({ data: { type: 'post', @@ -1607,10 +1580,10 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }, }, }); - const newComments = await post.get('comments'); + const newComments = await post.comments; assert.strictEqual(comments, newComments, 'hasMany array was kept the same'); - assert.strictEqual(newComments.get('length'), 3, 'comments updated successfully'); - assert.strictEqual(newComments.objectAt(0).get('body'), 'Third', 'third comment loaded successfully'); + assert.strictEqual(newComments.length, 3, 'comments updated successfully'); + assert.strictEqual(newComments.objectAt(0).body, 'Third', 'third comment loaded successfully'); }); test("When a polymorphic hasMany relationship is accessed, the adapter's findMany method should not be called if all the records in the relationship are already loaded", function (assert) { @@ -1658,8 +1631,8 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( run(function () { store.findRecord('user', 1).then(function (user) { - let messages = user.get('messages'); - assert.strictEqual(messages.get('length'), 2, 'The messages are correctly loaded'); + let messages = user.messages; + assert.strictEqual(messages.length, 2, 'The messages are correctly loaded'); }); }); }); @@ -1705,10 +1678,10 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( store .findRecord('user', 1) .then(function (user) { - return user.get('messages'); + return user.messages; }) .then(function (messages) { - assert.strictEqual(messages.get('length'), 2, 'The messages are correctly loaded'); + assert.strictEqual(messages.length, 2, 'The messages are correctly loaded'); }); }); }); @@ -1724,7 +1697,7 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( body: 'Well I thought the title was fine', }); - igor.get('messages').addObject(comment); + igor.messages.addObject(comment); assert.strictEqual(igor.get('messages.firstObject.body'), 'Well I thought the title was fine'); }); @@ -1781,7 +1754,7 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( ], }); const contacts = await user.contacts; - assert.strictEqual(contacts.get('length'), 1, 'The contacts relationship is correctly set up'); + assert.strictEqual(contacts.length, 1, 'The contacts relationship is correctly set up'); }); test('Type can be inferred from the key of an async hasMany relationship', function (assert) { @@ -1833,10 +1806,10 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( store .findRecord('user', 1) .then(function (user) { - return user.get('contacts'); + return user.contacts; }) .then(function (contacts) { - assert.strictEqual(contacts.get('length'), 1, 'The contacts relationship is correctly set up'); + assert.strictEqual(contacts.length, 1, 'The contacts relationship is correctly set up'); }); }); }); @@ -1887,10 +1860,10 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( store .findRecord('user', 1) .then(function (user) { - return user.get('contacts'); + return user.contacts; }) .then(function (contacts) { - assert.strictEqual(contacts.get('length'), 2, 'The contacts relationship is correctly set up'); + assert.strictEqual(contacts.length, 2, 'The contacts relationship is correctly set up'); }); }); }); @@ -1929,7 +1902,7 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( contact: email, }); - assert.strictEqual(post.get('contact'), email, 'The polymorphic belongsTo is set up correctly'); + assert.strictEqual(post.contact, email, 'The polymorphic belongsTo is set up correctly'); assert.strictEqual(get(email, 'posts.length'), 1, 'The inverse has many is set up correctly on the email side.'); }); @@ -1964,7 +1937,7 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( run(function () { all([store.findRecord('post', 1), store.findRecord('post', 2)]).then(function (records) { assert.expectAssertion(function () { - records[0].get('comments').pushObject(records[1]); + records[0].comments.pushObject(records[1]); }, /The 'post' type does not implement 'comment' and thus cannot be assigned to the 'comments' relationship in 'post'/); }); }); @@ -2031,13 +2004,13 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( asyncRecords .then(function (records) { - records.messages = records.user.get('messages'); + records.messages = records.user.messages; return hash(records); }) .then(function (records) { records.messages.pushObject(records.post); records.messages.pushObject(records.comment); - assert.strictEqual(records.messages.get('length'), 2, 'The messages are correctly added'); + assert.strictEqual(records.messages.length, 2, 'The messages are correctly added'); assert.expectAssertion(function () { records.messages.pushObject(records.anotherUser); @@ -2076,12 +2049,12 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( const messages = await user.messages; - assert.strictEqual(messages.get('length'), 1, 'The user has 1 message'); + assert.strictEqual(messages.length, 1, 'The user has 1 message'); let removedObject = messages.popObject(); assert.strictEqual(removedObject, comment, 'The message is correctly removed'); - assert.strictEqual(messages.get('length'), 0, 'The user does not have any messages'); + assert.strictEqual(messages.length, 0, 'The user does not have any messages'); assert.strictEqual(messages.objectAt(0), undefined, "Null messages can't be fetched"); }); @@ -2212,8 +2185,8 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( post.set('comments', store.peekAll('comment').toArray()); }); - return post.get('comments').then((comments) => { - assert.strictEqual(comments.get('length'), 2, 'we can set async HM relationship'); + return post.comments.then((comments) => { + assert.strictEqual(comments.length, 2, 'we can set async HM relationship'); }); }); @@ -2231,14 +2204,14 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( return run(() => { post = store.createRecord('post'); comment = store.createRecord('comment'); - post.get('comments').pushObject(comment); + post.comments.pushObject(comment); return post.save(); }).then(() => { assert.strictEqual(get(post, 'comments.length'), 1, "The unsaved comment should be in the post's comments array"); }); }); - test('dual non-async HM <-> BT', function (assert) { + test('dual non-async HM <-> BT', async function (assert) { class Message extends Model { @attr('date') created_at; @belongsTo('user', { async: false }) iser; @@ -2266,52 +2239,41 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( return resolve(serialized); }; - let post, firstComment; - - run(function () { - store.push({ - data: { - type: 'post', - id: '1', - relationships: { - comments: { - data: [{ type: 'comment', id: '1' }], - }, + let post = store.push({ + data: { + type: 'post', + id: '1', + relationships: { + comments: { + data: [{ type: 'comment', id: '1' }], }, }, - }); - store.push({ - data: { - type: 'comment', - id: '1', - relationships: { - comments: { - post: { type: 'post', id: '1' }, - }, + }, + }); + let firstComment = store.push({ + data: { + type: 'comment', + id: '1', + relationships: { + comments: { + post: { type: 'post', id: '1' }, }, }, - }); + }, + }); - post = store.peekRecord('post', 1); - firstComment = store.peekRecord('comment', 1); + const comment = store.createRecord('comment', { post }); + await comment.save(); - store - .createRecord('comment', { - post: post, - }) - .save() - .then(function (comment) { - let commentPost = comment.get('post'); - let postComments = comment.get('post.comments'); - let postCommentsLength = comment.get('post.comments.length'); - - assert.deepEqual(post, commentPost, 'expect the new comments post, to be the correct post'); - assert.ok(postComments, 'comments should exist'); - assert.strictEqual(postCommentsLength, 2, "comment's post should have a internalModel back to comment"); - assert.ok(postComments && postComments.indexOf(firstComment) !== -1, 'expect to contain first comment'); - assert.ok(postComments && postComments.indexOf(comment) !== -1, 'expected to contain the new comment'); - }); - }); + let commentPost = comment.post; + let postComments = comment.post.comments; + let postCommentsLength = comment.get('post.comments.length'); + + assert.deepEqual(post, commentPost, 'expect the new comments post, to be the correct post'); + assert.ok(postComments, 'comments should exist'); + assert.strictEqual(postCommentsLength, 2, "comment's post should have an identifier back to comment"); + assert.ok(postComments && postComments.indexOf(firstComment) !== -1, 'expect to contain first comment'); + assert.ok(postComments && postComments.indexOf(comment) !== -1, 'expected to contain the new comment'); }); test('When an unloaded record is added to the hasMany, it gets fetched once the hasMany is accessed even if the hasMany has been already fetched', async function (assert) { @@ -2371,10 +2333,10 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }, }); - let fetchedComments = await post.get('comments'); + let fetchedComments = await post.comments; - assert.strictEqual(fetchedComments.get('length'), 2, 'comments fetched successfully'); - assert.strictEqual(fetchedComments.objectAt(0).get('body'), 'first', 'first comment loaded successfully'); + assert.strictEqual(fetchedComments.length, 2, 'comments fetched successfully'); + assert.strictEqual(fetchedComments.objectAt(0).body, 'first', 'first comment loaded successfully'); store.push({ data: { @@ -2392,10 +2354,10 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }, }); - let newlyFetchedComments = await post.get('comments'); + let newlyFetchedComments = await post.comments; - assert.strictEqual(newlyFetchedComments.get('length'), 3, 'all three comments fetched successfully'); - assert.strictEqual(newlyFetchedComments.objectAt(2).get('body'), 'third', 'third comment loaded successfully'); + assert.strictEqual(newlyFetchedComments.length, 3, 'all three comments fetched successfully'); + assert.strictEqual(newlyFetchedComments.objectAt(2).body, 'third', 'third comment loaded successfully'); }); testInDebug('A sync hasMany errors out if there are unloaded records in it', function (assert) { @@ -2420,14 +2382,14 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( /You looked up the 'comments' relationship on a 'post' with id 1 but some of the associated records were not loaded./; try { - post.get('comments'); + post.comments; assert.ok(false, 'expected assertion'); } catch (e) { assert.ok(assertionMessage.test(e.message), 'correct assertion'); } }); - testInDebug('An async hasMany does not fetch with a model created with no options', function (assert) { + testInDebug('An async hasMany does not fetch with a model created with no options', async function (assert) { let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); adapter.findRecord = function () { @@ -2452,8 +2414,9 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }, }); - post.get('comments').pushObject(comment); - assert.ok(post.get('comments').length, 1, 'expected length for comments'); + const comments = await post.comments; + comments.pushObject(comment); + assert.ok(post.comments.length, 1, 'expected length for comments'); }); test('After removing and unloading a record, a hasMany relationship should still be valid', function (assert) { @@ -2473,11 +2436,11 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( included: [{ type: 'comment', id: '1' }], }); const post = store.peekRecord('post', 1); - const comments = post.get('comments'); + const comments = post.comments; const comment = comments.objectAt(0); comments.removeObject(comment); store.unloadRecord(comment); - assert.strictEqual(comments.get('length'), 0); + assert.strictEqual(comments.length, 0); return post; }); @@ -2536,7 +2499,7 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }); post = store.peekRecord('post', 1); - assert.deepEqual(post.get('comments').toArray(), [comment1, comment2], 'Initial ordering is correct'); + assert.deepEqual(post.comments.toArray(), [comment1, comment2], 'Initial ordering is correct'); }); run(() => { @@ -2555,7 +2518,7 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }, }); }); - assert.deepEqual(post.get('comments').toArray(), [comment2, comment1], 'Updated ordering is correct'); + assert.deepEqual(post.comments.toArray(), [comment2, comment1], 'Updated ordering is correct'); run(() => { store.push({ @@ -2570,7 +2533,7 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }, }); }); - assert.deepEqual(post.get('comments').toArray(), [comment2], 'Updated ordering is correct'); + assert.deepEqual(post.comments.toArray(), [comment2], 'Updated ordering is correct'); run(() => { store.push({ @@ -2590,11 +2553,7 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }, }); }); - assert.deepEqual( - post.get('comments').toArray(), - [comment1, comment2, comment3, comment4], - 'Updated ordering is correct' - ); + assert.deepEqual(post.comments.toArray(), [comment1, comment2, comment3, comment4], 'Updated ordering is correct'); run(() => { store.push({ @@ -2612,7 +2571,7 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }, }); }); - assert.deepEqual(post.get('comments').toArray(), [comment4, comment3], 'Updated ordering is correct'); + assert.deepEqual(post.comments.toArray(), [comment4, comment3], 'Updated ordering is correct'); run(() => { store.push({ @@ -2633,99 +2592,74 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }); }); - assert.deepEqual( - post.get('comments').toArray(), - [comment4, comment2, comment3, comment1], - 'Updated ordering is correct' - ); + assert.deepEqual(post.comments.toArray(), [comment4, comment2, comment3, comment1], 'Updated ordering is correct'); }); - test('Rollbacking attributes for deleted record restores implicit relationship correctly when the hasMany side has been deleted - async', function (assert) { + test('Rollbacking attributes for deleted record restores implicit relationship correctly when the hasMany side has been deleted - async', async function (assert) { let store = this.owner.lookup('service:store'); - - let book, chapter; - - run(() => { - store.push({ - data: { - type: 'book', - id: '1', - attributes: { - title: "Stanley's Amazing Adventures", - }, - relationships: { - chapters: { - data: [{ type: 'chapter', id: '2' }], - }, + let book = store.push({ + data: { + type: 'book', + id: '1', + attributes: { + title: "Stanley's Amazing Adventures", + }, + relationships: { + chapters: { + data: [{ type: 'chapter', id: '2' }], }, }, - included: [ - { - type: 'chapter', - id: '2', - attributes: { - title: 'Sailing the Seven Seas', - }, + }, + included: [ + { + type: 'chapter', + id: '2', + attributes: { + title: 'Sailing the Seven Seas', }, - ], - }); - book = store.peekRecord('book', 1); - chapter = store.peekRecord('chapter', 2); + }, + ], }); + let chapter = store.peekRecord('chapter', '2'); - run(() => { - chapter.deleteRecord(); - chapter.rollbackAttributes(); - }); + chapter.deleteRecord(); + chapter.rollbackAttributes(); - return run(() => { - return book.get('chapters').then((fetchedChapters) => { - assert.strictEqual(fetchedChapters.objectAt(0), chapter, 'Book has a chapter after rollback attributes'); - }); - }); + const fetchedChapters = await book.chapters; + assert.strictEqual(fetchedChapters.objectAt(0), chapter, 'Book has a chapter after rollback attributes'); }); - test('Rollbacking attributes for deleted record restores implicit relationship correctly when the hasMany side has been deleted - sync', function (assert) { + test('Rollbacking attributes for deleted record restores implicit relationship correctly when the hasMany side has been deleted - sync', async function (assert) { let store = this.owner.lookup('service:store'); - - let book, chapter; - - run(() => { - store.push({ - data: { - type: 'book', - id: '1', - attributes: { - title: "Stanley's Amazing Adventures", - }, - relationships: { - chapters: { - data: [{ type: 'chapter', id: '2' }], - }, + let chapter = store.push({ + data: { + type: 'chapter', + id: '1', + attributes: { + title: "Stanley's Amazing Adventures", + }, + relationships: { + pages: { + data: [{ type: 'page', id: '2' }], }, }, - included: [ - { - type: 'chapter', - id: '2', - attributes: { - title: 'Sailing the Seven Seas', - }, + }, + included: [ + { + type: 'page', + id: '2', + attributes: { + title: 'Sailing the Seven Seas', }, - ], - }); - book = store.peekRecord('book', 1); - chapter = store.peekRecord('chapter', 2); + }, + ], }); + let page = store.peekRecord('page', '2'); - run(() => { - chapter.deleteRecord(); - chapter.rollbackAttributes(); - }); + page.deleteRecord(); + page.rollbackAttributes(); - run(() => { - assert.strictEqual(book.get('chapters.firstObject'), chapter, 'Book has a chapter after rollback attributes'); - }); + assert.strictEqual(chapter.pages.firstObject, page, 'Chapter has a page after rollback attributes'); }); test('Rollbacking attributes for deleted record restores implicit relationship correctly when the belongsTo side has been deleted - async', function (assert) { @@ -2773,7 +2707,7 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }); return run(() => { - return page.get('chapter').then((fetchedChapter) => { + return page.chapter.then((fetchedChapter) => { assert.strictEqual(fetchedChapter, chapter, 'Page has a chapter after rollback attributes'); }); }); @@ -2817,7 +2751,7 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }); run(() => { - assert.strictEqual(page.get('chapter'), chapter, 'Page has a chapter after rollback attributes'); + assert.strictEqual(page.chapter, chapter, 'Page has a chapter after rollback attributes'); }); }); @@ -2869,7 +2803,7 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( page2 = store.peekRecord('page', 2); chapter = store.peekRecord('chapter', 1); - chapter.get('pages').addArrayObserver(this, { + chapter.pages.addArrayObserver(this, { willChange(pages, index, removeCount, addCount) { if (observe) { assert.strictEqual(pages.objectAt(index), page2, 'page2 is passed to willChange'); @@ -2937,7 +2871,7 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( page2 = store.peekRecord('page', 2); chapter = store.peekRecord('chapter', 1); - chapter.get('pages').addArrayObserver(this, { + chapter.pages.addArrayObserver(this, { willChange(pages, index, removeCount, addCount) { if (observe) { assert.strictEqual(addCount, 1, 'addCount is correct'); @@ -3175,73 +3109,62 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( let store = this.owner.lookup('service:store'); - run(() => { - store.push({ - data: [ - { - type: 'post', - id: '1', - relationships: { - comments: { - data: [ - { type: 'comment', id: '1' }, - { type: 'comment', id: '2' }, - { type: 'comment', id: '3' }, - ], - }, + store.push({ + data: [ + { + type: 'post', + id: '1', + relationships: { + comments: { + data: [ + { type: 'comment', id: '1' }, + { type: 'comment', id: '2' }, + { type: 'comment', id: '3' }, + ], }, }, - { - type: 'comment', - id: '1', - }, - { - type: 'comment', - id: '2', - }, - { - type: 'comment', - id: '3', - }, - ], - }); + }, + { + type: 'comment', + id: '1', + }, + { + type: 'comment', + id: '2', + }, + { + type: 'comment', + id: '3', + }, + ], }); - return run(() => { - return store.findRecord('post', 1).then((post) => { - let comments = post.get('comments'); - assert.strictEqual(comments.get('length'), 3, 'Initial comments count'); + const post = await store.findRecord('post', '1'); + let commentsPromiseArray = post.comments; + let comments = await commentsPromiseArray; + assert.strictEqual(commentsPromiseArray.length, 3, 'Initial comments count'); - // Add comment #4 - let comment = store.createRecord('comment'); - comments.addObject(comment); + // Add comment #4 + let comment = store.createRecord('comment'); + comments.addObject(comment); - return comment - .save() - .then(() => { - let comments = post.get('comments'); - assert.strictEqual(comments.get('length'), 4, 'Comments count after first add'); + await comment.save(); + commentsPromiseArray = post.comments; + assert.strictEqual(commentsPromiseArray.length, 4, 'Comments count after first add'); - // Delete comment #4 - return comments.get('lastObject').destroyRecord(); - }) - .then(() => { - let comments = post.get('comments'); - let length = comments.get('length'); + // Delete comment #4 + await comments.lastObject.destroyRecord(); - assert.strictEqual(length, 3, 'Comments count after destroy'); + commentsPromiseArray = post.comments; + assert.strictEqual(commentsPromiseArray.length, 3, 'Comments count after destroy'); - // Add another comment #4 - let comment = store.createRecord('comment'); - comments.addObject(comment); - return comment.save(); - }) - .then(() => { - let comments = post.get('comments'); - assert.strictEqual(comments.get('length'), 4, 'Comments count after second add'); - }); - }); - }); + // Add another comment #4 + comment = store.createRecord('comment'); + comments.addObject(comment); + await comment.save(); + + commentsPromiseArray = post.comments; + assert.strictEqual(commentsPromiseArray.length, 4, 'Comments count after second add'); }); test('hasMany hasAnyRelationshipData async loaded', function (assert) { @@ -3435,7 +3358,7 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( let user; run(() => { user = store.createRecord('user'); - user.get('messages'); + user.messages; assert.ok( hasRelationshipForRecord(user, 'messages'), 'Newly created record with relationships in params passed in its constructor should have relationships' @@ -3526,8 +3449,8 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }); return run(() => { - return book.get('chapters').then((chapters) => { - let meta = chapters.get('meta'); + return book.chapters.then((chapters) => { + let meta = chapters.meta; assert.strictEqual(get(meta, 'foo'), 'bar', 'metadata is available'); }); }); @@ -3600,12 +3523,12 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }); return run(() => { - return book1.get('chapters').then((chapters) => { - let meta = chapters.get('meta'); + return book1.chapters.then((chapters) => { + let meta = chapters.meta; assert.strictEqual(get(meta, 'foo'), 'bar', 'metadata should available'); - return book2.get('chapters').then((chapters) => { - let meta = chapters.get('meta'); + return book2.chapters.then((chapters) => { + let meta = chapters.meta; assert.strictEqual(meta, null, 'metadata should not be available'); }); }); @@ -3676,8 +3599,8 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }, }); - return post.get('comments').then((comments) => { - assert.strictEqual(comments.get('firstObject.body'), 'This is comment', 'comment body is correct'); + return post.comments.then((comments) => { + assert.strictEqual(comments.firstObject.body, 'This is comment', 'comment body is correct'); }); }); }); @@ -3747,8 +3670,8 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }, }); - return post.get('comments').then((comments) => { - assert.strictEqual(comments.get('firstObject.body'), 'This is comment', 'comment body is correct'); + return post.comments.then((comments) => { + assert.strictEqual(comments.firstObject.body, 'This is comment', 'comment body is correct'); }); }); }); @@ -3815,8 +3738,8 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( ], }); - return post.get('comments').then((comments) => { - assert.strictEqual(comments.get('firstObject.body'), 'This is comment', 'comment body is correct'); + return post.comments.then((comments) => { + assert.strictEqual(comments.firstObject.body, 'This is comment', 'comment body is correct'); }); }); }); @@ -3902,12 +3825,8 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( ], }); - return post.get('comments').then((comments) => { - assert.strictEqual( - comments.get('firstObject.body'), - 'This is comment fetched by link', - 'comment body is correct' - ); + return post.comments.then((comments) => { + assert.strictEqual(comments.firstObject.body, 'This is comment fetched by link', 'comment body is correct'); }); }); }); @@ -3977,66 +3896,70 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }, }); - return post.get('comments').then((comments) => { - assert.strictEqual(comments.get('firstObject.body'), 'This is updated comment', 'comment body is correct'); + return post.comments.then((comments) => { + assert.strictEqual(comments.firstObject.body, 'This is updated comment', 'comment body is correct'); }); }); }); - test('PromiseArray proxies createRecord to its ManyArray before the hasMany is loaded', function (assert) { - assert.expect(1); - class Message extends Model { - @attr('date') created_at; - @belongsTo('user', { async: false }) iser; - } + deprecatedTest( + 'PromiseArray proxies createRecord to its ManyArray before the hasMany is loaded', + { id: 'ember-data:deprecate-promise-many-array-behaviors', until: '5.0', count: 1 }, + function (assert) { + assert.expect(1); + class Message extends Model { + @attr('date') created_at; + @belongsTo('user', { async: false }) iser; + } - class Post extends Message { - @attr title; - @hasMany('comment', { async: true, inverse: 'message' }) comments; - } + class Post extends Message { + @attr title; + @hasMany('comment', { async: true, inverse: 'message' }) comments; + } - class Comment extends Message { - @attr body; - @belongsTo('post', { async: false, polymorphic: true, inverse: 'comments' }) message; - } - this.owner.register('model:post', Post); - this.owner.register('model:comment', Comment); - this.owner.register('model:message', Message); + class Comment extends Message { + @attr body; + @belongsTo('post', { async: false, polymorphic: true, inverse: 'comments' }) message; + } + this.owner.register('model:post', Post); + this.owner.register('model:comment', Comment); + this.owner.register('model:message', Message); - let store = this.owner.lookup('service:store'); - let adapter = store.adapterFor('application'); + let store = this.owner.lookup('service:store'); + let adapter = store.adapterFor('application'); - adapter.findHasMany = function (store, record, link, relationship) { - return resolve({ - data: [ - { id: 1, type: 'comment', attributes: { body: 'First' } }, - { id: 2, type: 'comment', attributes: { body: 'Second' } }, - ], - }); - }; + adapter.findHasMany = function (store, record, link, relationship) { + return resolve({ + data: [ + { id: 1, type: 'comment', attributes: { body: 'First' } }, + { id: 2, type: 'comment', attributes: { body: 'Second' } }, + ], + }); + }; - return run(() => { - let post = store.push({ - data: { - type: 'post', - id: 1, - relationships: { - comments: { - links: { - related: 'someLink', + return run(() => { + let post = store.push({ + data: { + type: 'post', + id: 1, + relationships: { + comments: { + links: { + related: 'someLink', + }, }, }, }, - }, - }); + }); - let comments = post.get('comments'); - comments.createRecord(); - return comments.then((comments) => { - assert.strictEqual(comments.get('length'), 3, 'comments have 3 length, including new record'); + let comments = post.comments; + comments.createRecord(); + return comments.then((comments) => { + assert.strictEqual(comments.length, 3, 'comments have 3 length, including new record'); + }); }); - }); - }); + } + ); test('deleteRecord + unloadRecord', async function (assert) { class User extends Model { @@ -4090,7 +4013,8 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }); let user = store.peekRecord('user', 'user-1'); - let posts = user.get('posts'); + let postsPromiseArray = user.posts; + let posts = await postsPromiseArray; store.adapterFor('post').deleteRecord = function () { // just acknowledge all deletes, but with a noop @@ -4098,30 +4022,34 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }; assert.deepEqual( - posts.map((x) => x.get('id')), + posts.map((x) => x.id), ['post-1', 'post-2', 'post-3', 'post-4', 'post-5'] ); + assert.strictEqual(postsPromiseArray.length, 5, 'promise array length is correct'); await store.peekRecord('post', 'post-2').destroyRecord(); assert.deepEqual( - posts.map((x) => x.get('id')), + posts.map((x) => x.id), ['post-1', 'post-3', 'post-4', 'post-5'] ); + assert.strictEqual(postsPromiseArray.length, 4, 'promise array length is correct'); await store.peekRecord('post', 'post-3').destroyRecord(); assert.deepEqual( - posts.map((x) => x.get('id')), + posts.map((x) => x.id), ['post-1', 'post-4', 'post-5'] ); + assert.strictEqual(postsPromiseArray.length, 3, 'promise array length is correct'); await store.peekRecord('post', 'post-4').destroyRecord(); assert.deepEqual( - posts.map((x) => x.get('id')), + posts.map((x) => x.id), ['post-1', 'post-5'] ); + assert.strictEqual(postsPromiseArray.length, 2, 'promise array length is correct'); }); test('unloading and reloading a record with hasMany relationship - #3084', function (assert) { @@ -4288,7 +4216,7 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }); run(() => { - book.get('chapters'); + book.chapters; }); assert.strictEqual(count, 0); @@ -4371,18 +4299,18 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }; const post = store.peekRecord('post', postID); - post.get('comments').then(function (comments) { - assert.true(comments.get('isLoaded'), 'comments are loaded'); + post.comments.then(function (comments) { + assert.true(comments.isLoaded, 'comments are loaded'); assert.strictEqual(hasManyCounter, 1, 'link was requested'); - assert.strictEqual(comments.get('length'), 2, 'comments have 2 length'); + assert.strictEqual(comments.length, 2, 'comments have 2 length'); post .hasMany('comments') .reload() .then(function (comments) { - assert.true(comments.get('isLoaded'), 'comments are loaded'); + assert.true(comments.isLoaded, 'comments are loaded'); assert.strictEqual(hasManyCounter, 2, 'link was requested'); - assert.strictEqual(comments.get('length'), 2, 'comments have 2 length'); + assert.strictEqual(comments.length, 2, 'comments have 2 length'); }); }); }); @@ -4431,6 +4359,6 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }, }); - assert.strictEqual(person.get('phoneNumbers.length'), 1); + assert.strictEqual(person.phoneNumbers.length, 1); }); }); diff --git a/packages/-ember-data/tests/integration/relationships/inverse-relationship-load-test.js b/packages/-ember-data/tests/integration/relationships/inverse-relationship-load-test.js index c6423423852..3fa9feb5fd3 100644 --- a/packages/-ember-data/tests/integration/relationships/inverse-relationship-load-test.js +++ b/packages/-ember-data/tests/integration/relationships/inverse-relationship-load-test.js @@ -90,27 +90,27 @@ module('inverse relationship load test', function (hooks) { }, }); - let dogs = await person.get('dogs'); + let dogs = await person.dogs; assert.false(person.hasMany('dogs').hasManyRelationship.state.isEmpty, 'relationship state was set up correctly'); - assert.strictEqual(dogs.get('length'), 2, 'hasMany relationship has correct number of records'); - let dog1 = dogs.get('firstObject'); - let dogPerson1 = await dog1.get('person'); + assert.strictEqual(dogs.length, 2, 'hasMany relationship has correct number of records'); + let dog1 = dogs.firstObject; + let dogPerson1 = await dog1.person; assert.strictEqual( - dogPerson1.get('id'), + dogPerson1.id, '1', 'dog.person inverse relationship is set up correctly when adapter does not include parent relationships in data.relationships' ); - let dogPerson2 = await dogs.objectAt(1).get('person'); + let dogPerson2 = await dogs.objectAt(1).person; assert.strictEqual( - dogPerson2.get('id'), + dogPerson2.id, '1', 'dog.person inverse relationship is set up correctly when adapter does not include parent relationships in data.relationships' ); await dog1.destroyRecord(); - assert.strictEqual(dogs.get('length'), 1, 'record removed from hasMany relationship after deletion'); - assert.strictEqual(dogs.get('firstObject.id'), '2', 'hasMany relationship has correct records'); + assert.strictEqual(dogs.length, 1, 'record removed from hasMany relationship after deletion'); + assert.strictEqual(dogs.firstObject.id, '2', 'hasMany relationship has correct records'); }); test('one-to-many (left hand async, right hand sync) - findHasMany/implicit inverse - adds parent relationship information to the payload if it is not included/added by the serializer', async function (assert) { @@ -176,27 +176,27 @@ module('inverse relationship load test', function (hooks) { }, }); - let dogs = await person.get('dogs'); + let dogs = await person.dogs; assert.false(person.hasMany('dogs').hasManyRelationship.state.isEmpty, 'relationship state was set up correctly'); - assert.strictEqual(dogs.get('length'), 2, 'hasMany relationship has correct number of records'); - let dog1 = dogs.get('firstObject'); - let dogPerson1 = await dog1.get('person'); + assert.strictEqual(dogs.length, 2, 'hasMany relationship has correct number of records'); + let dog1 = dogs.firstObject; + let dogPerson1 = await dog1.person; assert.strictEqual( - dogPerson1.get('id'), + dogPerson1.id, '1', 'dog.person inverse relationship is set up correctly when adapter does not include parent relationships in data.relationships' ); - let dogPerson2 = await dogs.objectAt(1).get('person'); + let dogPerson2 = await dogs.objectAt(1).person; assert.strictEqual( - dogPerson2.get('id'), + dogPerson2.id, '1', 'dog.person inverse relationship is set up correctly when adapter does not include parent relationships in data.relationships' ); await dog1.destroyRecord(); - assert.strictEqual(dogs.get('length'), 1, 'record removed from hasMany relationship after deletion'); - assert.strictEqual(dogs.get('firstObject.id'), '2', 'hasMany relationship has correct records'); + assert.strictEqual(dogs.length, 1, 'record removed from hasMany relationship after deletion'); + assert.strictEqual(dogs.firstObject.id, '2', 'hasMany relationship has correct records'); }); test('one-to-many - findHasMany/explicit inverse - adds parent relationship information to the payload if it is not included/added by the serializer', async function (assert) { @@ -263,27 +263,27 @@ module('inverse relationship load test', function (hooks) { }, }); - let dogs = await person.get('dogs'); + let dogs = await person.dogs; assert.false(person.hasMany('dogs').hasManyRelationship.state.isEmpty, 'relationship state was set up correctly'); - assert.strictEqual(dogs.get('length'), 2, 'hasMany relationship has correct number of records'); - let dog1 = dogs.get('firstObject'); - let dogPerson1 = await dog1.get('pal'); + assert.strictEqual(dogs.length, 2, 'hasMany relationship has correct number of records'); + let dog1 = dogs.firstObject; + let dogPerson1 = await dog1.pal; assert.strictEqual( - dogPerson1.get('id'), + dogPerson1.id, '1', 'dog.person inverse relationship is set up correctly when adapter does not include parent relationships in data.relationships' ); - let dogPerson2 = await dogs.objectAt(1).get('pal'); + let dogPerson2 = await dogs.objectAt(1).pal; assert.strictEqual( - dogPerson2.get('id'), + dogPerson2.id, '1', 'dog.person inverse relationship is set up correctly when adapter does not include parent relationships in data.relationships' ); await dog1.destroyRecord(); - assert.strictEqual(dogs.get('length'), 1, 'record removed from hasMany relationship after deletion'); - assert.strictEqual(dogs.get('firstObject.id'), '2', 'hasMany relationship has correct records'); + assert.strictEqual(dogs.length, 1, 'record removed from hasMany relationship after deletion'); + assert.strictEqual(dogs.firstObject.id, '2', 'hasMany relationship has correct records'); }); test('one-to-many (left hand async, right hand sync) - findHasMany/explicit inverse - adds parent relationship information to the payload if it is not included/added by the serializer', async function (assert) { @@ -351,27 +351,27 @@ module('inverse relationship load test', function (hooks) { }, }); - let dogs = await person.get('dogs'); + let dogs = await person.dogs; assert.false(person.hasMany('dogs').hasManyRelationship.state.isEmpty, 'relationship state was set up correctly'); - assert.strictEqual(dogs.get('length'), 2, 'hasMany relationship has correct number of records'); - let dog1 = dogs.get('firstObject'); - let dogPerson1 = await dog1.get('pal'); + assert.strictEqual(dogs.length, 2, 'hasMany relationship has correct number of records'); + let dog1 = dogs.firstObject; + let dogPerson1 = await dog1.pal; assert.strictEqual( - dogPerson1.get('id'), + dogPerson1.id, '1', 'dog.person inverse relationship is set up correctly when adapter does not include parent relationships in data.relationships' ); - let dogPerson2 = await dogs.objectAt(1).get('pal'); + let dogPerson2 = await dogs.objectAt(1).pal; assert.strictEqual( - dogPerson2.get('id'), + dogPerson2.id, '1', 'dog.person inverse relationship is set up correctly when adapter does not include parent relationships in data.relationships' ); await dog1.destroyRecord(); - assert.strictEqual(dogs.get('length'), 1, 'record removed from hasMany relationship after deletion'); - assert.strictEqual(dogs.get('firstObject.id'), '2', 'hasMany relationship has correct records'); + assert.strictEqual(dogs.length, 1, 'record removed from hasMany relationship after deletion'); + assert.strictEqual(dogs.firstObject.id, '2', 'hasMany relationship has correct records'); }); test('one-to-many - findHasMany/null inverse - adds parent relationship information to the payload if it is not included/added by the serializer', async function (assert) { @@ -443,15 +443,15 @@ module('inverse relationship load test', function (hooks) { }, }); - let dogs = await person.get('dogs'); + let dogs = await person.dogs; assert.false(person.hasMany('dogs').hasManyRelationship.state.isEmpty); - assert.strictEqual(dogs.get('length'), 2); + assert.strictEqual(dogs.length, 2); assert.deepEqual(dogs.mapBy('id'), ['1', '2']); - let dog1 = dogs.get('firstObject'); + let dog1 = dogs.firstObject; await dog1.destroyRecord(); - assert.strictEqual(dogs.get('length'), 1); - assert.strictEqual(dogs.get('firstObject.id'), '2'); + assert.strictEqual(dogs.length, 1); + assert.strictEqual(dogs.firstObject.id, '2'); }); test('one-to-one - findBelongsTo/implicit inverse - ensures inverse relationship is set up when payload does not return parent relationship info', async function (assert) { @@ -512,17 +512,17 @@ module('inverse relationship load test', function (hooks) { }, }); - let favoriteDog = await person.get('favoriteDog'); + let favoriteDog = await person.favoriteDog; assert.false(person.belongsTo('favoriteDog').belongsToRelationship.state.isEmpty); - assert.strictEqual(favoriteDog.get('id'), '1', 'favoriteDog id is set correctly'); - let favoriteDogPerson = await favoriteDog.get('person'); + assert.strictEqual(favoriteDog.id, '1', 'favoriteDog id is set correctly'); + let favoriteDogPerson = await favoriteDog.person; assert.strictEqual( - favoriteDogPerson.get('id'), + favoriteDogPerson.id, '1', 'favoriteDog.person inverse relationship is set up correctly when adapter does not include parent relationships in data.relationships' ); await favoriteDog.destroyRecord(); - favoriteDog = await person.get('favoriteDog'); + favoriteDog = await person.favoriteDog; assert.strictEqual(favoriteDog, null); }); @@ -584,17 +584,17 @@ module('inverse relationship load test', function (hooks) { }, }); - let favoriteDog = await person.get('favoriteDog'); + let favoriteDog = await person.favoriteDog; assert.false(person.belongsTo('favoriteDog').belongsToRelationship.state.isEmpty); - assert.strictEqual(favoriteDog.get('id'), '1', 'favoriteDog id is set correctly'); - let favoriteDogPerson = await favoriteDog.get('person'); + assert.strictEqual(favoriteDog.id, '1', 'favoriteDog id is set correctly'); + let favoriteDogPerson = await favoriteDog.person; assert.strictEqual( - favoriteDogPerson.get('id'), + favoriteDogPerson.id, '1', 'favoriteDog.person inverse relationship is set up correctly when adapter does not include parent relationships in data.relationships' ); await favoriteDog.destroyRecord(); - favoriteDog = await person.get('favoriteDog'); + favoriteDog = await person.favoriteDog; assert.strictEqual(favoriteDog, null); }); @@ -656,17 +656,17 @@ module('inverse relationship load test', function (hooks) { }, }); - let favoriteDog = await person.get('favoriteDog'); + let favoriteDog = await person.favoriteDog; assert.false(person.belongsTo('favoriteDog').belongsToRelationship.state.isEmpty); - assert.strictEqual(favoriteDog.get('id'), '1', 'favoriteDog id is set correctly'); - let favoriteDogPerson = await favoriteDog.get('pal'); + assert.strictEqual(favoriteDog.id, '1', 'favoriteDog id is set correctly'); + let favoriteDogPerson = await favoriteDog.pal; assert.strictEqual( - favoriteDogPerson.get('id'), + favoriteDogPerson.id, '1', 'favoriteDog.pal inverse relationship is set up correctly when adapter does not include parent relationships in data.relationships' ); await favoriteDog.destroyRecord(); - favoriteDog = await person.get('favoriteDog'); + favoriteDog = await person.favoriteDog; assert.strictEqual(favoriteDog, null); }); @@ -728,17 +728,17 @@ module('inverse relationship load test', function (hooks) { }, }); - let favoriteDog = await person.get('favoriteDog'); + let favoriteDog = await person.favoriteDog; assert.false(person.belongsTo('favoriteDog').belongsToRelationship.state.isEmpty); - assert.strictEqual(favoriteDog.get('id'), '1', 'favoriteDog id is set correctly'); - let favoriteDogPerson = await favoriteDog.get('pal'); + assert.strictEqual(favoriteDog.id, '1', 'favoriteDog id is set correctly'); + let favoriteDogPerson = await favoriteDog.pal; assert.strictEqual( - favoriteDogPerson.get('id'), + favoriteDogPerson.id, '1', 'favoriteDog.pal inverse relationship is set up correctly when adapter does not include parent relationships in data.relationships' ); await favoriteDog.destroyRecord(); - favoriteDog = await person.get('favoriteDog'); + favoriteDog = await person.favoriteDog; assert.strictEqual(favoriteDog, null); }); @@ -798,11 +798,11 @@ module('inverse relationship load test', function (hooks) { }, }); - let favoriteDog = await person.get('favoriteDog'); + let favoriteDog = await person.favoriteDog; assert.false(person.belongsTo('favoriteDog').belongsToRelationship.state.isEmpty); - assert.strictEqual(favoriteDog.get('id'), '1', 'favoriteDog id is set correctly'); + assert.strictEqual(favoriteDog.id, '1', 'favoriteDog id is set correctly'); await favoriteDog.destroyRecord(); - favoriteDog = await person.get('favoriteDog'); + favoriteDog = await person.favoriteDog; assert.strictEqual(favoriteDog, null); }); @@ -869,22 +869,22 @@ module('inverse relationship load test', function (hooks) { }, }); - let dogs = await person.get('dogs'); + let dogs = await person.dogs; assert.false(person.hasMany('dogs').hasManyRelationship.state.isEmpty); - assert.strictEqual(dogs.get('length'), 2, 'left hand side relationship is set up with correct number of records'); + assert.strictEqual(dogs.length, 2, 'left hand side relationship is set up with correct number of records'); let [dog1, dog2] = dogs.toArray(); - let dog1Walkers = await dog1.get('walkers'); + let dog1Walkers = await dog1.walkers; assert.strictEqual(dog1Walkers.length, 1, 'dog1.walkers inverse relationship includes correct number of records'); - assert.strictEqual(dog1Walkers.get('firstObject.id'), '1', 'dog1.walkers inverse relationship is set up correctly'); + assert.strictEqual(dog1Walkers.firstObject.id, '1', 'dog1.walkers inverse relationship is set up correctly'); - let dog2Walkers = await dog2.get('walkers'); + let dog2Walkers = await dog2.walkers; assert.strictEqual(dog2Walkers.length, 1, 'dog2.walkers inverse relationship includes correct number of records'); - assert.strictEqual(dog2Walkers.get('firstObject.id'), '1', 'dog2.walkers inverse relationship is set up correctly'); + assert.strictEqual(dog2Walkers.firstObject.id, '1', 'dog2.walkers inverse relationship is set up correctly'); await dog1.destroyRecord(); - assert.strictEqual(dogs.get('length'), 1, 'person.dogs relationship was updated when record removed'); - assert.strictEqual(dogs.get('firstObject.id'), '2', 'person.dogs relationship has the correct records'); + assert.strictEqual(dogs.length, 1, 'person.dogs relationship was updated when record removed'); + assert.strictEqual(dogs.firstObject.id, '2', 'person.dogs relationship has the correct records'); }); test('many-to-many (left hand async, right hand sync) - findHasMany/implicit inverse - adds parent relationship information to the payload if it is not included/added by the serializer', async function (assert) { @@ -950,22 +950,22 @@ module('inverse relationship load test', function (hooks) { }, }); - let dogs = await person.get('dogs'); + let dogs = await person.dogs; assert.false(person.hasMany('dogs').hasManyRelationship.state.isEmpty); - assert.strictEqual(dogs.get('length'), 2, 'left hand side relationship is set up with correct number of records'); + assert.strictEqual(dogs.length, 2, 'left hand side relationship is set up with correct number of records'); let [dog1, dog2] = dogs.toArray(); - let dog1Walkers = await dog1.get('walkers'); + let dog1Walkers = await dog1.walkers; assert.strictEqual(dog1Walkers.length, 1, 'dog1.walkers inverse relationship includes correct number of records'); - assert.strictEqual(dog1Walkers.get('firstObject.id'), '1', 'dog1.walkers inverse relationship is set up correctly'); + assert.strictEqual(dog1Walkers.firstObject.id, '1', 'dog1.walkers inverse relationship is set up correctly'); - let dog2Walkers = await dog2.get('walkers'); + let dog2Walkers = await dog2.walkers; assert.strictEqual(dog2Walkers.length, 1, 'dog2.walkers inverse relationship includes correct number of records'); - assert.strictEqual(dog2Walkers.get('firstObject.id'), '1', 'dog2.walkers inverse relationship is set up correctly'); + assert.strictEqual(dog2Walkers.firstObject.id, '1', 'dog2.walkers inverse relationship is set up correctly'); await dog1.destroyRecord(); - assert.strictEqual(dogs.get('length'), 1, 'person.dogs relationship was updated when record removed'); - assert.strictEqual(dogs.get('firstObject.id'), '2', 'person.dogs relationship has the correct records'); + assert.strictEqual(dogs.length, 1, 'person.dogs relationship was updated when record removed'); + assert.strictEqual(dogs.firstObject.id, '2', 'person.dogs relationship has the correct records'); }); test('many-to-many - findHasMany/explicit inverse - adds parent relationship information to the payload if it is not included/added by the serializer', async function (assert) { @@ -1032,22 +1032,22 @@ module('inverse relationship load test', function (hooks) { }, }); - let dogs = await person.get('dogs'); + let dogs = await person.dogs; assert.false(person.hasMany('dogs').hasManyRelationship.state.isEmpty); - assert.strictEqual(dogs.get('length'), 2, 'left hand side relationship is set up with correct number of records'); + assert.strictEqual(dogs.length, 2, 'left hand side relationship is set up with correct number of records'); let [dog1, dog2] = dogs.toArray(); - let dog1Pals = await dog1.get('pals'); + let dog1Pals = await dog1.pals; assert.strictEqual(dog1Pals.length, 1, 'dog1.pals inverse relationship includes correct number of records'); - assert.strictEqual(dog1Pals.get('firstObject.id'), '1', 'dog1.pals inverse relationship is set up correctly'); + assert.strictEqual(dog1Pals.firstObject.id, '1', 'dog1.pals inverse relationship is set up correctly'); - let dog2Pals = await dog2.get('pals'); + let dog2Pals = await dog2.pals; assert.strictEqual(dog2Pals.length, 1, 'dog2.pals inverse relationship includes correct number of records'); - assert.strictEqual(dog2Pals.get('firstObject.id'), '1', 'dog2.pals inverse relationship is set up correctly'); + assert.strictEqual(dog2Pals.firstObject.id, '1', 'dog2.pals inverse relationship is set up correctly'); await dog1.destroyRecord(); - assert.strictEqual(dogs.get('length'), 1, 'person.dogs relationship was updated when record removed'); - assert.strictEqual(dogs.get('firstObject.id'), '2', 'person.dogs relationship has the correct records'); + assert.strictEqual(dogs.length, 1, 'person.dogs relationship was updated when record removed'); + assert.strictEqual(dogs.firstObject.id, '2', 'person.dogs relationship has the correct records'); }); test('many-to-many (left hand async, right hand sync) - findHasMany/explicit inverse - adds parent relationship information to the payload if it is not included/added by the serializer', async function (assert) { @@ -1114,22 +1114,22 @@ module('inverse relationship load test', function (hooks) { }, }); - let dogs = await person.get('dogs'); + let dogs = await person.dogs; assert.false(person.hasMany('dogs').hasManyRelationship.state.isEmpty); - assert.strictEqual(dogs.get('length'), 2, 'left hand side relationship is set up with correct number of records'); + assert.strictEqual(dogs.length, 2, 'left hand side relationship is set up with correct number of records'); let [dog1, dog2] = dogs.toArray(); - let dog1Pals = await dog1.get('pals'); + let dog1Pals = await dog1.pals; assert.strictEqual(dog1Pals.length, 1, 'dog1.pals inverse relationship includes correct number of records'); - assert.strictEqual(dog1Pals.get('firstObject.id'), '1', 'dog1.pals inverse relationship is set up correctly'); + assert.strictEqual(dog1Pals.firstObject.id, '1', 'dog1.pals inverse relationship is set up correctly'); - let dog2Pals = await dog2.get('pals'); + let dog2Pals = await dog2.pals; assert.strictEqual(dog2Pals.length, 1, 'dog2.pals inverse relationship includes correct number of records'); - assert.strictEqual(dog2Pals.get('firstObject.id'), '1', 'dog2.pals inverse relationship is set up correctly'); + assert.strictEqual(dog2Pals.firstObject.id, '1', 'dog2.pals inverse relationship is set up correctly'); await dog1.destroyRecord(); - assert.strictEqual(dogs.get('length'), 1, 'person.dogs relationship was updated when record removed'); - assert.strictEqual(dogs.get('firstObject.id'), '2', 'person.dogs relationship has the correct records'); + assert.strictEqual(dogs.length, 1, 'person.dogs relationship was updated when record removed'); + assert.strictEqual(dogs.firstObject.id, '2', 'person.dogs relationship has the correct records'); }); test('many-to-one - findBelongsTo/implicit inverse - adds parent relationship information to the payload if it is not included/added by the serializer', async function (assert) { @@ -1186,21 +1186,21 @@ module('inverse relationship load test', function (hooks) { }, }); - let person = await dog.get('person'); + let person = await dog.person; assert.false( dog.belongsTo('person').belongsToRelationship.state.isEmpty, 'belongsTo relationship state was populated' ); - assert.strictEqual(person.get('id'), '1', 'dog.person relationship is correctly set up'); + assert.strictEqual(person.id, '1', 'dog.person relationship is correctly set up'); - let dogs = await person.get('dogs'); + let dogs = await person.dogs; - assert.strictEqual(dogs.get('length'), 1, 'person.dogs inverse relationship includes correct number of records'); + assert.strictEqual(dogs.length, 1, 'person.dogs inverse relationship includes correct number of records'); let [dog1] = dogs.toArray(); assert.strictEqual(dog1.id, '1', 'dog1.person inverse relationship is set up correctly'); await person.destroyRecord(); - dog = await dog.get('person'); + dog = await dog.person; assert.strictEqual(dog, null, 'record deleted removed from belongsTo relationship'); }); @@ -1258,21 +1258,21 @@ module('inverse relationship load test', function (hooks) { }, }); - let person = await dog.get('person'); + let person = await dog.person; assert.false( dog.belongsTo('person').belongsToRelationship.state.isEmpty, 'belongsTo relationship state was populated' ); - assert.strictEqual(person.get('id'), '1', 'dog.person relationship is correctly set up'); + assert.strictEqual(person.id, '1', 'dog.person relationship is correctly set up'); - let dogs = await person.get('dogs'); + let dogs = await person.dogs; - assert.strictEqual(dogs.get('length'), 1, 'person.dogs inverse relationship includes correct number of records'); + assert.strictEqual(dogs.length, 1, 'person.dogs inverse relationship includes correct number of records'); let [dog1] = dogs.toArray(); assert.strictEqual(dog1.id, '1', 'dog1.person inverse relationship is set up correctly'); await person.destroyRecord(); - dog = await dog.get('person'); + dog = await dog.person; assert.strictEqual(dog, null, 'record deleted removed from belongsTo relationship'); }); @@ -1331,21 +1331,21 @@ module('inverse relationship load test', function (hooks) { }, }); - let person = await dog.get('pal'); + let person = await dog.pal; assert.false( dog.belongsTo('pal').belongsToRelationship.state.isEmpty, 'belongsTo relationship state was populated' ); - assert.strictEqual(person.get('id'), '1', 'dog.person relationship is correctly set up'); + assert.strictEqual(person.id, '1', 'dog.person relationship is correctly set up'); - let dogs = await person.get('dogs'); + let dogs = await person.dogs; - assert.strictEqual(dogs.get('length'), 1, 'person.dogs inverse relationship includes correct number of records'); + assert.strictEqual(dogs.length, 1, 'person.dogs inverse relationship includes correct number of records'); let [dog1] = dogs.toArray(); assert.strictEqual(dog1.id, '1', 'dog1.person inverse relationship is set up correctly'); await person.destroyRecord(); - dog = await dog.get('pal'); + dog = await dog.pal; assert.strictEqual(dog, null, 'record deleted removed from belongsTo relationship'); }); @@ -1404,21 +1404,21 @@ module('inverse relationship load test', function (hooks) { }, }); - let person = await dog.get('pal'); + let person = await dog.pal; assert.false( dog.belongsTo('pal').belongsToRelationship.state.isEmpty, 'belongsTo relationship state was populated' ); - assert.strictEqual(person.get('id'), '1', 'dog.person relationship is correctly set up'); + assert.strictEqual(person.id, '1', 'dog.person relationship is correctly set up'); - let dogs = await person.get('dogs'); + let dogs = await person.dogs; - assert.strictEqual(dogs.get('length'), 1, 'person.dogs inverse relationship includes correct number of records'); + assert.strictEqual(dogs.length, 1, 'person.dogs inverse relationship includes correct number of records'); let [dog1] = dogs.toArray(); assert.strictEqual(dog1.id, '1', 'dog1.person inverse relationship is set up correctly'); await person.destroyRecord(); - dog = await dog.get('pal'); + dog = await dog.pal; assert.strictEqual(dog, null, 'record deleted removed from belongsTo relationship'); }); @@ -1504,7 +1504,7 @@ module('inverse relationship load test', function (hooks) { }); await assert.expectAssertion(async () => { - await person.get('dogs'); + await person.dogs; }, /The record loaded at/); } ); @@ -1591,7 +1591,7 @@ module('inverse relationship load test', function (hooks) { }); await assert.expectAssertion(async () => { - await person.get('dogs'); + await person.dogs; }, /The record loaded at/); } ); @@ -1672,7 +1672,7 @@ module('inverse relationship load test', function (hooks) { }); await assert.expectAssertion(async () => { - await person.get('dogs'); + await person.dogs; }, /The record loaded at/); } ); @@ -1753,7 +1753,7 @@ module('inverse relationship load test', function (hooks) { }); await assert.expectAssertion(async () => { - await person.get('dogs'); + await person.dogs; }, /The record loaded at/); } ); @@ -1823,7 +1823,7 @@ module('inverse relationship load test', function (hooks) { }); await assert.expectAssertion(async () => { - await person.get('dog'); + await person.dog; }, /The record loaded at/); } ); @@ -1893,7 +1893,7 @@ module('inverse relationship load test', function (hooks) { }); await assert.expectAssertion(async () => { - await person.get('dog'); + await person.dog; }, /The record loaded at/); } ); @@ -1960,7 +1960,7 @@ module('inverse relationship load test', function (hooks) { }); await assert.expectAssertion(async () => { - await person.get('dog'); + await person.dog; }, /The record loaded at/); } ); @@ -2027,7 +2027,7 @@ module('inverse relationship load test', function (hooks) { }); await assert.expectAssertion(async () => { - await person.get('dog'); + await person.dog; }, /The record loaded at/); } ); @@ -2099,7 +2099,7 @@ module('inverse relationship load test', function (hooks) { }); await assert.expectAssertion(async () => { - await dog.get('person'); + await dog.person; }, /The record loaded at/); } ); @@ -2171,7 +2171,7 @@ module('inverse relationship load test', function (hooks) { }); await assert.expectAssertion(async () => { - await dog.get('person'); + await dog.person; }, /The record loaded at/); } ); @@ -2238,7 +2238,7 @@ module('inverse relationship load test', function (hooks) { }); await assert.expectAssertion(async () => { - await dog.get('person'); + await dog.person; }, /The record loaded at/); } ); @@ -2305,7 +2305,7 @@ module('inverse relationship load test', function (hooks) { }); await assert.expectAssertion(async () => { - await dog.get('person'); + await dog.person; }, /The record loaded at/); } ); @@ -2811,7 +2811,7 @@ module('inverse relationship load test', function (hooks) { }); await assert.expectAssertion(async () => { - await person.get('dogs'); + await person.dogs; }, /The record loaded at data\[0\] in the payload specified null as its/); } ); @@ -2888,27 +2888,27 @@ module('inverse relationship load test', function (hooks) { }, }); - let dogs = await person.get('dogs'); + let dogs = await person.dogs; assert.false(person.hasMany('dogs').hasManyRelationship.state.isEmpty, 'relationship state was set up correctly'); - assert.strictEqual(dogs.get('length'), 2, 'hasMany relationship has correct number of records'); - let dog1 = dogs.get('firstObject'); - let dogPerson1 = await dog1.get('person'); + assert.strictEqual(dogs.length, 2, 'hasMany relationship has correct number of records'); + let dog1 = dogs.firstObject; + let dogPerson1 = await dog1.person; assert.strictEqual( - dogPerson1.get('id'), + dogPerson1.id, '1', 'dog.person inverse relationship is set up correctly when adapter does not include parent relationships in data.relationships' ); - let dogPerson2 = await dogs.objectAt(1).get('person'); + let dogPerson2 = await dogs.objectAt(1).person; assert.strictEqual( - dogPerson2.get('id'), + dogPerson2.id, '1', 'dog.person inverse relationship is set up correctly when adapter does not include parent relationships in data.relationships' ); await dog1.destroyRecord(); - assert.strictEqual(dogs.get('length'), 1, 'record removed from hasMany relationship after deletion'); - assert.strictEqual(dogs.get('firstObject.id'), '2', 'hasMany relationship has correct records'); + assert.strictEqual(dogs.length, 1, 'record removed from hasMany relationship after deletion'); + assert.strictEqual(dogs.firstObject.id, '2', 'hasMany relationship has correct records'); }); test('one-to-many (left hand async, right hand sync) - ids/non-link/implicit inverse - ids - records loaded through ids/findRecord are linked to the parent if the response from the server does not include relationship information', async function (assert) { @@ -2983,27 +2983,27 @@ module('inverse relationship load test', function (hooks) { }, }); - let dogs = await person.get('dogs'); + let dogs = await person.dogs; assert.false(person.hasMany('dogs').hasManyRelationship.state.isEmpty, 'relationship state was set up correctly'); - assert.strictEqual(dogs.get('length'), 2, 'hasMany relationship has correct number of records'); - let dog1 = dogs.get('firstObject'); - let dogPerson1 = await dog1.get('person'); + assert.strictEqual(dogs.length, 2, 'hasMany relationship has correct number of records'); + let dog1 = dogs.firstObject; + let dogPerson1 = await dog1.person; assert.strictEqual( - dogPerson1.get('id'), + dogPerson1.id, '1', 'dog.person inverse relationship is set up correctly when adapter does not include parent relationships in data.relationships' ); - let dogPerson2 = await dogs.objectAt(1).get('person'); + let dogPerson2 = await dogs.objectAt(1).person; assert.strictEqual( - dogPerson2.get('id'), + dogPerson2.id, '1', 'dog.person inverse relationship is set up correctly when adapter does not include parent relationships in data.relationships' ); await dog1.destroyRecord(); - assert.strictEqual(dogs.get('length'), 1, 'record removed from hasMany relationship after deletion'); - assert.strictEqual(dogs.get('firstObject.id'), '2', 'hasMany relationship has correct records'); + assert.strictEqual(dogs.length, 1, 'record removed from hasMany relationship after deletion'); + assert.strictEqual(dogs.firstObject.id, '2', 'hasMany relationship has correct records'); }); test('one-to-many - ids/non-link/explicit inverse - ids - records loaded through ids/findRecord are linked to the parent if the response from the server does not include relationship information', async function (assert) { @@ -3079,27 +3079,27 @@ module('inverse relationship load test', function (hooks) { }, }); - let dogs = await person.get('dogs'); + let dogs = await person.dogs; assert.false(person.hasMany('dogs').hasManyRelationship.state.isEmpty, 'relationship state was set up correctly'); - assert.strictEqual(dogs.get('length'), 2, 'hasMany relationship has correct number of records'); - let dog1 = dogs.get('firstObject'); - let dogPerson1 = await dog1.get('pal'); + assert.strictEqual(dogs.length, 2, 'hasMany relationship has correct number of records'); + let dog1 = dogs.firstObject; + let dogPerson1 = await dog1.pal; assert.strictEqual( - dogPerson1.get('id'), + dogPerson1.id, '1', 'dog.person inverse relationship is set up correctly when adapter does not include parent relationships in data.relationships' ); - let dogPerson2 = await dogs.objectAt(1).get('pal'); + let dogPerson2 = await dogs.objectAt(1).pal; assert.strictEqual( - dogPerson2.get('id'), + dogPerson2.id, '1', 'dog.person inverse relationship is set up correctly when adapter does not include parent relationships in data.relationships' ); await dog1.destroyRecord(); - assert.strictEqual(dogs.get('length'), 1, 'record removed from hasMany relationship after deletion'); - assert.strictEqual(dogs.get('firstObject.id'), '2', 'hasMany relationship has correct records'); + assert.strictEqual(dogs.length, 1, 'record removed from hasMany relationship after deletion'); + assert.strictEqual(dogs.firstObject.id, '2', 'hasMany relationship has correct records'); }); test('one-to-many (left hand async, right hand sync) - ids/non-link/explicit inverse - ids - records loaded through ids/findRecord are linked to the parent if the response from the server does not include relationship information', async function (assert) { @@ -3175,27 +3175,27 @@ module('inverse relationship load test', function (hooks) { }, }); - let dogs = await person.get('dogs'); + let dogs = await person.dogs; assert.false(person.hasMany('dogs').hasManyRelationship.state.isEmpty, 'relationship state was set up correctly'); - assert.strictEqual(dogs.get('length'), 2, 'hasMany relationship has correct number of records'); - let dog1 = dogs.get('firstObject'); - let dogPerson1 = await dog1.get('pal'); + assert.strictEqual(dogs.length, 2, 'hasMany relationship has correct number of records'); + let dog1 = dogs.firstObject; + let dogPerson1 = await dog1.pal; assert.strictEqual( - dogPerson1.get('id'), + dogPerson1.id, '1', 'dog.person inverse relationship is set up correctly when adapter does not include parent relationships in data.relationships' ); - let dogPerson2 = await dogs.objectAt(1).get('pal'); + let dogPerson2 = await dogs.objectAt(1).pal; assert.strictEqual( - dogPerson2.get('id'), + dogPerson2.id, '1', 'dog.person inverse relationship is set up correctly when adapter does not include parent relationships in data.relationships' ); await dog1.destroyRecord(); - assert.strictEqual(dogs.get('length'), 1, 'record removed from hasMany relationship after deletion'); - assert.strictEqual(dogs.get('firstObject.id'), '2', 'hasMany relationship has correct records'); + assert.strictEqual(dogs.length, 1, 'record removed from hasMany relationship after deletion'); + assert.strictEqual(dogs.firstObject.id, '2', 'hasMany relationship has correct records'); }); test('one-to-many - ids/non-link/null inverse - ids - records loaded through ids/findRecord are linked to the parent if the response from the server does not include relationship information', async function (assert) { @@ -3266,15 +3266,15 @@ module('inverse relationship load test', function (hooks) { }, }); - let dogs = await person.get('dogs'); + let dogs = await person.dogs; assert.false(person.hasMany('dogs').hasManyRelationship.state.isEmpty, 'relationship state was set up correctly'); - assert.strictEqual(dogs.get('length'), 2, 'hasMany relationship has correct number of records'); - let dog1 = dogs.get('firstObject'); + assert.strictEqual(dogs.length, 2, 'hasMany relationship has correct number of records'); + let dog1 = dogs.firstObject; await dog1.destroyRecord(); - assert.strictEqual(dogs.get('length'), 1, 'record removed from hasMany relationship after deletion'); - assert.strictEqual(dogs.get('firstObject.id'), '2', 'hasMany relationship has correct records'); + assert.strictEqual(dogs.length, 1, 'record removed from hasMany relationship after deletion'); + assert.strictEqual(dogs.firstObject.id, '2', 'hasMany relationship has correct records'); }); test('one-to-many - ids/non-link/implicit inverse - records loaded through ids/findRecord do not get associated with the parent if the server specifies another resource as the relationship value in the response', async function (assert) { @@ -3375,14 +3375,14 @@ module('inverse relationship load test', function (hooks) { }, }); - let dogs = await person.get('dogs'); + let dogs = await person.dogs; assert.false(person.hasMany('dogs').hasManyRelationship.state.isEmpty, 'relationship state was set up correctly'); - assert.strictEqual(dogs.get('length'), 0, 'hasMany relationship for parent is empty'); + assert.strictEqual(dogs.length, 0, 'hasMany relationship for parent is empty'); - let person2Dogs = await person2.get('dogs'); + let person2Dogs = await person2.dogs; assert.strictEqual( - person2Dogs.get('length'), + person2Dogs.length, 2, 'hasMany relationship on specified record has correct number of associated records' ); @@ -3390,14 +3390,14 @@ module('inverse relationship load test', function (hooks) { let allDogs = store.peekAll('dogs').toArray(); for (let i = 0; i < allDogs.length; i++) { let dog = allDogs[i]; - let dogPerson = await dog.get('person'); - assert.strictEqual(dogPerson.get('id'), person2.get('id'), 'right hand side has correct belongsTo value'); + let dogPerson = await dog.person; + assert.strictEqual(dogPerson.id, person2.id, 'right hand side has correct belongsTo value'); } let dog1 = store.peekRecord('dog', '1'); await dog1.destroyRecord(); - assert.strictEqual(person2Dogs.get('length'), 1, 'record removed from hasMany relationship after deletion'); - assert.strictEqual(person2Dogs.get('firstObject.id'), '2', 'hasMany relationship has correct records'); + assert.strictEqual(person2Dogs.length, 1, 'record removed from hasMany relationship after deletion'); + assert.strictEqual(person2Dogs.firstObject.id, '2', 'hasMany relationship has correct records'); }); test('one-to-many (left hand async, right hand sync) - ids/non-link/implicit inverse - records loaded through ids/findRecord do not get associated with the parent if the server specifies another resource as the relationship value in the response', async function (assert) { @@ -3498,14 +3498,14 @@ module('inverse relationship load test', function (hooks) { }, }); - let dogs = await person.get('dogs'); + let dogs = await person.dogs; assert.false(person.hasMany('dogs').hasManyRelationship.state.isEmpty, 'relationship state was set up correctly'); - assert.strictEqual(dogs.get('length'), 0, 'hasMany relationship for parent is empty'); + assert.strictEqual(dogs.length, 0, 'hasMany relationship for parent is empty'); - let person2Dogs = await person2.get('dogs'); + let person2Dogs = await person2.dogs; assert.strictEqual( - person2Dogs.get('length'), + person2Dogs.length, 2, 'hasMany relationship on specified record has correct number of associated records' ); @@ -3513,14 +3513,14 @@ module('inverse relationship load test', function (hooks) { let allDogs = store.peekAll('dogs').toArray(); for (let i = 0; i < allDogs.length; i++) { let dog = allDogs[i]; - let dogPerson = await dog.get('person'); - assert.strictEqual(dogPerson.get('id'), person2.get('id'), 'right hand side has correct belongsTo value'); + let dogPerson = await dog.person; + assert.strictEqual(dogPerson.id, person2.id, 'right hand side has correct belongsTo value'); } let dog1 = store.peekRecord('dog', '1'); await dog1.destroyRecord(); - assert.strictEqual(person2Dogs.get('length'), 1, 'record removed from hasMany relationship after deletion'); - assert.strictEqual(person2Dogs.get('firstObject.id'), '2', 'hasMany relationship has correct records'); + assert.strictEqual(person2Dogs.length, 1, 'record removed from hasMany relationship after deletion'); + assert.strictEqual(person2Dogs.firstObject.id, '2', 'hasMany relationship has correct records'); }); test('one-to-many - ids/non-link/implicit inverse - records loaded through ids/findRecord do not get associated with the parent if the server specifies null as the relationship value in the response', async function (assert) { @@ -3605,22 +3605,22 @@ module('inverse relationship load test', function (hooks) { }, }); - let personDogs = await person.get('dogs'); + let personDogs = await person.dogs; assert.false(person.hasMany('dogs').hasManyRelationship.state.isEmpty, 'relationship state was set up correctly'); - assert.strictEqual(personDogs.get('length'), 0, 'hasMany relationship for parent is empty'); + assert.strictEqual(personDogs.length, 0, 'hasMany relationship for parent is empty'); let allDogs = store.peekAll('dogs').toArray(); for (let i = 0; i < allDogs.length; i++) { let dog = allDogs[i]; - let dogPerson = await dog.get('person'); + let dogPerson = await dog.person; assert.strictEqual(dogPerson, null, 'right hand side has correct belongsTo value'); } let dog1 = store.peekRecord('dog', '1'); await dog1.destroyRecord(); - assert.strictEqual(personDogs.get('length'), 0); + assert.strictEqual(personDogs.length, 0); }); test('one-to-many (left hand async, right hand sync) - ids/non-link/implicit inverse - records loaded through ids/findRecord do not get associated with the parent if the server specifies null as the relationship value in the response', async function (assert) { @@ -3705,22 +3705,22 @@ module('inverse relationship load test', function (hooks) { }, }); - let personDogs = await person.get('dogs'); + let personDogs = await person.dogs; assert.false(person.hasMany('dogs').hasManyRelationship.state.isEmpty, 'relationship state was set up correctly'); - assert.strictEqual(personDogs.get('length'), 0, 'hasMany relationship for parent is empty'); + assert.strictEqual(personDogs.length, 0, 'hasMany relationship for parent is empty'); let allDogs = store.peekAll('dogs').toArray(); for (let i = 0; i < allDogs.length; i++) { let dog = allDogs[i]; - let dogPerson = await dog.get('person'); + let dogPerson = await dog.person; assert.strictEqual(dogPerson, null, 'right hand side has correct belongsTo value'); } let dog1 = store.peekRecord('dog', '1'); await dog1.destroyRecord(); - assert.strictEqual(personDogs.get('length'), 0); + assert.strictEqual(personDogs.length, 0); }); test('one-to-many - ids/non-link/explicit inverse - records loaded through ids/findRecord do not get associated with the parent if the server specifies another resource as the relationship value in the response', async function (assert) { @@ -3821,14 +3821,14 @@ module('inverse relationship load test', function (hooks) { }, }); - let dogs = await pal.get('dogs'); + let dogs = await pal.dogs; assert.false(pal.hasMany('dogs').hasManyRelationship.state.isEmpty, 'relationship state was set up correctly'); - assert.strictEqual(dogs.get('length'), 0, 'hasMany relationship for parent is empty'); + assert.strictEqual(dogs.length, 0, 'hasMany relationship for parent is empty'); - let pal2Dogs = await pal2.get('dogs'); + let pal2Dogs = await pal2.dogs; assert.strictEqual( - pal2Dogs.get('length'), + pal2Dogs.length, 2, 'hasMany relationship on specified record has correct number of associated records' ); @@ -3836,14 +3836,14 @@ module('inverse relationship load test', function (hooks) { let allDogs = store.peekAll('dogs').toArray(); for (let i = 0; i < allDogs.length; i++) { let dog = allDogs[i]; - let dogPerson = await dog.get('pal'); - assert.strictEqual(dogPerson.get('id'), pal2.get('id'), 'right hand side has correct belongsTo value'); + let dogPerson = await dog.pal; + assert.strictEqual(dogPerson.id, pal2.id, 'right hand side has correct belongsTo value'); } let dog1 = store.peekRecord('dog', '1'); await dog1.destroyRecord(); - assert.strictEqual(pal2Dogs.get('length'), 1, 'record removed from hasMany relationship after deletion'); - assert.strictEqual(pal2Dogs.get('firstObject.id'), '2', 'hasMany relationship has correct records'); + assert.strictEqual(pal2Dogs.length, 1, 'record removed from hasMany relationship after deletion'); + assert.strictEqual(pal2Dogs.firstObject.id, '2', 'hasMany relationship has correct records'); }); test('one-to-many (left hand async, right hand sync) - ids/non-link/explicit inverse - records loaded through ids/findRecord do not get associated with the parent if the server specifies another resource as the relationship value in the response', async function (assert) { @@ -3944,14 +3944,14 @@ module('inverse relationship load test', function (hooks) { }, }); - let dogs = await pal.get('dogs'); + let dogs = await pal.dogs; assert.false(pal.hasMany('dogs').hasManyRelationship.state.isEmpty, 'relationship state was set up correctly'); - assert.strictEqual(dogs.get('length'), 0, 'hasMany relationship for parent is empty'); + assert.strictEqual(dogs.length, 0, 'hasMany relationship for parent is empty'); - let pal2Dogs = await pal2.get('dogs'); + let pal2Dogs = await pal2.dogs; assert.strictEqual( - pal2Dogs.get('length'), + pal2Dogs.length, 2, 'hasMany relationship on specified record has correct number of associated records' ); @@ -3959,14 +3959,14 @@ module('inverse relationship load test', function (hooks) { let allDogs = store.peekAll('dogs').toArray(); for (let i = 0; i < allDogs.length; i++) { let dog = allDogs[i]; - let dogPerson = await dog.get('pal'); - assert.strictEqual(dogPerson.get('id'), pal2.get('id'), 'right hand side has correct belongsTo value'); + let dogPerson = await dog.pal; + assert.strictEqual(dogPerson.id, pal2.id, 'right hand side has correct belongsTo value'); } let dog1 = store.peekRecord('dog', '1'); await dog1.destroyRecord(); - assert.strictEqual(pal2Dogs.get('length'), 1, 'record removed from hasMany relationship after deletion'); - assert.strictEqual(pal2Dogs.get('firstObject.id'), '2', 'hasMany relationship has correct records'); + assert.strictEqual(pal2Dogs.length, 1, 'record removed from hasMany relationship after deletion'); + assert.strictEqual(pal2Dogs.firstObject.id, '2', 'hasMany relationship has correct records'); }); test("loading belongsTo doesn't remove inverse relationship for other instances", async function (assert) { @@ -4057,7 +4057,7 @@ module('inverse relationship load test', function (hooks) { assert.strictEqual(dog1.belongsTo('person').id(), '1'); assert.strictEqual(dog2.belongsTo('person').id(), '1'); - await dog1.get('person'); + await dog1.person; assert.strictEqual(dog1.belongsTo('person').id(), '1'); assert.strictEqual(dog2.belongsTo('person').id(), '1'); diff --git a/packages/-ember-data/tests/integration/relationships/inverse-relationships-test.js b/packages/-ember-data/tests/integration/relationships/inverse-relationships-test.js index dd9152a8a24..8f7736a25cf 100644 --- a/packages/-ember-data/tests/integration/relationships/inverse-relationships-test.js +++ b/packages/-ember-data/tests/integration/relationships/inverse-relationships-test.js @@ -4,6 +4,7 @@ import { setupTest } from 'ember-qunit'; import Model, { attr, belongsTo, hasMany } from '@ember-data/model'; import { graphFor } from '@ember-data/record-data/-private'; +import { recordIdentifierFor } from '@ember-data/store'; import { recordDataFor } from '@ember-data/store/-private'; import testInDebug from '@ember-data/unpublished-test-infra/test-support/test-in-debug'; @@ -37,10 +38,10 @@ module('integration/relationships/inverse_relationships - Inverse Relationships' const comment = store.createRecord('comment'); const post = store.createRecord('post'); - assert.strictEqual(comment.get('post'), null, 'no post has been set on the comment'); + assert.strictEqual(comment.post, null, 'no post has been set on the comment'); - post.get('comments').pushObject(comment); - assert.strictEqual(comment.get('post'), post, 'post was set on the comment'); + post.comments.pushObject(comment); + assert.strictEqual(comment.post, post, 'post was set on the comment'); }); test('Inverse relationships can be explicitly nullable', function (assert) { @@ -119,17 +120,17 @@ module('integration/relationships/inverse_relationships - Inverse Relationships' const comment = store.createRecord('comment'); const post = store.createRecord('post'); - assert.strictEqual(comment.get('onePost'), null, 'onePost has not been set on the comment'); - assert.strictEqual(comment.get('twoPost'), null, 'twoPost has not been set on the comment'); - assert.strictEqual(comment.get('redPost'), null, 'redPost has not been set on the comment'); - assert.strictEqual(comment.get('bluePost'), null, 'bluePost has not been set on the comment'); + assert.strictEqual(comment.onePost, null, 'onePost has not been set on the comment'); + assert.strictEqual(comment.twoPost, null, 'twoPost has not been set on the comment'); + assert.strictEqual(comment.redPost, null, 'redPost has not been set on the comment'); + assert.strictEqual(comment.bluePost, null, 'bluePost has not been set on the comment'); - post.get('comments').pushObject(comment); + post.comments.pushObject(comment); - assert.strictEqual(comment.get('onePost'), null, 'onePost has not been set on the comment'); - assert.strictEqual(comment.get('twoPost'), null, 'twoPost has not been set on the comment'); - assert.strictEqual(comment.get('redPost'), post, 'redPost has been set on the comment'); - assert.strictEqual(comment.get('bluePost'), null, 'bluePost has not been set on the comment'); + assert.strictEqual(comment.onePost, null, 'onePost has not been set on the comment'); + assert.strictEqual(comment.twoPost, null, 'twoPost has not been set on the comment'); + assert.strictEqual(comment.redPost, post, 'redPost has been set on the comment'); + assert.strictEqual(comment.bluePost, null, 'bluePost has not been set on the comment'); }); test("When a record's belongsTo relationship is set, it can specify the inverse hasMany to which the new child should be added", async function (assert) { @@ -157,17 +158,17 @@ module('integration/relationships/inverse_relationships - Inverse Relationships' comment = store.createRecord('comment'); post = store.createRecord('post'); - assert.strictEqual(post.get('meComments.length'), 0, 'meComments has no posts'); - assert.strictEqual(post.get('youComments.length'), 0, 'youComments has no posts'); - assert.strictEqual(post.get('everyoneWeKnowComments.length'), 0, 'everyoneWeKnowComments has no posts'); + assert.strictEqual(post.meComments.length, 0, 'meComments has no posts'); + assert.strictEqual(post.youComments.length, 0, 'youComments has no posts'); + assert.strictEqual(post.everyoneWeKnowComments.length, 0, 'everyoneWeKnowComments has no posts'); comment.set('post', post); - assert.strictEqual(comment.get('post'), post, 'The post that was set can be retrieved'); + assert.strictEqual(comment.post, post, 'The post that was set can be retrieved'); - assert.strictEqual(post.get('meComments.length'), 0, 'meComments has no posts'); - assert.strictEqual(post.get('youComments.length'), 1, 'youComments had the post added'); - assert.strictEqual(post.get('everyoneWeKnowComments.length'), 0, 'everyoneWeKnowComments has no posts'); + assert.strictEqual(post.meComments.length, 0, 'meComments has no posts'); + assert.strictEqual(post.youComments.length, 1, 'youComments had the post added'); + assert.strictEqual(post.everyoneWeKnowComments.length, 0, 'everyoneWeKnowComments has no posts'); }); test('When setting a belongsTo, the OneToOne invariant is respected even when other records have been previously used', async function (assert) { @@ -191,15 +192,15 @@ module('integration/relationships/inverse_relationships - Inverse Relationships' comment.set('post', post); post2.set('bestComment', null); - assert.strictEqual(comment.get('post'), post); - assert.strictEqual(post.get('bestComment'), comment); - assert.strictEqual(post2.get('bestComment'), null); + assert.strictEqual(comment.post, post); + assert.strictEqual(post.bestComment, comment); + assert.strictEqual(post2.bestComment, null); comment.set('post', post2); - assert.strictEqual(comment.get('post'), post2); - assert.strictEqual(post.get('bestComment'), null); - assert.strictEqual(post2.get('bestComment'), comment); + assert.strictEqual(comment.post, post2); + assert.strictEqual(post.bestComment, null); + assert.strictEqual(post2.bestComment, comment); }); test('When setting a belongsTo, the OneToOne invariant is transitive', async function (assert) { @@ -222,15 +223,15 @@ module('integration/relationships/inverse_relationships - Inverse Relationships' comment.set('post', post); - assert.strictEqual(comment.get('post'), post, 'comment post is set correctly'); - assert.strictEqual(post.get('bestComment'), comment, 'post1 comment is set correctly'); - assert.strictEqual(post2.get('bestComment'), null, 'post2 comment is not set'); + assert.strictEqual(comment.post, post, 'comment post is set correctly'); + assert.strictEqual(post.bestComment, comment, 'post1 comment is set correctly'); + assert.strictEqual(post2.bestComment, null, 'post2 comment is not set'); post2.set('bestComment', comment); - assert.strictEqual(comment.get('post'), post2, 'comment post is set correctly'); - assert.strictEqual(post.get('bestComment'), null, 'post1 comment is no longer set'); - assert.strictEqual(post2.get('bestComment'), comment, 'post2 comment is set correctly'); + assert.strictEqual(comment.post, post2, 'comment post is set correctly'); + assert.strictEqual(post.bestComment, null, 'post1 comment is no longer set'); + assert.strictEqual(post2.bestComment, comment, 'post2 comment is set correctly'); }); test('When setting a belongsTo, the OneToOne invariant is commutative', async function (assert) { @@ -253,15 +254,15 @@ module('integration/relationships/inverse_relationships - Inverse Relationships' comment.set('post', post); - assert.strictEqual(comment.get('post'), post); - assert.strictEqual(post.get('bestComment'), comment); - assert.strictEqual(comment2.get('post'), null); + assert.strictEqual(comment.post, post); + assert.strictEqual(post.bestComment, comment); + assert.strictEqual(comment2.post, null); post.set('bestComment', comment2); - assert.strictEqual(comment.get('post'), null); - assert.strictEqual(post.get('bestComment'), comment2); - assert.strictEqual(comment2.get('post'), post); + assert.strictEqual(comment.post, null); + assert.strictEqual(post.bestComment, comment2); + assert.strictEqual(comment2.post, post); }); test('OneToNone relationship works', async function (assert) { @@ -285,13 +286,13 @@ module('integration/relationships/inverse_relationships - Inverse Relationships' const post2 = store.createRecord('post'); comment.set('post', post1); - assert.strictEqual(comment.get('post'), post1, 'the post is set to the first one'); + assert.strictEqual(comment.post, post1, 'the post is set to the first one'); comment.set('post', post2); - assert.strictEqual(comment.get('post'), post2, 'the post is set to the second one'); + assert.strictEqual(comment.post, post2, 'the post is set to the second one'); comment.set('post', post1); - assert.strictEqual(comment.get('post'), post1, 'the post is re-set to the first one'); + assert.strictEqual(comment.post, post1, 'the post is re-set to the first one'); }); test('When a record is added to or removed from a polymorphic has-many relationship, the inverse belongsTo can be set explicitly', async function (assert) { @@ -323,24 +324,24 @@ module('integration/relationships/inverse_relationships - Inverse Relationships' const post = store.createRecord('post'); const user = store.createRecord('user'); - assert.strictEqual(post.get('oneUser'), null, 'oneUser has not been set on the user'); - assert.strictEqual(post.get('twoUser'), null, 'twoUser has not been set on the user'); - assert.strictEqual(post.get('redUser'), null, 'redUser has not been set on the user'); - assert.strictEqual(post.get('blueUser'), null, 'blueUser has not been set on the user'); + assert.strictEqual(post.oneUser, null, 'oneUser has not been set on the user'); + assert.strictEqual(post.twoUser, null, 'twoUser has not been set on the user'); + assert.strictEqual(post.redUser, null, 'redUser has not been set on the user'); + assert.strictEqual(post.blueUser, null, 'blueUser has not been set on the user'); - user.get('messages').pushObject(post); + user.messages.pushObject(post); - assert.strictEqual(post.get('oneUser'), null, 'oneUser has not been set on the user'); - assert.strictEqual(post.get('twoUser'), null, 'twoUser has not been set on the user'); - assert.strictEqual(post.get('redUser'), user, 'redUser has been set on the user'); - assert.strictEqual(post.get('blueUser'), null, 'blueUser has not been set on the user'); + assert.strictEqual(post.oneUser, null, 'oneUser has not been set on the user'); + assert.strictEqual(post.twoUser, null, 'twoUser has not been set on the user'); + assert.strictEqual(post.redUser, user, 'redUser has been set on the user'); + assert.strictEqual(post.blueUser, null, 'blueUser has not been set on the user'); - user.get('messages').popObject(); + user.messages.popObject(); - assert.strictEqual(post.get('oneUser'), null, 'oneUser has not been set on the user'); - assert.strictEqual(post.get('twoUser'), null, 'twoUser has not been set on the user'); - assert.strictEqual(post.get('redUser'), null, 'redUser has bot been set on the user'); - assert.strictEqual(post.get('blueUser'), null, 'blueUser has not been set on the user'); + assert.strictEqual(post.oneUser, null, 'oneUser has not been set on the user'); + assert.strictEqual(post.twoUser, null, 'twoUser has not been set on the user'); + assert.strictEqual(post.redUser, null, 'redUser has bot been set on the user'); + assert.strictEqual(post.blueUser, null, 'blueUser has not been set on the user'); }); test("When a record's belongsTo relationship is set, it can specify the inverse polymorphic hasMany to which the new child should be added or removed", async function (assert) { @@ -369,21 +370,21 @@ module('integration/relationships/inverse_relationships - Inverse Relationships' const user = store.createRecord('user'); const post = store.createRecord('post'); - assert.strictEqual(user.get('meMessages.length'), 0, 'meMessages has no posts'); - assert.strictEqual(user.get('youMessages.length'), 0, 'youMessages has no posts'); - assert.strictEqual(user.get('everyoneWeKnowMessages.length'), 0, 'everyoneWeKnowMessages has no posts'); + assert.strictEqual(user.meMessages.length, 0, 'meMessages has no posts'); + assert.strictEqual(user.youMessages.length, 0, 'youMessages has no posts'); + assert.strictEqual(user.everyoneWeKnowMessages.length, 0, 'everyoneWeKnowMessages has no posts'); post.set('user', user); - assert.strictEqual(user.get('meMessages.length'), 0, 'meMessages has no posts'); - assert.strictEqual(user.get('youMessages.length'), 1, 'youMessages had the post added'); - assert.strictEqual(user.get('everyoneWeKnowMessages.length'), 0, 'everyoneWeKnowMessages has no posts'); + assert.strictEqual(user.meMessages.length, 0, 'meMessages has no posts'); + assert.strictEqual(user.youMessages.length, 1, 'youMessages had the post added'); + assert.strictEqual(user.everyoneWeKnowMessages.length, 0, 'everyoneWeKnowMessages has no posts'); post.set('user', null); - assert.strictEqual(user.get('meMessages.length'), 0, 'meMessages has no posts'); - assert.strictEqual(user.get('youMessages.length'), 0, 'youMessages has no posts'); - assert.strictEqual(user.get('everyoneWeKnowMessages.length'), 0, 'everyoneWeKnowMessages has no posts'); + assert.strictEqual(user.meMessages.length, 0, 'meMessages has no posts'); + assert.strictEqual(user.youMessages.length, 0, 'youMessages has no posts'); + assert.strictEqual(user.everyoneWeKnowMessages.length, 0, 'everyoneWeKnowMessages has no posts'); }); test("When a record's polymorphic belongsTo relationship is set, it can specify the inverse hasMany to which the new child should be added", async function (assert) { @@ -412,21 +413,21 @@ module('integration/relationships/inverse_relationships - Inverse Relationships' const comment = store.createRecord('comment'); const post = store.createRecord('post'); - assert.strictEqual(post.get('meMessages.length'), 0, 'meMessages has no posts'); - assert.strictEqual(post.get('youMessages.length'), 0, 'youMessages has no posts'); - assert.strictEqual(post.get('everyoneWeKnowMessages.length'), 0, 'everyoneWeKnowMessages has no posts'); + assert.strictEqual(post.meMessages.length, 0, 'meMessages has no posts'); + assert.strictEqual(post.youMessages.length, 0, 'youMessages has no posts'); + assert.strictEqual(post.everyoneWeKnowMessages.length, 0, 'everyoneWeKnowMessages has no posts'); comment.set('message', post); - assert.strictEqual(post.get('meMessages.length'), 0, 'meMessages has no posts'); - assert.strictEqual(post.get('youMessages.length'), 1, 'youMessages had the post added'); - assert.strictEqual(post.get('everyoneWeKnowMessages.length'), 0, 'everyoneWeKnowMessages has no posts'); + assert.strictEqual(post.meMessages.length, 0, 'meMessages has no posts'); + assert.strictEqual(post.youMessages.length, 1, 'youMessages had the post added'); + assert.strictEqual(post.everyoneWeKnowMessages.length, 0, 'everyoneWeKnowMessages has no posts'); comment.set('message', null); - assert.strictEqual(post.get('meMessages.length'), 0, 'meMessages has no posts'); - assert.strictEqual(post.get('youMessages.length'), 0, 'youMessages has no posts'); - assert.strictEqual(post.get('everyoneWeKnowMessages.length'), 0, 'everyoneWeKnowMessages has no posts'); + assert.strictEqual(post.meMessages.length, 0, 'meMessages has no posts'); + assert.strictEqual(post.youMessages.length, 0, 'youMessages has no posts'); + assert.strictEqual(post.everyoneWeKnowMessages.length, 0, 'everyoneWeKnowMessages has no posts'); }); testInDebug("Inverse relationships that don't exist throw a nice error for a hasMany", async function (assert) { @@ -449,7 +450,7 @@ module('integration/relationships/inverse_relationships - Inverse Relationships' assert.expectAssertion(function () { post = store.createRecord('post'); - post.get('comments'); + post.comments; }, /We found no inverse relationships by the name of 'testPost' on the 'comment' model/); }); @@ -472,7 +473,7 @@ module('integration/relationships/inverse_relationships - Inverse Relationships' assert.expectAssertion(function () { post = store.createRecord('post'); - post.get('user'); + post.user; }, /We found no inverse relationships by the name of 'testPost' on the 'user' model/); }); @@ -671,13 +672,12 @@ module('integration/relationships/inverse_relationships - Inverse Relationships' const comment = store.createRecord('comment'); const recordData = recordDataFor(comment); const post = store.createRecord('post'); - - post.get('comments').pushObject(comment); + const comments = await post.comments; + comments.pushObject(comment); + const identifier = recordIdentifierFor(comment); await comment.destroyRecord(); - const identifier = comment._internalModel.identifier; - assert.false(graphFor(store).identifiers.has(identifier), 'relationships are cleared'); assert.ok(recordData.isDestroyed, 'recordData is destroyed'); }); diff --git a/packages/-ember-data/tests/integration/relationships/json-api-links-test.js b/packages/-ember-data/tests/integration/relationships/json-api-links-test.js index ccf48f82150..ebee772330c 100644 --- a/packages/-ember-data/tests/integration/relationships/json-api-links-test.js +++ b/packages/-ember-data/tests/integration/relationships/json-api-links-test.js @@ -80,7 +80,7 @@ module('integration/relationship/json-api-links | Relationship state updates', f assert.strictEqual(user1.belongsTo('organisation').remoteType(), 'id', `user's belongsTo is based on id`); assert.strictEqual(user1.belongsTo('organisation').id(), '1', `user's belongsTo has its id populated`); - return user1.get('organisation').then((orgFromUser) => { + return user1.organisation.then((orgFromUser) => { assert.false( user1.belongsTo('organisation').belongsToRelationship.state.isStale, 'user should have loaded its belongsTo relationship' @@ -714,15 +714,15 @@ module('integration/relationship/json-api-links | Relationship fetching', functi // setup user let user = run(() => store.push(deepCopy(payloads.user))); - let pets = run(() => user.get('pets')); + let pets = run(() => user.pets); assert.ok(!!pets, 'We found our pets'); run(() => pets.reload()); }); - test(`get+unload+get hasMany with ${description}`, function (assert) { - assert.expect(3); + test(`get+unload+get hasMany with ${description}`, async function (assert) { + assert.expect(5); let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); @@ -755,14 +755,16 @@ module('integration/relationship/json-api-links | Relationship fetching', functi }; // setup user - let user = run(() => store.push(deepCopy(payloads.user))); - let pets = run(() => user.get('pets')); + let user = store.push(deepCopy(payloads.user)); + let pets = await user.pets; assert.ok(!!pets, 'We found our pets'); if (!petRelDataWasEmpty) { - run(() => pets.objectAt(0).unloadRecord()); - run(() => user.get('pets')); + pets.objectAt(0).unloadRecord(); + assert.strictEqual(pets.length, 0, 'we unloaded'); + await user.pets; + assert.strictEqual(pets.length, 1, 'we reloaded'); } else { assert.ok(true, `We cant dirty a relationship we have no knowledge of`); } @@ -802,7 +804,7 @@ module('integration/relationship/json-api-links | Relationship fetching', functi // setup user let user = run(() => store.push(deepCopy(payloads.user))); - let home = run(() => user.get('home')); + let home = run(() => user.home); if (homeRelWasEmpty) { assert.notOk(didFetchInitially, 'We did not fetch'); @@ -840,13 +842,13 @@ module('integration/relationship/json-api-links | Relationship fetching', functi // setup user let user = run(() => store.push(deepCopy(payloads.user))); - let home = run(() => user.get('home')); + let home = run(() => user.home); assert.ok(!!home, 'We found our home'); if (!homeRelWasEmpty) { run(() => home.then((h) => h.unloadRecord())); - run(() => user.get('home')); + run(() => user.home); } else { assert.ok(true, `We cant dirty a relationship we have no knowledge of`); assert.ok(true, `Nor should we have fetched it.`); @@ -1012,15 +1014,15 @@ module('integration/relationship/json-api-links | Relationship fetching', functi // setup user and pets let user = run(() => store.push(deepCopy(payloads.user))); run(() => store.push(deepCopy(payloads.pets))); - let pets = run(() => user.get('pets')); + let pets = run(() => user.pets); assert.ok(!!pets, 'We found our pets'); run(() => pets.reload()); }); - test(`get+unload+get hasMany with ${description}`, function (assert) { - assert.expect(2); + test(`get+unload+get hasMany with ${description}`, async function (assert) { + assert.expect(4); let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); @@ -1042,14 +1044,16 @@ module('integration/relationship/json-api-links | Relationship fetching', functi }; // setup user and pets - let user = run(() => store.push(deepCopy(payloads.user))); - run(() => store.push(deepCopy(payloads.pets))); - let pets = run(() => user.get('pets')); + let user = store.push(deepCopy(payloads.user)); + store.push(deepCopy(payloads.pets)); + let pets = await user.pets; assert.ok(!!pets, 'We found our pets'); - run(() => pets.objectAt(0).unloadRecord()); - run(() => user.get('pets')); + pets.objectAt(0).unloadRecord(); + assert.strictEqual(pets.length, 0, 'we unloaded our pet'); + await user.pets; + assert.strictEqual(pets.length, 1, 'we have our pet again'); }); test(`get+reload belongsTo with ${description}`, function (assert) { @@ -1077,7 +1081,7 @@ module('integration/relationship/json-api-links | Relationship fetching', functi // setup user and home let user = run(() => store.push(deepCopy(payloads.user))); run(() => store.push(deepCopy(payloads.home))); - let home = run(() => user.get('home')); + let home = run(() => user.home); assert.ok(!!home, 'We found our home'); @@ -1110,12 +1114,12 @@ module('integration/relationship/json-api-links | Relationship fetching', functi let user = run(() => store.push(deepCopy(payloads.user))); run(() => store.push(deepCopy(payloads.home))); let home; - run(() => user.get('home').then((h) => (home = h))); + run(() => user.home.then((h) => (home = h))); assert.ok(!!home, 'We found our home'); run(() => home.unloadRecord()); - run(() => user.get('home')); + run(() => user.home); }); } @@ -1370,15 +1374,15 @@ module('integration/relationship/json-api-links | Relationship fetching', functi }, }) ); - let pets = run(() => user.get('pets')); + let pets = run(() => user.pets); assert.ok(!!pets, 'We found our pets'); run(() => pets.reload()); }); - test(`get+unload+get hasMany with data, no links`, function (assert) { - assert.expect(3); + test(`get+unload+get hasMany with data, no links`, async function (assert) { + assert.expect(5); let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); @@ -1411,32 +1415,30 @@ module('integration/relationship/json-api-links | Relationship fetching', functi assert.ok(false, 'We should not call findHasMany'); }; - // setup user - let user = run(() => - store.push({ - data: { - type: 'user', - id: '1', - attributes: { - name: '@runspired', + let user = store.push({ + data: { + type: 'user', + id: '1', + attributes: { + name: '@runspired', + }, + relationships: { + pets: { + data: [{ type: 'pet', id: '1' }], }, - relationships: { - pets: { - data: [{ type: 'pet', id: '1' }], - }, - home: { - data: { type: 'home', id: '1' }, - }, + home: { + data: { type: 'home', id: '1' }, }, }, - }) - ); - let pets = run(() => user.get('pets')); + }, + }); + let pets = await user.pets; assert.ok(!!pets, 'We found our pets'); - - run(() => pets.objectAt(0).unloadRecord()); - run(() => user.get('pets')); + pets.objectAt(0).unloadRecord(); + assert.strictEqual(pets.length, 0, 'we unloaded our pet'); + await user.pets; + assert.strictEqual(pets.length, 1, 'we reloaded our pet'); }); test(`get+reload belongsTo with data, no links`, function (assert) { @@ -1493,7 +1495,7 @@ module('integration/relationship/json-api-links | Relationship fetching', functi }, }) ); - let home = run(() => user.get('home')); + let home = run(() => user.home); assert.ok(!!home, 'We found our home'); @@ -1554,17 +1556,17 @@ module('integration/relationship/json-api-links | Relationship fetching', functi }, }) ); - let home = run(() => user.get('home')); + let home = run(() => user.home); assert.ok(!!home, 'We found our home'); run(() => home.then((h) => h.unloadRecord())); - run(() => user.get('home')); + run(() => user.home); }); // missing data setup from the other side, no links - test(`get+reload hasMany with missing data setup from the other side, no links`, function (assert) { - assert.expect(2); + test(`get+reload hasMany with missing data setup from the other side, no links`, async function (assert) { + assert.expect(4); let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); @@ -1598,43 +1600,43 @@ module('integration/relationship/json-api-links | Relationship fetching', functi }; // setup user and pet - let user = run(() => - store.push({ - data: { - type: 'user', + let user = store.push({ + data: { + type: 'user', + id: '1', + attributes: { + name: '@runspired', + }, + relationships: {}, + }, + included: [ + { + type: 'pet', id: '1', attributes: { - name: '@runspired', + name: 'Shen', }, - relationships: {}, - }, - included: [ - { - type: 'pet', - id: '1', - attributes: { - name: 'Shen', - }, - relationships: { - owner: { - data: { - type: 'user', - id: '1', - }, + relationships: { + owner: { + data: { + type: 'user', + id: '1', }, }, }, - ], - }) - ); - let pets = run(() => user.get('pets')); + }, + ], + }); + let pets = await user.pets; + assert.strictEqual(pets.length, 1, 'we setup the pets'); assert.ok(!!pets, 'We found our pets'); - run(() => pets.reload()); + await pets.reload(); + assert.strictEqual(pets.length, 1, 'still only the one'); }); - test(`get+unload+get hasMany with missing data setup from the other side, no links`, function (assert) { - assert.expect(2); + test(`get+unload+get hasMany with missing data setup from the other side, no links`, async function (assert) { + assert.expect(5); let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); @@ -1668,45 +1670,46 @@ module('integration/relationship/json-api-links | Relationship fetching', functi }; // setup user and pet - let user = run(() => - store.push({ - data: { - type: 'user', + let user = store.push({ + data: { + type: 'user', + id: '1', + attributes: { + name: '@runspired', + }, + relationships: {}, + }, + included: [ + { + type: 'pet', id: '1', attributes: { - name: '@runspired', + name: 'Shen', }, - relationships: {}, - }, - included: [ - { - type: 'pet', - id: '1', - attributes: { - name: 'Shen', - }, - relationships: { - owner: { - data: { - type: 'user', - id: '1', - }, + relationships: { + owner: { + data: { + type: 'user', + id: '1', }, }, }, - ], - }) - ); + }, + ], + }); - // should trigger a fetch bc we don't consider `pets` to have complete knowledge - let pets = run(() => user.get('pets')); + // should not trigger a fetch bc even though we don't consider `pets` to have complete knowledge + // we have no knowledge with which to initate a request. + let pets = await user.pets; assert.ok(!!pets, 'We found our pets'); - - run(() => pets.objectAt(0).unloadRecord()); + assert.strictEqual(pets.length, 1, 'we loaded our pets'); + pets.objectAt(0).unloadRecord(); + assert.strictEqual(pets.length, 0, 'we unloaded our pets'); // should trigger a findRecord for the unloaded pet - run(() => user.get('pets')); + await user.pets; + assert.strictEqual(pets.length, 1, 'we reloaded our pets'); }); test(`get+reload belongsTo with missing data setup from the other side, no links`, function (assert) { @@ -1773,7 +1776,7 @@ module('integration/relationship/json-api-links | Relationship fetching', functi ], }) ); - let home = run(() => user.get('home')); + let home = run(() => user.home); assert.ok(!!home, 'We found our home'); @@ -1843,12 +1846,12 @@ module('integration/relationship/json-api-links | Relationship fetching', functi ], }) ); - let home = run(() => user.get('home')); + let home = run(() => user.home); assert.ok(!!home, 'We found our home'); run(() => home.then((h) => h.unloadRecord())); - run(() => user.get('home')); + run(() => user.home); }); // empty data, no links @@ -1889,7 +1892,7 @@ module('integration/relationship/json-api-links | Relationship fetching', functi }, }) ); - let pets = run(() => user.get('pets')); + let pets = run(() => user.pets); assert.ok(!!pets, 'We found our pets'); @@ -1972,21 +1975,21 @@ module('integration/relationship/json-api-links | Relationship fetching', functi // should not fire a request requestedUser = null; failureDescription = 'We improperly fetched the link for a known empty relationship'; - run(() => user1.get('pets')); + run(() => user1.pets); // still should not fire a request requestedUser = null; failureDescription = 'We improperly fetched the link (again) for a known empty relationship'; - run(() => user1.get('pets')); + run(() => user1.pets); // should fire a request requestedUser = user2Payload; - run(() => user2.get('pets')); + run(() => user2.pets); // should not fire a request requestedUser = null; failureDescription = 'We improperly fetched the link for a previously fetched and found to be empty relationship'; - run(() => user2.get('pets')); + run(() => user2.pets); }); test('We should not fetch a sync hasMany relationship with a link that is missing the data member', function (assert) { @@ -2030,7 +2033,7 @@ module('integration/relationship/json-api-links | Relationship fetching', functi let shen = run(() => store.push(petPayload)); // should not fire a request - run(() => shen.get('pets')); + run(() => shen.pets); assert.ok(true, 'We reached the end of the test'); }); @@ -2077,7 +2080,7 @@ module('integration/relationship/json-api-links | Relationship fetching', functi let shen = run(() => store.push(petPayload)); // should not fire a request - run(() => shen.get('owner')); + run(() => shen.owner); assert.ok(true, 'We reached the end of the test'); }); diff --git a/packages/-ember-data/tests/integration/relationships/many-to-many-test.js b/packages/-ember-data/tests/integration/relationships/many-to-many-test.js index f49f8facd87..58eecc48bf9 100644 --- a/packages/-ember-data/tests/integration/relationships/many-to-many-test.js +++ b/packages/-ember-data/tests/integration/relationships/many-to-many-test.js @@ -87,8 +87,8 @@ module('integration/relationships/many_to_many_test - ManyToMany relationships', }); return run(() => { - return topic.get('users').then((fetchedUsers) => { - assert.strictEqual(fetchedUsers.get('length'), 1, 'User relationship was set up correctly'); + return topic.users.then((fetchedUsers) => { + assert.strictEqual(fetchedUsers.length, 1, 'User relationship was set up correctly'); }); }); }); @@ -129,7 +129,7 @@ module('integration/relationships/many_to_many_test - ManyToMany relationships', }); run(() => { - assert.strictEqual(account.get('users.length'), 1, 'User relationship was set up correctly'); + assert.strictEqual(account.users.length, 1, 'User relationship was set up correctly'); }); }); @@ -169,11 +169,11 @@ module('integration/relationships/many_to_many_test - ManyToMany relationships', }); return run(() => { - return user.get('topics').then((fetchedTopics) => { - assert.strictEqual(fetchedTopics.get('length'), 0, 'Topics were removed correctly'); + return user.topics.then((fetchedTopics) => { + assert.strictEqual(fetchedTopics.length, 0, 'Topics were removed correctly'); assert.strictEqual(fetchedTopics.objectAt(0), undefined, "Topics can't be fetched"); - return topic.get('users').then((fetchedUsers) => { - assert.strictEqual(fetchedUsers.get('length'), 0, 'Users were removed correctly'); + return topic.users.then((fetchedUsers) => { + assert.strictEqual(fetchedUsers.length, 0, 'Users were removed correctly'); assert.strictEqual(fetchedUsers.objectAt(0), undefined, "User can't be fetched"); }); }); @@ -230,8 +230,8 @@ module('integration/relationships/many_to_many_test - ManyToMany relationships', }); run(() => { - assert.strictEqual(user.get('accounts.length'), 0, 'Accounts were removed correctly'); - assert.strictEqual(account.get('users.length'), 0, 'Users were removed correctly'); + assert.strictEqual(user.accounts.length, 0, 'Accounts were removed correctly'); + assert.strictEqual(account.users.length, 0, 'Users were removed correctly'); }); }); @@ -273,10 +273,10 @@ module('integration/relationships/many_to_many_test - ManyToMany relationships', }); return run(() => { - return topic.get('users').then((fetchedUsers) => { + return topic.users.then((fetchedUsers) => { fetchedUsers.pushObject(user); - return user.get('topics').then((fetchedTopics) => { - assert.strictEqual(fetchedTopics.get('length'), 1, 'User relationship was set up correctly'); + return user.topics.then((fetchedTopics) => { + assert.strictEqual(fetchedTopics.length, 1, 'User relationship was set up correctly'); }); }); }); @@ -305,11 +305,11 @@ module('integration/relationships/many_to_many_test - ManyToMany relationships', }, }, }); - stanley.get('accounts').pushObject(account); + stanley.accounts.pushObject(account); }); run(() => { - assert.strictEqual(account.get('users.length'), 1, 'User relationship was set up correctly'); + assert.strictEqual(account.users.length, 1, 'User relationship was set up correctly'); }); }); @@ -349,11 +349,11 @@ module('integration/relationships/many_to_many_test - ManyToMany relationships', }); return run(() => { - return user.get('topics').then((fetchedTopics) => { - assert.strictEqual(fetchedTopics.get('length'), 1, 'Topics were setup correctly'); + return user.topics.then((fetchedTopics) => { + assert.strictEqual(fetchedTopics.length, 1, 'Topics were setup correctly'); fetchedTopics.removeObject(topic); - return topic.get('users').then((fetchedUsers) => { - assert.strictEqual(fetchedUsers.get('length'), 0, 'Users were removed correctly'); + return topic.users.then((fetchedUsers) => { + assert.strictEqual(fetchedUsers.length, 0, 'Users were removed correctly'); assert.strictEqual(fetchedUsers.objectAt(0), undefined, "User can't be fetched"); }); }); @@ -396,10 +396,10 @@ module('integration/relationships/many_to_many_test - ManyToMany relationships', }); run(() => { - assert.strictEqual(account.get('users.length'), 1, 'Users were setup correctly'); - account.get('users').removeObject(user); - assert.strictEqual(user.get('accounts.length'), 0, 'Accounts were removed correctly'); - assert.strictEqual(account.get('users.length'), 0, 'Users were removed correctly'); + assert.strictEqual(account.users.length, 1, 'Users were setup correctly'); + account.users.removeObject(user); + assert.strictEqual(user.accounts.length, 0, 'Accounts were removed correctly'); + assert.strictEqual(account.users.length, 0, 'Users were removed correctly'); }); }); @@ -448,12 +448,12 @@ module('integration/relationships/many_to_many_test - ManyToMany relationships', }); return run(() => { - let users = topic.get('users').then((fetchedUsers) => { - assert.strictEqual(fetchedUsers.get('length'), 1, 'Users are still there'); + let users = topic.users.then((fetchedUsers) => { + assert.strictEqual(fetchedUsers.length, 1, 'Users are still there'); }); - let topics = user.get('topics').then((fetchedTopics) => { - assert.strictEqual(fetchedTopics.get('length'), 1, 'Topic got rollbacked into the user'); + let topics = user.topics.then((fetchedTopics) => { + assert.strictEqual(fetchedTopics.length, 1, 'Topic got rollbacked into the user'); }); return EmberPromise.all([users, topics]); @@ -498,47 +498,36 @@ module('integration/relationships/many_to_many_test - ManyToMany relationships', run(() => { account.deleteRecord(); account.rollbackAttributes(); - assert.strictEqual(account.get('users.length'), 1, 'Users are still there'); - assert.strictEqual(user.get('accounts.length'), 1, 'Account got rolledback correctly into the user'); + assert.strictEqual(account.users.length, 1, 'Users are still there'); + assert.strictEqual(user.accounts.length, 1, 'Account got rolledback correctly into the user'); }); }); - test('Rollbacking attributes for a created record that has a ManyToMany relationship works correctly - async', function (assert) { + test('Rollbacking attributes for a created record that has a ManyToMany relationship works correctly - async', async function (assert) { let store = this.owner.lookup('service:store'); - let user, topic; - run(() => { - user = store.push({ - data: { - id: '1', - type: 'user', - attributes: { - name: 'Stanley', - }, + let user = store.push({ + data: { + id: '1', + type: 'user', + attributes: { + name: 'Stanley', }, - }); - - topic = store.createRecord('topic'); + }, }); + let topic = store.createRecord('topic'); - return run(() => { - return user.get('topics').then((fetchedTopics) => { - fetchedTopics.pushObject(topic); - topic.rollbackAttributes(); + let fetchedTopics = await user.topics; + fetchedTopics.pushObject(topic); + topic.rollbackAttributes(); - let users = topic.get('users').then((fetchedUsers) => { - assert.strictEqual(fetchedUsers.get('length'), 0, 'Users got removed'); - assert.strictEqual(fetchedUsers.objectAt(0), undefined, "User can't be fetched"); - }); + let fetchedUsers = await topic.users; + assert.strictEqual(fetchedUsers.length, 0, 'Users got removed'); + assert.strictEqual(fetchedUsers.objectAt(0), undefined, "User can't be fetched"); - let topics = user.get('topics').then((fetchedTopics) => { - assert.strictEqual(fetchedTopics.get('length'), 0, 'Topics got removed'); - assert.strictEqual(fetchedTopics.objectAt(0), undefined, "Topic can't be fetched"); - }); - - return EmberPromise.all([users, topics]); - }); - }); + fetchedTopics = await user.topics; + assert.strictEqual(fetchedTopics.length, 0, 'Topics got removed'); + assert.strictEqual(fetchedTopics.objectAt(0), undefined, "Topic can't be fetched"); }); test('Deleting an unpersisted record via rollbackAttributes that has a hasMany relationship removes it from the otherMany array but does not remove the other record from itself - sync', function (assert) { @@ -560,12 +549,12 @@ module('integration/relationships/many_to_many_test - ManyToMany relationships', }); run(() => { - account.get('users').pushObject(user); + account.users.pushObject(user); user.rollbackAttributes(); }); - assert.strictEqual(account.get('users.length'), 0, 'Users got removed'); - assert.strictEqual(user.get('accounts.length'), 0, 'Accounts got rolledback correctly'); + assert.strictEqual(account.users.length, 0, 'Users got removed'); + assert.strictEqual(user.accounts.length, 0, 'Accounts got rolledback correctly'); }); todo( @@ -625,7 +614,7 @@ module('integration/relationships/many_to_many_test - ManyToMany relationships', }, }, }); - account.get('users').removeObject(byron); + account.users.removeObject(byron); account = store.push({ data: { id: '2', @@ -652,9 +641,9 @@ module('integration/relationships/many_to_many_test - ManyToMany relationships', }); let state = account.hasMany('users').hasManyRelationship.canonicalState; - let users = account.get('users'); + let users = account.users; - assert.todo.equal(users.get('length'), 1, 'Accounts were updated correctly (ui state)'); + assert.todo.equal(users.length, 1, 'Accounts were updated correctly (ui state)'); assert.todo.deepEqual( users.map((r) => get(r, 'id')), ['1'], diff --git a/packages/-ember-data/tests/integration/relationships/nested-relationship-test.js b/packages/-ember-data/tests/integration/relationships/nested-relationship-test.js index 3515cdcaf58..28d248da690 100644 --- a/packages/-ember-data/tests/integration/relationships/nested-relationship-test.js +++ b/packages/-ember-data/tests/integration/relationships/nested-relationship-test.js @@ -1,6 +1,3 @@ -import { get } from '@ember/object'; -import { run } from '@ember/runloop'; - import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; @@ -38,7 +35,7 @@ module('integration/relationships/nested_relationships_test - Nested relationshi Server loading tests */ - test('Sideloaded nested relationships load correctly', function (assert) { + test('Sideloaded nested relationships load correctly', async function (assert) { let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); @@ -46,104 +43,98 @@ module('integration/relationships/nested_relationships_test - Nested relationshi return false; }; - run(() => { - store.push({ - data: { + store.push({ + data: { + id: '1', + type: 'kid', + links: { + self: '/kids/1', + }, + attributes: { + name: 'Kid 1', + }, + relationships: { + middleAger: { + links: { + self: '/kids/1/relationships/middle-ager', + related: '/kids/1/middle-ager', + }, + data: { + type: 'middle-ager', + id: '1', + }, + }, + }, + }, + included: [ + { id: '1', - type: 'kid', + type: 'middle-ager', links: { - self: '/kids/1', + self: '/middle-ager/1', }, attributes: { - name: 'Kid 1', + name: 'Middle Ager 1', }, relationships: { - middleAger: { + elder: { links: { - self: '/kids/1/relationships/middle-ager', - related: '/kids/1/middle-ager', + self: '/middle-agers/1/relationships/elder', + related: '/middle-agers/1/elder', }, data: { - type: 'middle-ager', + type: 'elder', id: '1', }, }, - }, - }, - included: [ - { - id: '1', - type: 'middle-ager', - links: { - self: '/middle-ager/1', - }, - attributes: { - name: 'Middle Ager 1', - }, - relationships: { - elder: { - links: { - self: '/middle-agers/1/relationships/elder', - related: '/middle-agers/1/elder', - }, - data: { - type: 'elder', - id: '1', - }, + kids: { + links: { + self: '/middle-agers/1/relationships/kids', + related: '/middle-agers/1/kids', }, - kids: { - links: { - self: '/middle-agers/1/relationships/kids', - related: '/middle-agers/1/kids', + data: [ + { + type: 'kid', + id: '1', }, - data: [ - { - type: 'kid', - id: '1', - }, - ], - }, + ], }, }, + }, - { - id: '1', - type: 'elder', - links: { - self: '/elders/1', - }, - attributes: { - name: 'Elder 1', - }, - relationships: { - middleAger: { - links: { - self: '/elders/1/relationships/middle-agers', - related: '/elders/1/middle-agers', - }, + { + id: '1', + type: 'elder', + links: { + self: '/elders/1', + }, + attributes: { + name: 'Elder 1', + }, + relationships: { + middleAger: { + links: { + self: '/elders/1/relationships/middle-agers', + related: '/elders/1/middle-agers', }, }, }, - ], - }); + }, + ], }); - return run(() => { - let kid = store.peekRecord('kid', '1'); + let kid = store.peekRecord('kid', '1'); + const middleAger = await kid.middleAger; + assert.ok(middleAger, 'MiddleAger relationship was set up correctly'); - return kid.get('middleAger').then((middleAger) => { - assert.ok(middleAger, 'MiddleAger relationship was set up correctly'); + let middleAgerName = middleAger.name; + let kids = await middleAger.kids; + assert.strictEqual(middleAgerName, 'Middle Ager 1', 'MiddleAger name is there'); + assert.ok(kids.includes(kid)); - let middleAgerName = get(middleAger, 'name'); - assert.strictEqual(middleAgerName, 'Middle Ager 1', 'MiddleAger name is there'); - assert.ok(middleAger.get('kids').includes(kid)); - - return middleAger.get('elder').then((elder) => { - assert.notEqual(elder, null, 'Elder relationship was set up correctly'); - let elderName = get(elder, 'name'); - assert.strictEqual(elderName, 'Elder 1', 'Elder name is there'); - }); - }); - }); + const elder = await middleAger.elder; + assert.notEqual(elder, null, 'Elder relationship was set up correctly'); + let elderName = elder.name; + assert.strictEqual(elderName, 'Elder 1', 'Elder name is there'); }); }); diff --git a/packages/-ember-data/tests/integration/relationships/one-to-many-test.js b/packages/-ember-data/tests/integration/relationships/one-to-many-test.js index 0deb19ca591..0a4c1a4facb 100644 --- a/packages/-ember-data/tests/integration/relationships/one-to-many-test.js +++ b/packages/-ember-data/tests/integration/relationships/one-to-many-test.js @@ -9,6 +9,7 @@ import { setupTest } from 'ember-qunit'; import Adapter from '@ember-data/adapter'; import Model, { attr, belongsTo, hasMany } from '@ember-data/model'; import JSONAPISerializer from '@ember-data/serializer/json-api'; +import { deprecatedTest } from '@ember-data/unpublished-test-infra/test-support/deprecated-test'; module('integration/relationships/one_to_many_test - OneToMany relationships', function (hooks) { setupTest(hooks); @@ -81,7 +82,7 @@ module('integration/relationships/one_to_many_test - OneToMany relationships', f }); }); run(function () { - message.get('user').then(function (fetchedUser) { + message.user.then(function (fetchedUser) { assert.strictEqual(fetchedUser, user, 'User relationship was set up correctly'); }); }); @@ -179,7 +180,7 @@ module('integration/relationships/one_to_many_test - OneToMany relationships', f }, }); }); - assert.strictEqual(account.get('user'), user, 'User relationship was set up correctly'); + assert.strictEqual(account.user, user, 'User relationship was set up correctly'); }); test('Relationship is available from the hasMany side even if only loaded from the belongsTo side - async', function (assert) { @@ -215,7 +216,7 @@ module('integration/relationships/one_to_many_test - OneToMany relationships', f }); }); run(function () { - user.get('messages').then(function (fetchedMessages) { + user.messages.then(function (fetchedMessages) { assert.strictEqual(fetchedMessages.objectAt(0), message, 'Messages relationship was set up correctly'); }); }); @@ -254,7 +255,7 @@ module('integration/relationships/one_to_many_test - OneToMany relationships', f }); }); run(function () { - assert.strictEqual(user.get('accounts').objectAt(0), account, 'Accounts relationship was set up correctly'); + assert.strictEqual(user.accounts.objectAt(0), account, 'Accounts relationship was set up correctly'); }); }); @@ -321,7 +322,7 @@ module('integration/relationships/one_to_many_test - OneToMany relationships', f }); }); run(function () { - user.get('messages').then(function (fetchedMessages) { + user.messages.then(function (fetchedMessages) { assert.strictEqual(get(fetchedMessages, 'length'), 1, 'Messages relationship was set up correctly'); }); }); @@ -379,7 +380,7 @@ module('integration/relationships/one_to_many_test - OneToMany relationships', f }); run(function () { - assert.strictEqual(user.get('accounts').objectAt(0), undefined, 'Account was sucesfully removed'); + assert.strictEqual(user.accounts.objectAt(0), undefined, 'Account was sucesfully removed'); }); }); @@ -441,7 +442,7 @@ module('integration/relationships/one_to_many_test - OneToMany relationships', f }); }); run(function () { - user.get('messages').then(function (fetchedMessages) { + user.messages.then(function (fetchedMessages) { assert.strictEqual(get(fetchedMessages, 'length'), 2, 'Messages relationship was set up correctly'); }); }); @@ -492,7 +493,7 @@ module('integration/relationships/one_to_many_test - OneToMany relationships', f }); run(function () { - assert.strictEqual(user.get('accounts').objectAt(0), account, 'Account was sucesfully removed'); + assert.strictEqual(user.accounts.objectAt(0), account, 'Account was sucesfully removed'); }); }); @@ -569,11 +570,11 @@ module('integration/relationships/one_to_many_test - OneToMany relationships', f }); }); run(function () { - message.get('user').then(function (fetchedUser) { + message.user.then(function (fetchedUser) { assert.strictEqual(fetchedUser, null, 'User was removed correctly'); }); - message2.get('user').then(function (fetchedUser) { + message2.user.then(function (fetchedUser) { assert.strictEqual(fetchedUser, user, 'User was set on the second message'); }); }); @@ -648,8 +649,8 @@ module('integration/relationships/one_to_many_test - OneToMany relationships', f }); run(function () { - assert.strictEqual(account1.get('user'), null, 'User was removed correctly'); - assert.strictEqual(account2.get('user'), user, 'User was added correctly'); + assert.strictEqual(account1.user, null, 'User was removed correctly'); + assert.strictEqual(account2.user, user, 'User was added correctly'); }); }); @@ -706,7 +707,7 @@ module('integration/relationships/one_to_many_test - OneToMany relationships', f }); run(function () { - message.get('user').then(function (fetchedUser) { + message.user.then(function (fetchedUser) { assert.strictEqual(fetchedUser, user, 'User was not removed'); }); }); @@ -774,7 +775,7 @@ module('integration/relationships/one_to_many_test - OneToMany relationships', f }); run(function () { - assert.strictEqual(account.get('user'), user, 'User was not removed'); + assert.strictEqual(account.user, user, 'User was not removed'); }); }); @@ -827,9 +828,9 @@ module('integration/relationships/one_to_many_test - OneToMany relationships', f }); run(function () { - user.get('messages').then(function (fetchedMessages) { + user.messages.then(function (fetchedMessages) { fetchedMessages.pushObject(message2); - message2.get('user').then(function (fetchedUser) { + message2.user.then(function (fetchedUser) { assert.strictEqual(fetchedUser, user, 'user got set correctly'); }); }); @@ -887,10 +888,10 @@ module('integration/relationships/one_to_many_test - OneToMany relationships', f }, }, }); - user.get('accounts').pushObject(account2); + user.accounts.pushObject(account2); }); - assert.strictEqual(account2.get('user'), user, 'user got set correctly'); + assert.strictEqual(account2.user, user, 'user got set correctly'); }); test('Removing from the hasMany side reflects the change on the belongsTo side - async', function (assert) { @@ -929,9 +930,9 @@ module('integration/relationships/one_to_many_test - OneToMany relationships', f }); run(function () { - user.get('messages').then(function (fetchedMessages) { + user.messages.then(function (fetchedMessages) { fetchedMessages.removeObject(message); - message.get('user').then(function (fetchedUser) { + message.user.then(function (fetchedUser) { assert.strictEqual(fetchedUser, null, 'user got removed correctly'); }); }); @@ -981,10 +982,10 @@ module('integration/relationships/one_to_many_test - OneToMany relationships', f }); }); run(function () { - user.get('accounts').removeObject(account); + user.accounts.removeObject(account); }); - assert.strictEqual(account.get('user'), null, 'user got removed correctly'); + assert.strictEqual(account.user, null, 'user got removed correctly'); }); test('Pushing to the hasMany side keeps the oneToMany invariant on the belongsTo side - async', function (assert) { @@ -1034,14 +1035,14 @@ module('integration/relationships/one_to_many_test - OneToMany relationships', f }); run(function () { - user2.get('messages').then(function (fetchedMessages) { + user2.messages.then(function (fetchedMessages) { fetchedMessages.pushObject(message); - message.get('user').then(function (fetchedUser) { + message.user.then(function (fetchedUser) { assert.strictEqual(fetchedUser, user2, 'user got set correctly'); }); - user.get('messages').then(function (newFetchedMessages) { + user.messages.then(function (newFetchedMessages) { assert.strictEqual(get(newFetchedMessages, 'length'), 0, 'message got removed from the old messages hasMany'); }); }); @@ -1090,11 +1091,11 @@ module('integration/relationships/one_to_many_test - OneToMany relationships', f }, }, }); - user2.get('accounts').pushObject(account); + user2.accounts.pushObject(account); }); - assert.strictEqual(account.get('user'), user2, 'user got set correctly'); - assert.strictEqual(user.get('accounts.length'), 0, 'the account got removed correctly'); - assert.strictEqual(user2.get('accounts.length'), 1, 'the account got pushed correctly'); + assert.strictEqual(account.user, user2, 'user got set correctly'); + assert.strictEqual(user.accounts.length, 0, 'the account got removed correctly'); + assert.strictEqual(user2.accounts.length, 1, 'the account got pushed correctly'); }); test('Setting the belongsTo side keeps the oneToMany invariant on the hasMany- async', function (assert) { @@ -1153,12 +1154,12 @@ module('integration/relationships/one_to_many_test - OneToMany relationships', f }); run(function () { - user.get('messages').then(function (fetchedMessages) { + user.messages.then(function (fetchedMessages) { assert.strictEqual(get(fetchedMessages, 'length'), 0, 'message got removed from the first user correctly'); }); }); run(function () { - user2.get('messages').then(function (fetchedMessages) { + user2.messages.then(function (fetchedMessages) { assert.strictEqual(get(fetchedMessages, 'length'), 1, 'message got added to the second user correctly'); }); }); @@ -1216,9 +1217,9 @@ module('integration/relationships/one_to_many_test - OneToMany relationships', f }); account.set('user', user2); }); - assert.strictEqual(account.get('user'), user2, 'user got set correctly'); - assert.strictEqual(user.get('accounts.length'), 0, 'the account got removed correctly'); - assert.strictEqual(user2.get('accounts.length'), 1, 'the account got pushed correctly'); + assert.strictEqual(account.user, user2, 'user got set correctly'); + assert.strictEqual(user.accounts.length, 0, 'the account got removed correctly'); + assert.strictEqual(user2.accounts.length, 1, 'the account got pushed correctly'); }); test('Setting the belongsTo side to null removes the record from the hasMany side - async', function (assert) { @@ -1267,13 +1268,13 @@ module('integration/relationships/one_to_many_test - OneToMany relationships', f message.set('user', null); }); run(function () { - user.get('messages').then(function (fetchedMessages) { + user.messages.then(function (fetchedMessages) { assert.strictEqual(get(fetchedMessages, 'length'), 0, 'message got removed from the user correctly'); }); }); run(function () { - message.get('user').then(function (fetchedUser) { + message.user.then(function (fetchedUser) { assert.strictEqual(fetchedUser, null, 'user got set to null correctly'); }); }); @@ -1323,9 +1324,9 @@ module('integration/relationships/one_to_many_test - OneToMany relationships', f account.set('user', null); }); - assert.strictEqual(account.get('user'), null, 'user got set to null correctly'); + assert.strictEqual(account.user, null, 'user got set to null correctly'); - assert.strictEqual(user.get('accounts.length'), 0, 'the account got removed correctly'); + assert.strictEqual(user.accounts.length, 0, 'the account got removed correctly'); }); /* @@ -1371,10 +1372,10 @@ module('integration/relationships/one_to_many_test - OneToMany relationships', f message.rollbackAttributes(); }); run(function () { - message.get('user').then(function (fetchedUser) { + message.user.then(function (fetchedUser) { assert.strictEqual(fetchedUser, user, 'Message still has the user'); }); - user.get('messages').then(function (fetchedMessages) { + user.messages.then(function (fetchedMessages) { assert.strictEqual(fetchedMessages.objectAt(0), message, 'User has the message'); }); }); @@ -1417,8 +1418,8 @@ module('integration/relationships/one_to_many_test - OneToMany relationships', f run(function () { account.deleteRecord(); account.rollbackAttributes(); - assert.strictEqual(user.get('accounts.length'), 1, 'Accounts are rolled back'); - assert.strictEqual(account.get('user'), user, 'Account still has the user'); + assert.strictEqual(user.accounts.length, 1, 'Accounts are rolled back'); + assert.strictEqual(account.user, user, 'Account still has the user'); }); }); @@ -1461,11 +1462,11 @@ module('integration/relationships/one_to_many_test - OneToMany relationships', f user.rollbackAttributes(); }); run(function () { - message.get('user').then(function (fetchedUser) { + message.user.then(function (fetchedUser) { assert.strictEqual(fetchedUser, user, 'Message has the user again'); }); - user.get('messages').then(function (fetchedMessages) { - assert.strictEqual(fetchedMessages.get('length'), 1, 'User still has the messages'); + user.messages.then(function (fetchedMessages) { + assert.strictEqual(fetchedMessages.length, 1, 'User still has the messages'); }); }); }); @@ -1507,8 +1508,8 @@ module('integration/relationships/one_to_many_test - OneToMany relationships', f run(function () { user.deleteRecord(); user.rollbackAttributes(); - assert.strictEqual(user.get('accounts.length'), 1, 'User still has the accounts'); - assert.strictEqual(account.get('user'), user, 'Account has the user again'); + assert.strictEqual(user.accounts.length, 1, 'User still has the accounts'); + assert.strictEqual(account.user, user, 'Account has the user again'); }); }); @@ -1516,34 +1517,29 @@ module('integration/relationships/one_to_many_test - OneToMany relationships', f Rollback attributes from created state */ - test('Rollbacking attributes of a created record works correctly when the hasMany side has been created - async', function (assert) { + test('Rollbacking attributes of a created record works correctly when the hasMany side has been created - async', async function (assert) { let store = this.owner.lookup('service:store'); - - var user, message; - run(function () { - user = store.push({ - data: { - id: '1', - type: 'user', - attributes: { - name: 'Stanley', - }, + let user = store.push({ + data: { + id: '1', + type: 'user', + attributes: { + name: 'Stanley', }, - }); - message = store.createRecord('message', { - user: user, - }); + }, }); - run(message, 'rollbackAttributes'); - run(function () { - message.get('user').then(function (fetchedUser) { - assert.strictEqual(fetchedUser, null, 'Message does not have the user anymore'); - }); - user.get('messages').then(function (fetchedMessages) { - assert.strictEqual(fetchedMessages.get('length'), 0, 'User does not have the message anymore'); - assert.strictEqual(fetchedMessages.get('firstObject'), undefined, "User message can't be accessed"); - }); + let message = store.createRecord('message', { + user: user, }); + + message.rollbackAttributes(); + + let fetchedUser = await message.user; + assert.strictEqual(fetchedUser, null, 'Message does not have the user anymore'); + let fetchedMessages = await user.messages; + + assert.strictEqual(fetchedMessages.length, 0, 'User does not have the message anymore'); + assert.strictEqual(fetchedMessages.firstObject, undefined, "User message can't be accessed"); }); test('Rollbacking attributes of a created record works correctly when the hasMany side has been created - sync', function (assert) { @@ -1565,39 +1561,31 @@ module('integration/relationships/one_to_many_test - OneToMany relationships', f }); }); run(account, 'rollbackAttributes'); - assert.strictEqual(user.get('accounts.length'), 0, 'Accounts are rolled back'); - assert.strictEqual(account.get('user'), null, 'Account does not have the user anymore'); + assert.strictEqual(user.accounts.length, 0, 'Accounts are rolled back'); + assert.strictEqual(account.user, null, 'Account does not have the user anymore'); }); - test('Rollbacking attributes of a created record works correctly when the belongsTo side has been created - async', function (assert) { + test('Rollbacking attributes of a created record works correctly when the belongsTo side has been created - async', async function (assert) { let store = this.owner.lookup('service:store'); - - var message, user; - run(function () { - message = store.push({ - data: { - id: '2', - type: 'message', - attributes: { - title: 'EmberFest was great', - }, + let message = store.push({ + data: { + id: '2', + type: 'message', + attributes: { + title: 'EmberFest was great', }, - }); - user = store.createRecord('user'); - }); - run(function () { - user.get('messages').then(function (messages) { - messages.pushObject(message); - user.rollbackAttributes(); - message.get('user').then(function (fetchedUser) { - assert.strictEqual(fetchedUser, null, 'Message does not have the user anymore'); - }); - user.get('messages').then(function (fetchedMessages) { - assert.strictEqual(fetchedMessages.get('length'), 0, 'User does not have the message anymore'); - assert.strictEqual(fetchedMessages.get('firstObject'), undefined, "User message can't be accessed"); - }); - }); + }, }); + let user = store.createRecord('user'); + let messages = await user.messages; + messages.pushObject(message); + user.rollbackAttributes(); + let fetchedUser = await message.user; + assert.strictEqual(fetchedUser, null, 'Message does not have the user anymore'); + + let fetchedMessages = await user.messages; + assert.strictEqual(fetchedMessages.length, 0, 'User does not have the message anymore'); + assert.strictEqual(fetchedMessages.firstObject, undefined, "User message can't be accessed"); }); test('Rollbacking attributes of a created record works correctly when the belongsTo side has been created - sync', function (assert) { @@ -1617,51 +1605,55 @@ module('integration/relationships/one_to_many_test - OneToMany relationships', f user = store.createRecord('user'); }); run(function () { - user.get('accounts').pushObject(account); + user.accounts.pushObject(account); }); run(user, 'rollbackAttributes'); - assert.strictEqual(user.get('accounts.length'), 0, 'User does not have the account anymore'); - assert.strictEqual(account.get('user'), null, 'Account does not have the user anymore'); + assert.strictEqual(user.accounts.length, 0, 'User does not have the account anymore'); + assert.strictEqual(account.user, null, 'Account does not have the user anymore'); }); - test('createRecord updates inverse record array which has observers', async function (assert) { - let store = this.owner.lookup('service:store'); - let adapter = store.adapterFor('application'); - - adapter.findAll = () => { - return { - data: [ - { - id: '2', - type: 'user', - attributes: { - name: 'Stanley', + deprecatedTest( + 'createRecord updates inverse record array which has observers', + { id: 'ember-data:deprecate-promise-many-array-behaviors', until: '5.0', count: 5 }, + async function (assert) { + let store = this.owner.lookup('service:store'); + let adapter = store.adapterFor('application'); + + adapter.findAll = () => { + return { + data: [ + { + id: '2', + type: 'user', + attributes: { + name: 'Stanley', + }, }, - }, - ], + ], + }; }; - }; - const users = await store.findAll('user'); - assert.strictEqual(users.get('length'), 1, 'Exactly 1 user'); + const users = await store.findAll('user'); + assert.strictEqual(users.length, 1, 'Exactly 1 user'); - let user = users.get('firstObject'); - assert.strictEqual(user.get('messages.length'), 0, 'Record array is initially empty'); + let user = users.firstObject; + assert.strictEqual(user.messages.length, 0, 'Record array is initially empty'); - // set up an observer - user.addObserver('messages.@each.title', () => {}); - user.get('messages.firstObject'); + // set up an observer + user.addObserver('messages.@each.title', () => {}); + user.messages.firstObject; - const messages = await user.messages; + const messages = await user.messages; - assert.strictEqual(messages.length, 0, 'we have no messages'); - assert.strictEqual(user.messages.length, 0, 'we have no messages'); + assert.strictEqual(messages.length, 0, 'we have no messages'); + assert.strictEqual(user.messages.length, 0, 'we have no messages'); - let message = store.createRecord('message', { user, title: 'EmberFest was great' }); - assert.strictEqual(messages.length, 1, 'The message is added to the record array'); - assert.strictEqual(user.messages.length, 1, 'The message is added to the record array'); + let message = store.createRecord('message', { user, title: 'EmberFest was great' }); + assert.strictEqual(messages.length, 1, 'The message is added to the record array'); + assert.strictEqual(user.messages.length, 1, 'The message is added to the record array'); - let messageFromArray = user.messages.firstObject; - assert.strictEqual(message, messageFromArray, 'Only one message record instance should be created'); - }); + let messageFromArray = user.messages.firstObject; + assert.strictEqual(message, messageFromArray, 'Only one message record instance should be created'); + } + ); }); diff --git a/packages/-ember-data/tests/integration/relationships/one-to-one-test.js b/packages/-ember-data/tests/integration/relationships/one-to-one-test.js index d2239fcf01a..0f847b7dc26 100644 --- a/packages/-ember-data/tests/integration/relationships/one-to-one-test.js +++ b/packages/-ember-data/tests/integration/relationships/one-to-one-test.js @@ -1,4 +1,5 @@ import { run } from '@ember/runloop'; +import { settled } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { Promise as EmberPromise, resolve } from 'rsvp'; @@ -75,7 +76,7 @@ module('integration/relationships/one_to_one_test - OneToOne relationships', fun }, }); - stanleysFriend.get('bestFriend').then(function (fetchedUser) { + stanleysFriend.bestFriend.then(function (fetchedUser) { assert.strictEqual(fetchedUser, stanley, 'User relationship was set up correctly'); }); }); @@ -113,7 +114,7 @@ module('integration/relationships/one_to_one_test - OneToOne relationships', fun }, }); }); - assert.strictEqual(job.get('user'), user, 'User relationship was set up correctly'); + assert.strictEqual(job.user, user, 'User relationship was set up correctly'); }); test('Fetching a belongsTo that is set to null removes the record from a relationship - async', function (assert) { @@ -152,7 +153,7 @@ module('integration/relationships/one_to_one_test - OneToOne relationships', fun }, }, }); - stanleysFriend.get('bestFriend').then(function (fetchedUser) { + stanleysFriend.bestFriend.then(function (fetchedUser) { assert.strictEqual(fetchedUser, null, 'User relationship was removed correctly'); }); }); @@ -206,7 +207,7 @@ module('integration/relationships/one_to_one_test - OneToOne relationships', fun }, }); }); - assert.strictEqual(job.get('user'), null, 'User relationship was removed correctly'); + assert.strictEqual(job.user, null, 'User relationship was removed correctly'); }); test('Fetching a belongsTo that is set to a different record, sets the old relationship to null - async', async function (assert) { @@ -238,7 +239,7 @@ module('integration/relationships/one_to_one_test - OneToOne relationships', fun }); let user2 = store.peekRecord('user', '2'); - let user1Friend = await user1.get('bestFriend'); + let user1Friend = await user1.bestFriend; assert.strictEqual(user1Friend, user2, '.bestFriend is '); @@ -279,9 +280,9 @@ module('integration/relationships/one_to_one_test - OneToOne relationships', fun }); let user3 = store.peekRecord('user', '3'); - let user1bestFriend = await user1.get('bestFriend'); - let user2bestFriend = await user2.get('bestFriend'); - let user3bestFriend = await user3.get('bestFriend'); + let user1bestFriend = await user1.bestFriend; + let user2bestFriend = await user2.bestFriend; + let user3bestFriend = await user3.bestFriend; assert.strictEqual(user3bestFriend, user2, '.bestFriend is '); assert.strictEqual(user2bestFriend, user3, '.bestFriend is '); @@ -327,7 +328,7 @@ module('integration/relationships/one_to_one_test - OneToOne relationships', fun let job1 = store.peekRecord('job', '1'); - assert.strictEqual(user1.get('job'), job1, '.job is '); + assert.strictEqual(user1.job, job1, '.job is '); /* Now we "reload" but with a new user. While this only gives @@ -367,9 +368,9 @@ module('integration/relationships/one_to_one_test - OneToOne relationships', fun let user2 = store.peekRecord('user', '2'); - assert.strictEqual(user2.get('job'), job1, '.job is '); - assert.strictEqual(job1.get('user'), user2, '.user is '); - assert.strictEqual(user1.get('job'), null, '.job is null'); + assert.strictEqual(user2.job, job1, '.job is '); + assert.strictEqual(job1.user, user2, '.user is '); + assert.strictEqual(user1.job, null, '.job is null'); let user1JobState = user1.belongsTo('job').belongsToRelationship; @@ -411,7 +412,7 @@ module('integration/relationships/one_to_one_test - OneToOne relationships', fun }); run(function () { stanley.set('bestFriend', stanleysFriend); - stanleysFriend.get('bestFriend').then(function (fetchedUser) { + stanleysFriend.bestFriend.then(function (fetchedUser) { assert.strictEqual(fetchedUser, stanley, 'User relationship was updated correctly'); }); }); @@ -444,7 +445,7 @@ module('integration/relationships/one_to_one_test - OneToOne relationships', fun run(function () { user.set('job', job); }); - assert.strictEqual(job.get('user'), user, 'User relationship was set up correctly'); + assert.strictEqual(job.user, user, 'User relationship was set up correctly'); }); test('Setting a BelongsTo to a promise unwraps the promise before setting- async', function (assert) { @@ -489,15 +490,15 @@ module('integration/relationships/one_to_one_test - OneToOne relationships', fun }); }); run(function () { - newFriend.set('bestFriend', stanleysFriend.get('bestFriend')); - stanley.get('bestFriend').then(function (fetchedUser) { + newFriend.set('bestFriend', stanleysFriend.bestFriend); + stanley.bestFriend.then(function (fetchedUser) { assert.strictEqual( fetchedUser, newFriend, `Stanley's bestFriend relationship was updated correctly to newFriend` ); }); - newFriend.get('bestFriend').then(function (fetchedUser) { + newFriend.bestFriend.then(function (fetchedUser) { assert.strictEqual( fetchedUser, stanley, @@ -549,8 +550,8 @@ module('integration/relationships/one_to_one_test - OneToOne relationships', fun }); }); run(function () { - newFriend.set('bestFriend', igor.get('bestFriend')); - newFriend.get('bestFriend').then(function (fetchedUser) { + newFriend.set('bestFriend', igor.bestFriend); + newFriend.bestFriend.then(function (fetchedUser) { assert.strictEqual(fetchedUser, null, 'User relationship was updated correctly'); }); }); @@ -664,10 +665,10 @@ module('integration/relationships/one_to_one_test - OneToOne relationships', fun }; run(function () { - newFriend.set('bestFriend', stanley.get('bestFriend')); - newFriend.set('bestFriend', igor.get('bestFriend')); - newFriend.get('bestFriend').then(function (fetchedUser) { - assert.strictEqual(fetchedUser.get('name'), "Igor's friend", 'User relationship was updated correctly'); + newFriend.set('bestFriend', stanley.bestFriend); + newFriend.set('bestFriend', igor.bestFriend); + newFriend.bestFriend.then(function (fetchedUser) { + assert.strictEqual(fetchedUser.name, "Igor's friend", 'User relationship was updated correctly'); }); }); }); @@ -715,7 +716,7 @@ module('integration/relationships/one_to_one_test - OneToOne relationships', fun run(function () { stanley.set('bestFriend', null); // :( - stanleysFriend.get('bestFriend').then(function (fetchedUser) { + stanleysFriend.bestFriend.then(function (fetchedUser) { assert.strictEqual(fetchedUser, null, 'User relationship was removed correctly'); }); }); @@ -765,7 +766,7 @@ module('integration/relationships/one_to_one_test - OneToOne relationships', fun run(function () { user.set('job', null); }); - assert.strictEqual(job.get('user'), null, 'User relationship was removed correctly'); + assert.strictEqual(job.user, null, 'User relationship was removed correctly'); }); test('Setting a belongsTo to a different record, sets the old relationship to null - async', function (assert) { @@ -810,7 +811,7 @@ module('integration/relationships/one_to_one_test - OneToOne relationships', fun }, }); - stanleysFriend.get('bestFriend').then(function (fetchedUser) { + stanleysFriend.bestFriend.then(function (fetchedUser) { assert.strictEqual(fetchedUser, stanley, 'User relationship was initally setup correctly'); var stanleysNewFriend = store.push({ data: { @@ -826,11 +827,11 @@ module('integration/relationships/one_to_one_test - OneToOne relationships', fun stanleysNewFriend.set('bestFriend', stanley); }); - stanley.get('bestFriend').then(function (fetchedNewFriend) { + stanley.bestFriend.then(function (fetchedNewFriend) { assert.strictEqual(fetchedNewFriend, stanleysNewFriend, 'User relationship was updated correctly'); }); - stanleysFriend.get('bestFriend').then(function (fetchedOldFriend) { + stanleysFriend.bestFriend.then(function (fetchedOldFriend) { assert.strictEqual(fetchedOldFriend, null, 'The old relationship was set to null correctly'); }); }); @@ -870,7 +871,7 @@ module('integration/relationships/one_to_one_test - OneToOne relationships', fun }); }); - assert.strictEqual(job.get('user'), user, 'Job and user initially setup correctly'); + assert.strictEqual(job.user, user, 'Job and user initially setup correctly'); run(function () { newBetterJob = store.push({ @@ -886,9 +887,9 @@ module('integration/relationships/one_to_one_test - OneToOne relationships', fun newBetterJob.set('user', user); }); - assert.strictEqual(user.get('job'), newBetterJob, 'Job updated correctly'); - assert.strictEqual(job.get('user'), null, 'Old relationship nulled out correctly'); - assert.strictEqual(newBetterJob.get('user'), user, 'New job setup correctly'); + assert.strictEqual(user.job, newBetterJob, 'Job updated correctly'); + assert.strictEqual(job.user, null, 'Old relationship nulled out correctly'); + assert.strictEqual(newBetterJob.user, user, 'New job setup correctly'); }); /* @@ -932,10 +933,10 @@ module('integration/relationships/one_to_one_test - OneToOne relationships', fun }); run(function () { stanley.rollbackAttributes(); - stanleysFriend.get('bestFriend').then(function (fetchedUser) { + stanleysFriend.bestFriend.then(function (fetchedUser) { assert.strictEqual(fetchedUser, stanley, 'Stanley got rollbacked correctly'); }); - stanley.get('bestFriend').then(function (fetchedUser) { + stanley.bestFriend.then(function (fetchedUser) { assert.strictEqual(fetchedUser, stanleysFriend, 'Stanleys friend did not get removed'); }); }); @@ -977,59 +978,50 @@ module('integration/relationships/one_to_one_test - OneToOne relationships', fun job.deleteRecord(); job.rollbackAttributes(); }); - assert.strictEqual(user.get('job'), job, 'Job got rollbacked correctly'); - assert.strictEqual(job.get('user'), user, 'Job still has the user'); + assert.strictEqual(user.job, job, 'Job got rollbacked correctly'); + assert.strictEqual(job.user, user, 'Job still has the user'); }); - test('Rollbacking attributes of created record removes the relationship on both sides - async', function (assert) { + test('Rollbacking attributes of created record removes the relationship on both sides - async', async function (assert) { let store = this.owner.lookup('service:store'); - var stanleysFriend, stanley; - run(function () { - stanleysFriend = store.push({ - data: { - id: 2, - type: 'user', - attributes: { - name: "Stanley's friend", - }, + const stanleysFriend = store.push({ + data: { + id: 2, + type: 'user', + attributes: { + name: "Stanley's friend", }, - }); - - stanley = store.createRecord('user', { bestFriend: stanleysFriend }); - }); - run(function () { - stanley.rollbackAttributes(); - stanleysFriend.get('bestFriend').then(function (fetchedUser) { - assert.strictEqual(fetchedUser, null, 'Stanley got rollbacked correctly'); - }); - stanley.get('bestFriend').then(function (fetchedUser) { - assert.strictEqual(fetchedUser, null, 'Stanleys friend did got removed'); - }); + }, }); + const stanley = store.createRecord('user', { bestFriend: stanleysFriend }); + + stanley.rollbackAttributes(); + + let fetchedUser = await stanleysFriend.bestFriend; + assert.strictEqual(fetchedUser, null, 'Stanley got rollbacked correctly'); + // TODO we should figure out how to handle the fact that we disconnect things. Right now we're asserting eagerly. + fetchedUser = await stanley.bestFriend; + assert.strictEqual(fetchedUser, null, 'Stanleys friend did get removed'); }); - test('Rollbacking attributes of created record removes the relationship on both sides - sync', function (assert) { + test('Rollbacking attributes of created record removes the relationship on both sides - sync', async function (assert) { let store = this.owner.lookup('service:store'); - var user, job; - run(function () { - user = store.push({ - data: { - id: 1, - type: 'user', - attributes: { - name: 'Stanley', - }, + const user = store.push({ + data: { + id: 1, + type: 'user', + attributes: { + name: 'Stanley', }, - }); - - job = store.createRecord('job', { user: user }); - }); - run(function () { - job.rollbackAttributes(); + }, }); - assert.strictEqual(user.get('job'), null, 'Job got rollbacked correctly'); - assert.strictEqual(job.get('user'), null, 'Job does not have user anymore'); + const job = store.createRecord('job', { user: user }); + job.rollbackAttributes(); + await settled(); + + assert.strictEqual(user.job, null, 'Job got rollbacked correctly'); + assert.true(job.isDestroyed, 'Job is destroyed'); }); }); diff --git a/packages/-ember-data/tests/integration/relationships/polymorphic-mixins-belongs-to-test.js b/packages/-ember-data/tests/integration/relationships/polymorphic-mixins-belongs-to-test.js index b2ae81e548c..e5cafa732c5 100644 --- a/packages/-ember-data/tests/integration/relationships/polymorphic-mixins-belongs-to-test.js +++ b/packages/-ember-data/tests/integration/relationships/polymorphic-mixins-belongs-to-test.js @@ -75,9 +75,9 @@ module( video = store.peekRecord('video', 2); }); run(function () { - user.get('bestMessage').then(function (message) { + user.bestMessage.then(function (message) { assert.strictEqual(message, video, 'The message was loaded correctly'); - message.get('user').then(function (fetchedUser) { + message.user.then(function (fetchedUser) { assert.strictEqual(fetchedUser, user, 'The inverse was setup correctly'); }); }); @@ -116,10 +116,10 @@ module( run(function () { user.set('bestMessage', video); - video.get('user').then(function (fetchedUser) { + video.user.then(function (fetchedUser) { assert.strictEqual(fetchedUser, user, 'user got set correctly'); }); - user.get('bestMessage').then(function (message) { + user.bestMessage.then(function (message) { assert.strictEqual(message, video, 'The message was set correctly'); }); }); @@ -193,10 +193,10 @@ module( run(function () { user.set('bestMessage', video); - video.get('user').then(function (fetchedUser) { + video.user.then(function (fetchedUser) { assert.strictEqual(fetchedUser, user, 'user got set correctly'); }); - user.get('bestMessage').then(function (message) { + user.bestMessage.then(function (message) { assert.strictEqual(message, video, 'The message was set correctly'); }); }); diff --git a/packages/-ember-data/tests/integration/relationships/polymorphic-mixins-has-many-test.js b/packages/-ember-data/tests/integration/relationships/polymorphic-mixins-has-many-test.js index 07b3858c281..b6cf2ed899f 100644 --- a/packages/-ember-data/tests/integration/relationships/polymorphic-mixins-has-many-test.js +++ b/packages/-ember-data/tests/integration/relationships/polymorphic-mixins-has-many-test.js @@ -77,14 +77,11 @@ module( video = store.peekRecord('video', 2); }); run(function () { - user.get('messages').then(function (messages) { + user.messages.then(function (messages) { assert.strictEqual(messages.objectAt(0), video, 'The hasMany has loaded correctly'); - messages - .objectAt(0) - .get('user') - .then(function (fetchedUser) { - assert.strictEqual(fetchedUser, user, 'The inverse was setup correctly'); - }); + messages.objectAt(0).user.then(function (fetchedUser) { + assert.strictEqual(fetchedUser, user, 'The inverse was setup correctly'); + }); }); }); }); @@ -125,9 +122,9 @@ module( }); run(function () { - user.get('messages').then(function (fetchedMessages) { + user.messages.then(function (fetchedMessages) { fetchedMessages.pushObject(video); - video.get('user').then(function (fetchedUser) { + video.user.then(function (fetchedUser) { assert.strictEqual(fetchedUser, user, 'user got set correctly'); }); }); @@ -171,9 +168,9 @@ module( }); run(function () { - user.get('messages').then(function (fetchedMessages) { + user.messages.then(function (fetchedMessages) { fetchedMessages.pushObject(video); - video.get('user').then(function (fetchedUser) { + video.user.then(function (fetchedUser) { assert.strictEqual(fetchedUser, user, 'user got set correctly'); }); }); @@ -218,7 +215,7 @@ module( }); run(function () { - user.get('messages').then(function (fetchedMessages) { + user.messages.then(function (fetchedMessages) { assert.expectAssertion(function () { fetchedMessages.pushObject(notMessage); }, /The 'not-message' type does not implement 'message' and thus cannot be assigned to the 'messages' relationship in 'user'. Make it a descendant of 'message/); @@ -260,9 +257,9 @@ module( }); run(function () { - user.get('messages').then(function (fetchedMessages) { + user.messages.then(function (fetchedMessages) { fetchedMessages.pushObject(video); - video.get('user').then(function (fetchedUser) { + video.user.then(function (fetchedUser) { assert.strictEqual(fetchedUser, user, 'user got set correctly'); }); }); @@ -307,7 +304,7 @@ module( }); run(function () { - user.get('messages').then(function (fetchedMessages) { + user.messages.then(function (fetchedMessages) { assert.expectAssertion(function () { fetchedMessages.pushObject(notMessage); }, /The 'not-message' type does not implement 'message' and thus cannot be assigned to the 'messages' relationship in 'user'. Make it a descendant of 'message'/); diff --git a/packages/-ember-data/tests/integration/relationships/promise-many-array-test.js b/packages/-ember-data/tests/integration/relationships/promise-many-array-test.js index 98967d05e81..4c81810f393 100644 --- a/packages/-ember-data/tests/integration/relationships/promise-many-array-test.js +++ b/packages/-ember-data/tests/integration/relationships/promise-many-array-test.js @@ -8,6 +8,8 @@ import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import Model, { attr, hasMany } from '@ember-data/model'; +import { DEPRECATE_PROMISE_MANY_ARRAY_BEHAVIORS } from '@ember-data/private-build-infra/deprecations'; +import { deprecatedTest } from '@ember-data/unpublished-test-infra/test-support/deprecated-test'; module('PromiseManyArray', (hooks) => { setupRenderingTest(hooks); @@ -26,97 +28,108 @@ module('PromiseManyArray', (hooks) => { const members = ['Bob', 'John', 'Michael', 'Larry', 'Lucy'].map((name) => store.createRecord('person', { name })); const group = store.createRecord('group', { members }); - const replaceFn = group.members.replace; + const forEachFn = group.members.forEach; assert.strictEqual(group.members.length, 5, 'initial length is correct'); - group.members.replace(0, 1); - assert.strictEqual(group.members.length, 4, 'updated length is correct'); + if (DEPRECATE_PROMISE_MANY_ARRAY_BEHAVIORS) { + group.members.replace(0, 1); + assert.strictEqual(group.members.length, 4, 'updated length is correct'); + } A(group.members); - assert.strictEqual(replaceFn, group.members.replace, 'we have the same function for replace'); - group.members.replace(0, 1); - assert.strictEqual(group.members.length, 3, 'updated length is correct'); - }); + assert.strictEqual(forEachFn, group.members.forEach, 'we have the same function for forEach'); - test('PromiseManyArray can be subscribed to by computed chains', async function (assert) { - const { owner } = this; - class Person extends Model { - @attr('string') name; + if (DEPRECATE_PROMISE_MANY_ARRAY_BEHAVIORS) { + group.members.replace(0, 1); + assert.strictEqual(group.members.length, 3, 'updated length is correct'); + // we'll want to use a different test for this but will want to still ensure we are not side-affected + assert.expectDeprecation({ id: 'ember-data:deprecate-promise-many-array-behaviors', until: '5.0', count: 2 }); } - class Group extends Model { - @hasMany('person', { async: true, inverse: null }) members; + }); - @computed('members.@each.id') - get memberIds() { - return this.members.map((m) => m.id); + deprecatedTest( + 'PromiseManyArray can be subscribed to by computed chains', + { id: 'ember-data:deprecate-promise-many-array-behaviors', until: '5.0', count: 16 }, + async function (assert) { + const { owner } = this; + class Person extends Model { + @attr('string') name; } + class Group extends Model { + @hasMany('person', { async: true, inverse: null }) members; - @filterBy('members', 'name', 'John') - johns; - } - owner.register('model:person', Person); - owner.register('model:group', Group); - owner.register( - 'serializer:application', - class extends EmberObject { - normalizeResponse(_, __, data) { - return data; + @computed('members.@each.id') + get memberIds() { + return this.members.map((m) => m.id); } + + @filterBy('members', 'name', 'John') + johns; } - ); - - let _id = 0; - const names = ['Bob', 'John', 'Michael', 'John', 'Larry', 'Lucy']; - owner.register( - 'adapter:application', - class extends EmberObject { - findRecord() { - const name = names[_id++]; - const data = { - type: 'person', - id: `${_id}`, - attributes: { - name, - }, - }; - return { data }; + owner.register('model:person', Person); + owner.register('model:group', Group); + owner.register( + 'serializer:application', + class extends EmberObject { + normalizeResponse(_, __, data) { + return data; + } } - } - ); - const store = owner.lookup('service:store'); - - const group = store.push({ - data: { - type: 'group', - id: '1', - relationships: { - members: { - data: [ - { type: 'person', id: '1' }, - { type: 'person', id: '2' }, - { type: 'person', id: '3' }, - { type: 'person', id: '4' }, - { type: 'person', id: '5' }, - { type: 'person', id: '6' }, - ], + ); + + let _id = 0; + const names = ['Bob', 'John', 'Michael', 'John', 'Larry', 'Lucy']; + owner.register( + 'adapter:application', + class extends EmberObject { + findRecord() { + const name = names[_id++]; + const data = { + type: 'person', + id: `${_id}`, + attributes: { + name, + }, + }; + return { data }; + } + } + ); + const store = owner.lookup('service:store'); + + const group = store.push({ + data: { + type: 'group', + id: '1', + relationships: { + members: { + data: [ + { type: 'person', id: '1' }, + { type: 'person', id: '2' }, + { type: 'person', id: '3' }, + { type: 'person', id: '4' }, + { type: 'person', id: '5' }, + { type: 'person', id: '6' }, + ], + }, }, }, - }, - }); - - // access the group data - let memberIds = group.memberIds; - let johnRecords = group.johns; - assert.strictEqual(memberIds.length, 0, 'member ids is 0 initially'); - assert.strictEqual(johnRecords.length, 0, 'john ids is 0 initially'); - - await settled(); - - memberIds = group.memberIds; - johnRecords = group.johns; - assert.strictEqual(memberIds.length, 6, 'memberIds length is correct'); - assert.strictEqual(johnRecords.length, 2, 'johnRecords length is correct'); - assert.strictEqual(group.members.length, 6, 'members length is correct'); - }); + }); + + // access the group data + let memberIds = group.memberIds; + let johnRecords = group.johns; + assert.strictEqual(memberIds.length, 0, 'member ids is 0 initially'); + assert.strictEqual(johnRecords.length, 0, 'john ids is 0 initially'); + + await settled(); + + memberIds = group.memberIds; + johnRecords = group.johns; + assert.strictEqual(memberIds.length, 6, 'memberIds length is correct'); + assert.strictEqual(johnRecords.length, 2, 'johnRecords length is correct'); + assert.strictEqual(group.members.length, 6, 'members length is correct'); + } + ); }); diff --git a/packages/-ember-data/tests/integration/serializers/embedded-records-mixin-test.js b/packages/-ember-data/tests/integration/serializers/embedded-records-mixin-test.js index 561b3727019..7807e457448 100644 --- a/packages/-ember-data/tests/integration/serializers/embedded-records-mixin-test.js +++ b/packages/-ember-data/tests/integration/serializers/embedded-records-mixin-test.js @@ -1597,14 +1597,14 @@ module('integration/embedded-records-mixin', function (hooks) { superVillain, }); - superVillain.get('secretWeapons').pushObject(secretWeapon); + superVillain.secretWeapons.pushObject(secretWeapon); let evilMinion = store.createRecord('evil-minion', { id: '1', name: 'Evil Minion', superVillain, }); - superVillain.get('evilMinions').pushObject(evilMinion); + superVillain.evilMinions.pushObject(evilMinion); const serializer = store.serializerFor('super-villain'); const serializedRestJson = serializer.serialize(superVillain._createSnapshot()); @@ -1687,13 +1687,13 @@ module('integration/embedded-records-mixin', function (hooks) { superVillain, }); - superVillain.get('secretWeapons').pushObject(secretWeapon); + superVillain.secretWeapons.pushObject(secretWeapon); let evilMinion = store.createRecord('evil-minion', { id: '1', name: 'Evil Minion', superVillain, }); - superVillain.get('evilMinions').pushObject(evilMinion); + superVillain.evilMinions.pushObject(evilMinion); const serializer = store.serializerFor('super-villain'); const serializedRestJson = serializer.serialize(superVillain._createSnapshot()); @@ -1936,8 +1936,8 @@ module('integration/embedded-records-mixin', function (hooks) { superVillain, }); - superVillain.get('evilMinions').pushObject(evilMinion); - superVillain.get('secretWeapons').pushObject(secretWeapon); + superVillain.evilMinions.pushObject(evilMinion); + superVillain.secretWeapons.pushObject(secretWeapon); const serializer = store.serializerFor('super-villain'); const serializedRestJson = serializer.serialize(superVillain._createSnapshot()); diff --git a/packages/-ember-data/tests/integration/serializers/json-api-serializer-test.js b/packages/-ember-data/tests/integration/serializers/json-api-serializer-test.js index 81bad529623..fd0126b0baa 100644 --- a/packages/-ember-data/tests/integration/serializers/json-api-serializer-test.js +++ b/packages/-ember-data/tests/integration/serializers/json-api-serializer-test.js @@ -55,68 +55,64 @@ module('integration/serializers/json-api-serializer - JSONAPISerializer', functi this.owner.register('serializer:application', class extends JSONAPISerializer {}); }); - test('Calling pushPayload works', function (assert) { + test('Calling pushPayload works', async function (assert) { let store = this.owner.lookup('service:store'); let serializer = store.serializerFor('application'); - run(function () { - serializer.pushPayload(store, { - data: { - type: 'users', - id: '1', - attributes: { - 'first-name': 'Yehuda', - 'last-name': 'Katz', + serializer.pushPayload(store, { + data: { + type: 'users', + id: '1', + attributes: { + 'first-name': 'Yehuda', + 'last-name': 'Katz', + }, + relationships: { + company: { + data: { type: 'companies', id: '2' }, }, - relationships: { - company: { - data: { type: 'companies', id: '2' }, - }, - handles: { - data: [ - { type: 'github-handles', id: '3' }, - { type: 'twitter-handles', id: '4' }, - ], - }, + handles: { + data: [ + { type: 'github-handles', id: '3' }, + { type: 'twitter-handles', id: '4' }, + ], }, }, - included: [ - { - type: 'companies', - id: '2', - attributes: { - name: 'Tilde Inc.', - }, + }, + included: [ + { + type: 'companies', + id: '2', + attributes: { + name: 'Tilde Inc.', }, - { - type: 'github-handles', - id: '3', - attributes: { - username: 'wycats', - }, + }, + { + type: 'github-handles', + id: '3', + attributes: { + username: 'wycats', }, - { - type: 'twitter-handles', - id: '4', - attributes: { - nickname: '@wycats', - }, + }, + { + type: 'twitter-handles', + id: '4', + attributes: { + nickname: '@wycats', }, - ], - }); - - var user = store.peekRecord('user', 1); - - assert.strictEqual(get(user, 'firstName'), 'Yehuda', 'firstName is correct'); - assert.strictEqual(get(user, 'lastName'), 'Katz', 'lastName is correct'); - assert.strictEqual(get(user, 'company.name'), 'Tilde Inc.', 'company.name is correct'); - assert.strictEqual( - get(user, 'handles.firstObject.username'), - 'wycats', - 'handles.firstObject.username is correct' - ); - assert.strictEqual(get(user, 'handles.lastObject.nickname'), '@wycats', 'handles.lastObject.nickname is correct'); + }, + ], }); + + const user = store.peekRecord('user', 1); + const company = await user.company; + const handles = await user.handles; + + assert.strictEqual(user.firstName, 'Yehuda', 'firstName is correct'); + assert.strictEqual(user.lastName, 'Katz', 'lastName is correct'); + assert.strictEqual(company.name, 'Tilde Inc.', 'company.name is correct'); + assert.strictEqual(handles.firstObject.username, 'wycats', 'handles.firstObject.username is correct'); + assert.strictEqual(handles.lastObject.nickname, '@wycats', 'handles.lastObject.nickname is correct'); }); testInDebug('Warns when normalizing an unknown type', function (assert) { @@ -666,7 +662,7 @@ module('integration/serializers/json-api-serializer - JSONAPISerializer', functi }); }); - test('it should include an empty list when serializing an empty hasMany relationship', function (assert) { + test('it should include an empty list when serializing an empty hasMany relationship', async function (assert) { this.owner.register( 'serializer:user', JSONAPISerializer.extend({ @@ -678,50 +674,50 @@ module('integration/serializers/json-api-serializer - JSONAPISerializer', functi let store = this.owner.lookup('service:store'); - run(function () { - store.serializerFor('user').pushPayload(store, { - data: { - type: 'users', - id: 1, - relationships: { - handles: { - data: [ - { type: 'handles', id: 1 }, - { type: 'handles', id: 2 }, - ], - }, + store.serializerFor('user').pushPayload(store, { + data: { + type: 'users', + id: '1', + relationships: { + handles: { + data: [ + { type: 'handles', id: '1' }, + { type: 'handles', id: '2' }, + ], }, }, - included: [ - { type: 'handles', id: 1 }, - { type: 'handles', id: 2 }, - ], - }); + }, + included: [ + { type: 'handles', id: '1' }, + { type: 'handles', id: '2' }, + ], + }); - let user = store.peekRecord('user', 1); - let handle1 = store.peekRecord('handle', 1); - let handle2 = store.peekRecord('handle', 2); - user.get('handles').removeObject(handle1); - user.get('handles').removeObject(handle2); + let user = store.peekRecord('user', '1'); + let handle1 = store.peekRecord('handle', '1'); + let handle2 = store.peekRecord('handle', '2'); - let serialized = user.serialize({ includeId: true }); + const handles = await user.handles; + handles.removeObject(handle1); + handles.removeObject(handle2); - assert.deepEqual(serialized, { - data: { - type: 'users', - id: '1', - attributes: { - 'first-name': null, - 'last-name': null, - title: null, - }, - relationships: { - handles: { - data: [], - }, + let serialized = user.serialize({ includeId: true }); + + assert.deepEqual(serialized, { + data: { + type: 'users', + id: '1', + attributes: { + 'first-name': null, + 'last-name': null, + title: null, + }, + relationships: { + handles: { + data: [], }, }, - }); + }, }); }); diff --git a/packages/-ember-data/tests/integration/serializers/json-serializer-test.js b/packages/-ember-data/tests/integration/serializers/json-serializer-test.js index 4366eb6c821..011d5bb20a1 100644 --- a/packages/-ember-data/tests/integration/serializers/json-serializer-test.js +++ b/packages/-ember-data/tests/integration/serializers/json-serializer-test.js @@ -307,7 +307,7 @@ module('integration/serializer/json - JSONSerializer', function (hooks) { }); run(function () { - post.get('comments').pushObject(comment); + post.comments.pushObject(comment); }); let json = {}; @@ -895,7 +895,7 @@ module('integration/serializer/json - JSONSerializer', function (hooks) { assert.notOk(payload.hasOwnProperty(serializedProperty), 'Does not add the key to instance'); }); - test('Serializer respects `serialize: true` on the attrs hash for a `hasMany` property', function (assert) { + test('Serializer respects `serialize: true` on the attrs hash for a `hasMany` property', async function (assert) { assert.expect(1); class Post extends Model { @attr('string') title; @@ -922,14 +922,13 @@ module('integration/serializer/json - JSONSerializer', function (hooks) { let post = store.createRecord('post', { title: 'Rails is omakase' }); let comment = store.createRecord('comment', { body: 'Omakase is delicious', post: post }); - run(function () { - post.get('comments').pushObject(comment); - }); + const comments = await post.comments; + comments.pushObject(comment); - var serializer = store.serializerFor('post'); - var serializedProperty = serializer.keyForRelationship('comments', 'hasMany'); + const serializer = store.serializerFor('post'); + const serializedProperty = serializer.keyForRelationship('comments', 'hasMany'); + const payload = serializer.serialize(post._createSnapshot()); - var payload = serializer.serialize(post._createSnapshot()); assert.ok(payload.hasOwnProperty(serializedProperty), 'Add the key to instance'); }); diff --git a/packages/-ember-data/tests/integration/serializers/rest-serializer-test.js b/packages/-ember-data/tests/integration/serializers/rest-serializer-test.js index 77c329ac3d8..b615be1301a 100644 --- a/packages/-ember-data/tests/integration/serializers/rest-serializer-test.js +++ b/packages/-ember-data/tests/integration/serializers/rest-serializer-test.js @@ -7,6 +7,7 @@ import DS from 'ember-data'; import Inflector, { singularize } from 'ember-inflector'; import { setupTest } from 'ember-qunit'; +import Model, { attr, belongsTo, hasMany } from '@ember-data/model'; import RESTSerializer from '@ember-data/serializer/rest'; import testInDebug from '@ember-data/unpublished-test-infra/test-support/test-in-debug'; @@ -16,40 +17,40 @@ module('integration/serializer/rest - RESTSerializer', function (hooks) { setupTest(hooks); hooks.beforeEach(function () { - HomePlanet = DS.Model.extend({ - name: DS.attr('string'), - superVillains: DS.hasMany('super-villain', { async: false }), + HomePlanet = Model.extend({ + name: attr('string'), + superVillains: hasMany('super-villain', { async: false }), }); - SuperVillain = DS.Model.extend({ - firstName: DS.attr('string'), - lastName: DS.attr('string'), - homePlanet: DS.belongsTo('home-planet', { async: false }), - evilMinions: DS.hasMany('evil-minion', { async: false }), + SuperVillain = Model.extend({ + firstName: attr('string'), + lastName: attr('string'), + homePlanet: belongsTo('home-planet', { async: false }), + evilMinions: hasMany('evil-minion', { async: false }), }); - EvilMinion = DS.Model.extend({ - superVillain: DS.belongsTo('super-villain', { async: false }), - name: DS.attr('string'), - doomsdayDevice: DS.belongsTo('doomsday-device', { async: false }), + EvilMinion = Model.extend({ + superVillain: belongsTo('super-villain', { async: false }), + name: attr('string'), + doomsdayDevice: belongsTo('doomsday-device', { async: false }), }); YellowMinion = EvilMinion.extend({ - eyes: DS.attr('number'), + eyes: attr('number'), }); - DoomsdayDevice = DS.Model.extend({ - name: DS.attr('string'), - evilMinion: DS.belongsTo('evil-minion', { polymorphic: true, async: true }), + DoomsdayDevice = Model.extend({ + name: attr('string'), + evilMinion: belongsTo('evil-minion', { polymorphic: true, async: true }), }); - Comment = DS.Model.extend({ - body: DS.attr('string'), - root: DS.attr('boolean'), - children: DS.hasMany('comment', { inverse: null, async: false }), + Comment = Model.extend({ + body: attr('string'), + root: attr('boolean'), + children: hasMany('comment', { inverse: null, async: false }), }); - Basket = DS.Model.extend({ - type: DS.attr('string'), - size: DS.attr('number'), + Basket = Model.extend({ + type: attr('string'), + size: attr('number'), }); - Container = DS.Model.extend({ - type: DS.belongsTo('basket', { async: true }), - volume: DS.attr('string'), + Container = Model.extend({ + type: belongsTo('basket', { async: true }), + volume: attr('string'), }); this.owner.register('model:super-villain', SuperVillain); @@ -682,10 +683,10 @@ module('integration/serializer/rest - RESTSerializer', function (hooks) { store .findRecord('doomsday-device', 1) .then((deathRay) => { - return deathRay.get('evilMinion'); + return deathRay.evilMinion; }) .then((evilMinion) => { - assert.strictEqual(evilMinion.get('eyes'), 3); + assert.strictEqual(evilMinion.eyes, 3); }); }); }); @@ -723,31 +724,31 @@ module('integration/serializer/rest - RESTSerializer', function (hooks) { store .findRecord('doomsday-device', 1) .then((deathRay) => { - return deathRay.get('evilMinion'); + return deathRay.evilMinion; }) .then((evilMinion) => { - assert.strictEqual(evilMinion.get('eyes'), 3); + assert.strictEqual(evilMinion.eyes, 3); }); }); }); test('normalizeResponse with async polymorphic hasMany', function (assert) { - const HomePlanet = DS.Model.extend({ - name: DS.attr('string'), - superVillains: DS.hasMany('super-villain2', { async: false }), + const HomePlanet = Model.extend({ + name: attr('string'), + superVillains: hasMany('super-villain2', { async: false }), }); - const SuperVillain = DS.Model.extend({ - firstName: DS.attr('string'), - lastName: DS.attr('string'), - homePlanet: DS.belongsTo('home-planet2', { async: false }), - evilMinions: DS.hasMany('evil-minion2', { async: true, polymorphic: true }), + const SuperVillain = Model.extend({ + firstName: attr('string'), + lastName: attr('string'), + homePlanet: belongsTo('home-planet2', { async: false }), + evilMinions: hasMany('evil-minion2', { async: true, polymorphic: true }), }); - const EvilMinion = DS.Model.extend({ - superVillain: DS.belongsTo('super-villain2', { async: false }), - name: DS.attr('string'), + const EvilMinion = Model.extend({ + superVillain: belongsTo('super-villain2', { async: false }), + name: attr('string'), }); const YellowMinion = EvilMinion.extend({ - eyes: DS.attr('number'), + eyes: attr('number'), }); this.owner.register('model:super-villain2', SuperVillain); @@ -790,11 +791,11 @@ module('integration/serializer/rest - RESTSerializer', function (hooks) { store .findRecord('super-villain2', '1') .then((superVillain) => { - return superVillain.get('evilMinions'); + return superVillain.evilMinions; }) .then((evilMinions) => { - assert.ok(evilMinions.get('firstObject') instanceof YellowMinion, 'we have an instance'); - assert.strictEqual(evilMinions.get('firstObject.eyes'), 3, 'we have the right minion'); + assert.ok(evilMinions.firstObject instanceof YellowMinion, 'we have an instance'); + assert.strictEqual(evilMinions.firstObject.eyes, 3, 'we have the right minion'); }); }); }); @@ -874,13 +875,13 @@ module('integration/serializer/rest - RESTSerializer', function (hooks) { const normalRecord = store.peekRecord('basket', '1'); assert.ok(normalRecord, "payload with type that doesn't exist"); - assert.strictEqual(normalRecord.get('type'), 'bamboo'); - assert.strictEqual(normalRecord.get('size'), 10); + assert.strictEqual(normalRecord.type, 'bamboo'); + assert.strictEqual(normalRecord.size, 10); const clashingRecord = store.peekRecord('basket', '65536'); assert.ok(clashingRecord, 'payload with type that matches another model name'); - assert.strictEqual(clashingRecord.get('type'), 'yellowMinion'); - assert.strictEqual(clashingRecord.get('size'), 10); + assert.strictEqual(clashingRecord.type, 'yellowMinion'); + assert.strictEqual(clashingRecord.size, 10); }); test("don't polymorphically deserialize base on the type key in payload when a type attribute exist on a singular response", function (assert) { @@ -902,8 +903,8 @@ module('integration/serializer/rest - RESTSerializer', function (hooks) { const clashingRecord = store.peekRecord('basket', '65536'); assert.ok(clashingRecord, 'payload with type that matches another model name'); - assert.strictEqual(clashingRecord.get('type'), 'yellowMinion'); - assert.strictEqual(clashingRecord.get('size'), 10); + assert.strictEqual(clashingRecord.type, 'yellowMinion'); + assert.strictEqual(clashingRecord.size, 10); }); test("don't polymorphically deserialize based on the type key in payload when a relationship exists named type", function (assert) { @@ -921,12 +922,12 @@ module('integration/serializer/rest - RESTSerializer', function (hooks) { store .findRecord('container', 42) .then((container) => { - assert.strictEqual(container.get('volume'), '10 liters'); - return container.get('type'); + assert.strictEqual(container.volume, '10 liters'); + return container.type; }) .then((basket) => { assert.ok(basket instanceof Basket); - assert.strictEqual(basket.get('size'), 4); + assert.strictEqual(basket.size, 4); }); }); }); diff --git a/packages/-ember-data/tests/integration/snapshot-test.js b/packages/-ember-data/tests/integration/snapshot-test.js index 76c1b8a704f..dcb8b07b7e9 100644 --- a/packages/-ember-data/tests/integration/snapshot-test.js +++ b/packages/-ember-data/tests/integration/snapshot-test.js @@ -134,8 +134,8 @@ module('integration/snapshot - Snapshot', function (hooks) { }, }, }); - let postInternalModel = store._instanceCache._internalModelForResource({ type: 'post', id: '1' }); - let snapshot = await store._instanceCache.createSnapshot(postInternalModel.identifier); + let identifier = store.identifierCache.getOrCreateRecordIdentifier({ type: 'post', id: '1' }); + let snapshot = await store._instanceCache.createSnapshot(identifier); assert.false(postClassLoaded, 'model class is not eagerly loaded'); assert.strictEqual(snapshot.type, _Post, 'type is correct'); @@ -146,7 +146,9 @@ module('integration/snapshot - Snapshot', function (hooks) { test('an initial findRecord call has no record for internal-model when a snapshot is generated', function (assert) { assert.expect(2); store.adapterFor('application').findRecord = (store, type, id, snapshot) => { - assert.false(snapshot._internalModel.hasRecord, 'We do not have a materialized record'); + const identifier = store.identifierCache.getOrCreateRecordIdentifier({ type: 'post', id: '1' }); + const record = store._instanceCache.peek({ identifier, bucket: 'record' }); + assert.false(!!record, 'We do not have a materialized record'); assert.strictEqual(snapshot.__attributes, null, 'attributes were not populated initially'); return resolve({ data: { @@ -175,8 +177,8 @@ module('integration/snapshot - Snapshot', function (hooks) { }, }); - let postInternalModel = store._instanceCache._internalModelForResource({ type: 'post', id: '1' }); - let snapshot = store._instanceCache.createSnapshot(postInternalModel.identifier); + let identifier = store.identifierCache.getOrCreateRecordIdentifier({ type: 'post', id: '1' }); + let snapshot = store._instanceCache.createSnapshot(identifier); let expected = { author: undefined, title: 'Hello World', @@ -200,8 +202,8 @@ module('integration/snapshot - Snapshot', function (hooks) { }, }); - let postInternalModel = store._instanceCache._internalModelForResource({ type: 'post', id: '1' }); - let snapshot = store._instanceCache.createSnapshot(postInternalModel.identifier); + let identifier = store.identifierCache.getOrCreateRecordIdentifier({ type: 'post', id: '1' }); + let snapshot = store._instanceCache.createSnapshot(identifier); let expected = { author: undefined, title: 'Hello World', @@ -506,7 +508,7 @@ module('integration/snapshot - Snapshot', function (hooks) { let comment = store.peekRecord('comment', 2); assert.strictEqual(comment._createSnapshot().belongsTo('post'), undefined, 'relationship is undefined'); - await comment.get('post'); + await comment.post; assert.strictEqual(comment._createSnapshot().belongsTo('post'), undefined, 'relationship is undefined'); }); @@ -559,7 +561,7 @@ module('integration/snapshot - Snapshot', function (hooks) { }); let comment = store.peekRecord('comment', 2); - await comment.get('post').then((post) => { + await comment.post.then((post) => { store.push({ data: [ { @@ -580,7 +582,7 @@ module('integration/snapshot - Snapshot', function (hooks) { }); let comment = store.peekRecord('comment', 2); - post.get('comments').then((comments) => { + post.comments.then((comments) => { comments.addObject(comment); let postSnapshot = post._createSnapshot(); @@ -626,7 +628,7 @@ module('integration/snapshot - Snapshot', function (hooks) { let post = store.peekRecord('post', 1); let comment = store.peekRecord('comment', 2); - const comments = await post.get('comments'); + const comments = await post.comments; comments.addObject(comment); let postSnapshot = post._createSnapshot(); @@ -1034,7 +1036,7 @@ module('integration/snapshot - Snapshot', function (hooks) { let post = store.peekRecord('post', 1); - await post.get('comments').then((comments) => { + await post.comments.then((comments) => { let snapshot = post._createSnapshot(); let relationship = snapshot.hasMany('comments'); @@ -1067,7 +1069,7 @@ module('integration/snapshot - Snapshot', function (hooks) { ); }); - test('snapshot.hasMany() respects the order of items in the relationship', function (assert) { + test('snapshot.hasMany() respects the order of items in the relationship', async function (assert) { assert.expect(3); store.push({ @@ -1113,9 +1115,9 @@ module('integration/snapshot - Snapshot', function (hooks) { }); let comment3 = store.peekRecord('comment', 3); let post = store.peekRecord('post', 4); - - post.get('comments').removeObject(comment3); - post.get('comments').insertAt(0, comment3); + const comments = await post.comments; + comments.removeObject(comment3); + comments.insertAt(0, comment3); let snapshot = post._createSnapshot(); let relationship = snapshot.hasMany('comments'); diff --git a/packages/-ember-data/tests/integration/store-test.js b/packages/-ember-data/tests/integration/store-test.js index 3164d2e2dc5..17447121868 100644 --- a/packages/-ember-data/tests/integration/store-test.js +++ b/packages/-ember-data/tests/integration/store-test.js @@ -226,7 +226,7 @@ module('integration/store - destroy', function (hooks) { let personWillDestroy = tap(person, 'willDestroy'); let carWillDestroy = tap(car, 'willDestroy'); - let carsWillDestroy = tap(car.get('person.cars'), 'willDestroy'); + let carsWillDestroy = tap(car.person.cars, 'willDestroy'); adapter.query = function () { return { @@ -256,12 +256,8 @@ module('integration/store - destroy', function (hooks) { 0, 'expected adapterPopulatedPeople.willDestroy to not have been called' ); - assert.strictEqual(car.get('person'), person, "expected car's person to be the correct person"); - assert.strictEqual( - person.get('cars.firstObject'), - car, - " expected persons cars's firstRecord to be the correct car" - ); + assert.strictEqual(car.person, person, "expected car's person to be the correct person"); + assert.strictEqual(person.cars.firstObject, car, " expected persons cars's firstRecord to be the correct car"); store.destroy(); @@ -310,11 +306,11 @@ module('integration/store - findRecord', function (hooks) { let car = await store.findRecord('car', '20'); - assert.strictEqual(car.get('make'), 'BMC', 'Car with id=20 is now loaded'); + assert.strictEqual(car.make, 'BMC', 'Car with id=20 is now loaded'); }); test('store#findRecord returns cached record immediately and reloads record in the background', async function (assert) { - assert.expect(4); + assert.expect(2); let adapter = store.adapterFor('application'); @@ -346,16 +342,13 @@ module('integration/store - findRecord', function (hooks) { }; }; - const promiseCar = store.findRecord('car', '1'); - const car = await promiseCar; + const car = await store.findRecord('car', '1'); - assert.strictEqual(promiseCar.get('model'), 'Mini', 'promiseCar is from cache'); - assert.strictEqual(car.get('model'), 'Mini', 'car record is returned from cache'); + assert.strictEqual(car.model, 'Mini', 'car record is returned from cache'); await settled(); - assert.strictEqual(promiseCar.get('model'), 'Princess', 'promiseCar is updated'); - assert.strictEqual(car.get('model'), 'Princess', 'Updated car record is returned'); + assert.strictEqual(car.model, 'Princess', 'Updated car record is returned'); }); test('store#findRecord { reload: true } ignores cached record and reloads record from server', async function (assert) { @@ -394,7 +387,7 @@ module('integration/store - findRecord', function (hooks) { let cachedCar = store.peekRecord('car', '1'); - assert.strictEqual(cachedCar.get('model'), 'Mini', 'cached car has expected model'); + assert.strictEqual(cachedCar.model, 'Mini', 'cached car has expected model'); let car = await store.findRecord('car', '1', { reload: true }); @@ -438,12 +431,12 @@ module('integration/store - findRecord', function (hooks) { let promiseCar = store.findRecord('car', '1', { reload: true }); - assert.strictEqual(promiseCar.get('model'), undefined, `We don't have early access to local data`); + assert.strictEqual(promiseCar.model, undefined, `We don't have early access to local data`); car = await promiseCar; assert.strictEqual(calls, 2, 'We made a second call to findRecord'); - assert.strictEqual(car.get('model'), 'Princess', 'cached record ignored, record reloaded via server'); + assert.strictEqual(car.model, 'Princess', 'cached record ignored, record reloaded via server'); }); test('store#findRecord caches the inflight requests', async function (assert) { @@ -670,11 +663,11 @@ module('integration/store - findRecord', function (hooks) { let car = store.peekRecord('car', '1'); - assert.strictEqual(car.get('model'), 'Mini', 'Car record is initially a Mini'); + assert.strictEqual(car.model, 'Mini', 'Car record is initially a Mini'); car = await store.findRecord('car', '1', { backgroundReload: false }); - assert.strictEqual(car.get('model'), 'Princess', 'Car record is reloaded immediately (not in the background)'); + assert.strictEqual(car.model, 'Princess', 'Car record is reloaded immediately (not in the background)'); }); test('store#findRecord call with `id` of type different than non-empty string or number should trigger an assertion', function (assert) { @@ -1196,11 +1189,6 @@ module('integration/store - deleteRecord', function (hooks) { await assert.expectAssertion(async () => { await car.save(); }, /Your car record was saved to the server, but the response does not have an id and no id has been set client side. Records must have ids. Please update the server response to provide an id in the response or generate the id on the client side either before saving the record or while normalizing the response./); - - // This is here to transition the model out of the inFlight state to avoid - // throwing another error when the test context is torn down, which tries - // to unload the record, which is not allowed when record is inFlight. - // car._internalModel.transitionTo('loaded.saved'); }); }); diff --git a/packages/-ember-data/tests/integration/store/query-test.js b/packages/-ember-data/tests/integration/store/query-test.js index 6a4d004c7a6..1e44f30d51c 100644 --- a/packages/-ember-data/tests/integration/store/query-test.js +++ b/packages/-ember-data/tests/integration/store/query-test.js @@ -1,49 +1,44 @@ -import { run } from '@ember/runloop'; - import { module, test } from 'qunit'; import RSVP from 'rsvp'; -import DS from 'ember-data'; import { setupTest } from 'ember-qunit'; +import Adapter from '@ember-data/adapter'; +import Model from '@ember-data/model'; import JSONAPISerializer from '@ember-data/serializer/json-api'; module('integration/store/query', function (hooks) { setupTest(hooks); hooks.beforeEach(function () { - const Person = DS.Model.extend(); + const Person = Model.extend(); this.owner.register('model:person', Person); - this.owner.register('adapter:application', DS.Adapter.extend()); + this.owner.register('adapter:application', Adapter.extend()); this.owner.register('serializer:application', class extends JSONAPISerializer {}); }); - test('meta is proxied correctly on the PromiseArray', function (assert) { + test('meta is proxied correctly on the PromiseArray', async function (assert) { let store = this.owner.lookup('service:store'); let defered = RSVP.defer(); this.owner.register( 'adapter:person', - DS.Adapter.extend({ + Adapter.extend({ query(store, type, query) { return defered.promise; }, }) ); - let result; - run(function () { - result = store.query('person', {}); - }); + let result = store.query('person', {}); - assert.notOk(result.get('meta.foo'), 'precond: meta is not yet set'); + assert.notOk(result.meta?.foo, 'precond: meta is not yet set'); - run(function () { - defered.resolve({ data: [], meta: { foo: 'bar' } }); - }); + defered.resolve({ data: [], meta: { foo: 'bar' } }); + await result; - assert.strictEqual(result.get('meta.foo'), 'bar'); + assert.strictEqual(result.meta.foo, 'bar'); }); }); diff --git a/packages/-ember-data/tests/test-helper.js b/packages/-ember-data/tests/test-helper.js index 057ae6a25c6..147cf2a3d4d 100644 --- a/packages/-ember-data/tests/test-helper.js +++ b/packages/-ember-data/tests/test-helper.js @@ -7,7 +7,6 @@ import RSVP from 'rsvp'; import { start } from 'ember-qunit'; import assertAllDeprecations from '@ember-data/unpublished-test-infra/test-support/assert-all-deprecations'; -import additionalLegacyAsserts from '@ember-data/unpublished-test-infra/test-support/legacy'; import configureAsserts from '@ember-data/unpublished-test-infra/test-support/qunit-asserts'; import customQUnitAdapter from '@ember-data/unpublished-test-infra/test-support/testem/custom-qunit-adapter'; @@ -26,7 +25,6 @@ if (QUnit.urlParams.enableoptionalfeatures) { setup(QUnit.assert); configureAsserts(); -additionalLegacyAsserts(); setApplication(Application.create(config.APP)); diff --git a/packages/-ember-data/tests/unit/adapters/rest-adapter/group-records-for-find-many-test.js b/packages/-ember-data/tests/unit/adapters/rest-adapter/group-records-for-find-many-test.js index 4cd53ce2314..abe756fd3a3 100644 --- a/packages/-ember-data/tests/unit/adapters/rest-adapter/group-records-for-find-many-test.js +++ b/packages/-ember-data/tests/unit/adapters/rest-adapter/group-records-for-find-many-test.js @@ -44,7 +44,7 @@ module( .join('&'); let fullUrl = url + '?' + queryString; - maxLength = this.get('maxURLLength'); + maxLength = this.maxURLLength; lengths.push(fullUrl.length); let testRecords = options.data.ids.map((id) => ({ id })); diff --git a/packages/-ember-data/tests/unit/custom-class-support/custom-class-model-test.ts b/packages/-ember-data/tests/unit/custom-class-support/custom-class-model-test.ts index 7afcfb5b65d..983d6008ba1 100644 --- a/packages/-ember-data/tests/unit/custom-class-support/custom-class-model-test.ts +++ b/packages/-ember-data/tests/unit/custom-class-support/custom-class-model-test.ts @@ -9,9 +9,9 @@ import JSONAPIAdapter from '@ember-data/adapter/json-api'; import JSONAPISerializer from '@ember-data/serializer/json-api'; import Store from '@ember-data/store'; import type { RecordDataStoreWrapper, Snapshot } from '@ember-data/store/-private'; -import type NotificationManager from '@ember-data/store/-private/record-notification-manager'; +import type NotificationManager from '@ember-data/store/-private/managers/record-notification-manager'; import type { RecordIdentifier, StableRecordIdentifier } from '@ember-data/types/q/identifier'; -import type { RecordDataRecordWrapper } from '@ember-data/types/q/record-data-record-wrapper'; +import type { RecordDataWrapper } from '@ember-data/types/q/record-data-record-wrapper'; import type { AttributesSchema, RelationshipsSchema } from '@ember-data/types/q/record-data-schemas'; import type { RecordInstance } from '@ember-data/types/q/record-instance'; import type { SchemaDefinitionService } from '@ember-data/types/q/schema-definition-service'; @@ -313,7 +313,7 @@ module('unit/model - Custom Class Model', function (hooks) { }); test('store.deleteRecord', async function (assert) { - let rd: RecordDataRecordWrapper; + let rd: RecordDataWrapper; assert.expect(9); this.owner.register( 'adapter:application', diff --git a/packages/-ember-data/tests/unit/many-array-test.js b/packages/-ember-data/tests/unit/many-array-test.js index 60e55415539..a5df7929224 100644 --- a/packages/-ember-data/tests/unit/many-array-test.js +++ b/packages/-ember-data/tests/unit/many-array-test.js @@ -71,12 +71,9 @@ module('unit/many_array - ManyArray', function (hooks) { let post = store.peekRecord('post', 3); - return post - .get('tags') - .save() - .then(() => { - assert.ok(true, 'manyArray.save() promise resolved'); - }); + return post.tags.save().then(() => { + assert.ok(true, 'manyArray.save() promise resolved'); + }); }); }); @@ -166,7 +163,7 @@ module('unit/many_array - ManyArray', function (hooks) { ], }); - store.peekRecord('post', 3).get('tags'); + store.peekRecord('post', 3).tags; store.push({ data: { diff --git a/packages/-ember-data/tests/unit/model-test.js b/packages/-ember-data/tests/unit/model-test.js index 7ca91c61356..bdd0e582035 100644 --- a/packages/-ember-data/tests/unit/model-test.js +++ b/packages/-ember-data/tests/unit/model-test.js @@ -12,6 +12,7 @@ import JSONAPIAdapter from '@ember-data/adapter/json-api'; import Model, { attr, attr as DSattr } from '@ember-data/model'; import JSONSerializer from '@ember-data/serializer/json'; import JSONAPISerializer from '@ember-data/serializer/json-api'; +import { recordIdentifierFor } from '@ember-data/store'; import { recordDataFor } from '@ember-data/store/-private'; import testInDebug from '@ember-data/unpublished-test-infra/test-support/test-in-debug'; @@ -298,7 +299,7 @@ module('unit/model - Model', function (hooks) { test('setting the id during createRecord should correctly update the id', async function (assert) { let person = store.createRecord('person', { id: 'john' }); - assert.strictEqual(person.get('id'), 'john', 'new id should be correctly set.'); + assert.strictEqual(person.id, 'john', 'new id should be correctly set.'); let record = store.peekRecord('person', 'john'); @@ -308,11 +309,11 @@ module('unit/model - Model', function (hooks) { test('setting the id after createRecord should correctly update the id', async function (assert) { let person = store.createRecord('person'); - assert.strictEqual(person.get('id'), null, 'initial created model id should be null'); + assert.strictEqual(person.id, null, 'initial created model id should be null'); person.set('id', 'john'); - assert.strictEqual(person.get('id'), 'john', 'new id should be correctly set.'); + assert.strictEqual(person.id, 'john', 'new id should be correctly set.'); let record = store.peekRecord('person', 'john'); @@ -322,7 +323,7 @@ module('unit/model - Model', function (hooks) { testInDebug('mutating the id after createRecord but before save works', async function (assert) { let person = store.createRecord('person', { id: 'chris' }); - assert.strictEqual(person.get('id'), 'chris', 'initial created model id should be null'); + assert.strictEqual(person.id, 'chris', 'initial created model id should be null'); try { person.set('id', 'john'); @@ -342,63 +343,27 @@ module('unit/model - Model', function (hooks) { const OddPerson = Model.extend({ name: DSattr('string'), idComputed: computed('id', function () { - return this.get('id'); + return this.id; }), }); this.owner.register('model:odd-person', OddPerson); let person = store.createRecord('odd-person'); - let oddId = person.get('idComputed'); + let oddId = person.idComputed; assert.strictEqual(oddId, null, 'initial computed get is null'); // test .get access of id - assert.strictEqual(person.get('id'), null, 'initial created model id should be null'); + assert.strictEqual(person.id, null, 'initial created model id should be null'); - store._instanceCache.setRecordId('odd-person', 'john', person._internalModel.clientId); + const identifier = recordIdentifierFor(person); + store._instanceCache.setRecordId('odd-person', 'john', identifier.lid); - oddId = person.get('idComputed'); + oddId = person.idComputed; assert.strictEqual(oddId, 'john', 'computed get is correct'); // test direct access of id assert.strictEqual(person.id, 'john', 'new id should be correctly set.'); }); - test('ID mutation (complicated)', async function (assert) { - let idChange = 0; - let compChange = 0; - const OddPerson = Model.extend({ - name: DSattr('string'), - idComputed: computed('id', function () { - // we intentionally don't access the id here - return 'not-the-id:' + compChange++; - }), - idDidChange: observer('id', function () { - idChange++; - }), - }); - this.owner.register('model:odd-person', OddPerson); - - let person = store.createRecord('odd-person'); - assert.strictEqual(person.get('idComputed'), 'not-the-id:0'); - assert.strictEqual(idChange, 0, 'we have had no changes initially'); - - let personId = person.get('id'); - assert.strictEqual(personId, null, 'initial created model id should be null'); - assert.strictEqual(idChange, 0, 'we should still have no id changes'); - - // simulate an update from the store or RecordData that doesn't - // go through the internalModelFactory - person._internalModel.setId('john'); - assert.strictEqual(idChange, 1, 'we should have one change after updating id'); - let recordData = recordDataFor(person); - assert.strictEqual( - recordData.getResourceIdentifier().id, - 'john', - 'new id should be set on the identifier on record data.' - ); - assert.strictEqual(recordData.id, 'john', 'new id should be correctly set on the record data itself.'); - assert.strictEqual(person.get('id'), 'john', 'new id should be correctly set.'); - }); - test('an ID of 0 is allowed', async function (assert) { store.push({ data: { @@ -414,7 +379,7 @@ module('unit/model - Model', function (hooks) { // we can locate it in the identity map let record = store.peekRecord('person', 0); - assert.strictEqual(record.get('name'), 'Tom Dale', 'found record with id 0'); + assert.strictEqual(record.name, 'Tom Dale', 'found record with id 0'); }); }); @@ -624,12 +589,6 @@ module('unit/model - Model', function (hooks) { }, /Cannot set property isLoaded of \[object Object\] which has only a getter/); }); - class NativePostWithInternalModel extends Model { - @attr('string') - _internalModel; - @attr('string') - name; - } class NativePostWithCurrentState extends Model { @attr('string') currentState; @@ -637,7 +596,6 @@ module('unit/model - Model', function (hooks) { name; } const PROP_MAP = { - _internalModel: NativePostWithInternalModel, currentState: NativePostWithCurrentState, }; @@ -677,7 +635,7 @@ module('unit/model - Model', function (hooks) { }); } - ['_internalModel', 'currentState'].forEach(testReservedProperty); + ['currentState'].forEach(testReservedProperty); testInDebug('A subclass of Model throws an error when calling create() directly', async function (assert) { class NativePerson extends Model {} @@ -738,7 +696,7 @@ module('unit/model - Model', function (hooks) { assert.strictEqual(person.currentState.stateName, 'root.loaded.saved', 'model is in loaded state'); }); - test('internalModel is ready by `init`', async function (assert) { + test('record properties can be set during `init`', async function (assert) { let nameDidChange = 0; class OddNativePerson extends Model { @@ -765,10 +723,10 @@ module('unit/model - Model', function (hooks) { assert.strictEqual(nameDidChange, 0, 'observer should not trigger on create'); let person = store.createRecord('legacy-person'); assert.strictEqual(nameDidChange, 0, 'observer should not trigger on create'); - assert.strictEqual(person.get('name'), 'my-name-set-in-init'); + assert.strictEqual(person.name, 'my-name-set-in-init'); person = store.createRecord('native-person'); - assert.strictEqual(person.get('name'), 'my-name-set-in-init'); + assert.strictEqual(person.name, 'my-name-set-in-init'); }); test('accessing attributes during init should not throw an error', async function (assert) { @@ -777,7 +735,7 @@ module('unit/model - Model', function (hooks) { init() { this._super(...arguments); - assert.strictEqual(this.get('name'), 'bam!', 'We all good here'); + assert.strictEqual(this.name, 'bam!', 'We all good here'); }, }); this.owner.register('model:odd-person', Person); @@ -937,15 +895,12 @@ module('unit/model - Model', function (hooks) { let person = await store.findRecord('person', '1'); - assert.false(person.get('hasDirtyAttributes'), 'precond - person record should not be dirty'); + assert.false(person.hasDirtyAttributes, 'precond - person record should not be dirty'); person.set('name', 'Peter'); person.set('isDrugAddict', true); - assert.false( - person.get('hasDirtyAttributes'), - 'record does not become dirty after setting property to old value' - ); + assert.false(person.hasDirtyAttributes, 'record does not become dirty after setting property to old value'); }); test('resetting a property on a record cause it to become clean again', async function (assert) { @@ -962,15 +917,15 @@ module('unit/model - Model', function (hooks) { let person = await store.findRecord('person', '1'); - assert.false(person.get('hasDirtyAttributes'), 'precond - person record should not be dirty'); + assert.false(person.hasDirtyAttributes, 'precond - person record should not be dirty'); person.set('isDrugAddict', false); - assert.true(person.get('hasDirtyAttributes'), 'record becomes dirty after setting property to a new value'); + assert.true(person.hasDirtyAttributes, 'record becomes dirty after setting property to a new value'); person.set('isDrugAddict', true); - assert.false(person.get('hasDirtyAttributes'), 'record becomes clean after resetting property to the old value'); + assert.false(person.hasDirtyAttributes, 'record becomes clean after resetting property to the old value'); }); test('resetting a property to the current in-flight value causes it to become clean when the save completes', async function (assert) { @@ -993,17 +948,17 @@ module('unit/model - Model', function (hooks) { let saving = person.save(); - assert.strictEqual(person.get('name'), 'Thomas'); + assert.strictEqual(person.name, 'Thomas'); person.set('name', 'Tomathy'); - assert.strictEqual(person.get('name'), 'Tomathy'); + assert.strictEqual(person.name, 'Tomathy'); person.set('name', 'Thomas'); - assert.strictEqual(person.get('name'), 'Thomas'); + assert.strictEqual(person.name, 'Thomas'); await saving; - assert.false(person.get('hasDirtyAttributes'), 'The person is now clean'); + assert.false(person.hasDirtyAttributes, 'The person is now clean'); }); test('a record becomes clean again only if all changed properties are reset', async function (assert) { @@ -1020,21 +975,15 @@ module('unit/model - Model', function (hooks) { let person = await store.findRecord('person', 1); - assert.false(person.get('hasDirtyAttributes'), 'precond - person record should not be dirty'); + assert.false(person.hasDirtyAttributes, 'precond - person record should not be dirty'); person.set('isDrugAddict', false); - assert.true(person.get('hasDirtyAttributes'), 'record becomes dirty after setting one property to a new value'); + assert.true(person.hasDirtyAttributes, 'record becomes dirty after setting one property to a new value'); person.set('name', 'Mark'); - assert.true(person.get('hasDirtyAttributes'), 'record stays dirty after setting another property to a new value'); + assert.true(person.hasDirtyAttributes, 'record stays dirty after setting another property to a new value'); person.set('isDrugAddict', true); - assert.true( - person.get('hasDirtyAttributes'), - 'record stays dirty after resetting only one property to the old value' - ); + assert.true(person.hasDirtyAttributes, 'record stays dirty after resetting only one property to the old value'); person.set('name', 'Peter'); - assert.false( - person.get('hasDirtyAttributes'), - 'record becomes clean after resetting both properties to the old value' - ); + assert.false(person.hasDirtyAttributes, 'record becomes clean after resetting both properties to the old value'); }); test('an invalid record becomes clean again if changed property is reset', async function (assert) { @@ -1063,9 +1012,9 @@ module('unit/model - Model', function (hooks) { let person = store.peekRecord('person', 1); - assert.false(person.get('hasDirtyAttributes'), 'precond - person record should not be dirty'); + assert.false(person.hasDirtyAttributes, 'precond - person record should not be dirty'); person.set('name', 'Wolf'); - assert.true(person.get('hasDirtyAttributes'), 'record becomes dirty after setting one property to a new value'); + assert.true(person.hasDirtyAttributes, 'record becomes dirty after setting one property to a new value'); await person .save() @@ -1073,16 +1022,13 @@ module('unit/model - Model', function (hooks) { assert.ok(false, 'We should reject the save'); }) .catch(() => { - assert.false(person.get('isValid'), 'record is not valid'); - assert.true(person.get('hasDirtyAttributes'), 'record still has dirty attributes'); + assert.false(person.isValid, 'record is not valid'); + assert.true(person.hasDirtyAttributes, 'record still has dirty attributes'); person.set('name', 'Peter'); - assert.true(person.get('isValid'), 'record is valid after resetting attribute to old value'); - assert.false( - person.get('hasDirtyAttributes'), - 'record becomes clean after resetting property to the old value' - ); + assert.true(person.isValid, 'record is valid after resetting attribute to old value'); + assert.false(person.hasDirtyAttributes, 'record becomes clean after resetting property to the old value'); }); }); @@ -1112,10 +1058,10 @@ module('unit/model - Model', function (hooks) { let person = store.peekRecord('person', 1); - assert.false(person.get('hasDirtyAttributes'), 'precond - person record should not be dirty'); + assert.false(person.hasDirtyAttributes, 'precond - person record should not be dirty'); person.set('name', 'Wolf'); person.set('isDrugAddict', false); - assert.true(person.get('hasDirtyAttributes'), 'record becomes dirty after setting one property to a new value'); + assert.true(person.hasDirtyAttributes, 'record becomes dirty after setting one property to a new value'); await person .save() @@ -1123,13 +1069,13 @@ module('unit/model - Model', function (hooks) { assert.ok(false, 'save should have rejected'); }) .catch(() => { - assert.false(person.get('isValid'), 'record is not valid'); - assert.true(person.get('hasDirtyAttributes'), 'record still has dirty attributes'); + assert.false(person.isValid, 'record is not valid'); + assert.true(person.hasDirtyAttributes, 'record still has dirty attributes'); person.set('name', 'Peter'); - assert.true(person.get('isValid'), 'record is valid after resetting invalid attribute to old value'); - assert.true(person.get('hasDirtyAttributes'), 'record still has dirty attributes'); + assert.true(person.isValid, 'record is valid after resetting invalid attribute to old value'); + assert.true(person.hasDirtyAttributes, 'record still has dirty attributes'); }); }); diff --git a/packages/-ember-data/tests/unit/model/errors-test.js b/packages/-ember-data/tests/unit/model/errors-test.js index 7363080691a..24688eca4b4 100644 --- a/packages/-ember-data/tests/unit/model/errors-test.js +++ b/packages/-ember-data/tests/unit/model/errors-test.js @@ -44,22 +44,22 @@ module('unit/model/errors', function (hooks) { errors.add('firstName', 'error'); errors.trigger = assert.unexpectedSend; assert.ok(errors.has('firstName'), 'it has firstName errors'); - assert.strictEqual(errors.get('length'), 1, 'it has 1 error'); + assert.strictEqual(errors.length, 1, 'it has 1 error'); errors.add('firstName', ['error1', 'error2']); - assert.strictEqual(errors.get('length'), 3, 'it has 3 errors'); - assert.ok(!errors.get('isEmpty'), 'it is not empty'); + assert.strictEqual(errors.length, 3, 'it has 3 errors'); + assert.ok(!errors.isEmpty, 'it is not empty'); errors.add('lastName', 'error'); errors.add('lastName', 'error'); - assert.strictEqual(errors.get('length'), 4, 'it has 4 errors'); + assert.strictEqual(errors.length, 4, 'it has 4 errors'); }); testInDebug('get error', function (assert) { - assert.ok(errors.get('firstObject') === undefined, 'returns undefined'); + assert.ok(errors.firstObject === undefined, 'returns undefined'); errors.trigger = assert.becameInvalid; errors.add('firstName', 'error'); errors.trigger = assert.unexpectedSend; assert.ok(errors.get('firstName').length === 1, 'returns errors'); - assert.deepEqual(errors.get('firstObject'), { attribute: 'firstName', message: 'error' }); + assert.deepEqual(errors.firstObject, { attribute: 'firstName', message: 'error' }); errors.add('firstName', 'error2'); assert.ok(errors.get('firstName').length === 2, 'returns errors'); errors.add('lastName', 'error3'); @@ -72,7 +72,7 @@ module('unit/model/errors', function (hooks) { { attribute: 'firstName', message: 'error' }, { attribute: 'firstName', message: 'error2' }, ]); - assert.deepEqual(errors.get('messages'), ['error', 'error2', 'error3']); + assert.deepEqual(errors.messages, ['error', 'error2', 'error3']); }); testInDebug('remove error', function (assert) { @@ -82,8 +82,8 @@ module('unit/model/errors', function (hooks) { errors.remove('firstName'); errors.trigger = assert.unexpectedSend; assert.ok(!errors.has('firstName'), 'it has no firstName errors'); - assert.strictEqual(errors.get('length'), 0, 'it has 0 error'); - assert.ok(errors.get('isEmpty'), 'it is empty'); + assert.strictEqual(errors.length, 0, 'it has 0 error'); + assert.ok(errors.isEmpty, 'it is empty'); errors.remove('firstName'); }); @@ -92,23 +92,23 @@ module('unit/model/errors', function (hooks) { errors.add('firstName', 'error'); errors.add('lastName', 'error'); errors.trigger = assert.unexpectedSend; - assert.strictEqual(errors.get('length'), 2, 'it has 2 error'); + assert.strictEqual(errors.length, 2, 'it has 2 error'); errors.remove('firstName'); - assert.strictEqual(errors.get('length'), 1, 'it has 1 error'); + assert.strictEqual(errors.length, 1, 'it has 1 error'); errors.trigger = assert.becameValid; errors.remove('lastName'); - assert.ok(errors.get('isEmpty'), 'it is empty'); + assert.ok(errors.isEmpty, 'it is empty'); }); testInDebug('clear errors', function (assert) { errors.trigger = assert.becameInvalid; errors.add('firstName', ['error', 'error1']); - assert.strictEqual(errors.get('length'), 2, 'it has 2 errors'); + assert.strictEqual(errors.length, 2, 'it has 2 errors'); errors.trigger = assert.becameValid; errors.clear(); errors.trigger = assert.unexpectedSend; assert.ok(!errors.has('firstName'), 'it has no firstName errors'); - assert.strictEqual(errors.get('length'), 0, 'it has 0 error'); + assert.strictEqual(errors.length, 0, 'it has 0 error'); errors.clear(); }); }); diff --git a/packages/-ember-data/tests/unit/model/merge-test.js b/packages/-ember-data/tests/unit/model/merge-test.js index 20146bef561..5ac1ded0e0c 100644 --- a/packages/-ember-data/tests/unit/model/merge-test.js +++ b/packages/-ember-data/tests/unit/model/merge-test.js @@ -42,13 +42,13 @@ module('unit/model/merge - Merging', function (hooks) { return run(() => { let save = person.save(); - assert.strictEqual(person.get('name'), 'Tom Dale'); + assert.strictEqual(person.name, 'Tom Dale'); person.set('name', 'Thomas Dale'); return save.then((person) => { - assert.true(person.get('hasDirtyAttributes'), 'The person is still dirty'); - assert.strictEqual(person.get('name'), 'Thomas Dale', 'The changes made still apply'); + assert.true(person.hasDirtyAttributes, 'The person is still dirty'); + assert.strictEqual(person.name, 'Thomas Dale', 'The changes made still apply'); }); }); }); @@ -83,15 +83,15 @@ module('unit/model/merge - Merging', function (hooks) { return run(() => { let promise = person.save(); - assert.strictEqual(person.get('name'), 'Thomas Dale'); + assert.strictEqual(person.name, 'Thomas Dale'); person.set('name', 'Tomasz Dale'); - assert.strictEqual(person.get('name'), 'Tomasz Dale', 'the local changes applied on top'); + assert.strictEqual(person.name, 'Tomasz Dale', 'the local changes applied on top'); return promise.then((person) => { - assert.true(person.get('hasDirtyAttributes'), 'The person is still dirty'); - assert.strictEqual(person.get('name'), 'Tomasz Dale', 'The local changes apply'); + assert.true(person.hasDirtyAttributes, 'The person is still dirty'); + assert.strictEqual(person.name, 'Tomasz Dale', 'The local changes apply'); }); }); }); @@ -134,7 +134,7 @@ module('unit/model/merge - Merging', function (hooks) { return run(() => { var promise = person.save(); - assert.strictEqual(person.get('name'), 'Thomas Dale'); + assert.strictEqual(person.name, 'Thomas Dale'); person.set('name', 'Tomasz Dale'); @@ -149,17 +149,13 @@ module('unit/model/merge - Merging', function (hooks) { }, }); - assert.strictEqual(person.get('name'), 'Tomasz Dale', 'the local changes applied on top'); - assert.strictEqual(person.get('city'), 'PDX', 'the pushed change is available'); + assert.strictEqual(person.name, 'Tomasz Dale', 'the local changes applied on top'); + assert.strictEqual(person.city, 'PDX', 'the pushed change is available'); return promise.then((person) => { - assert.true(person.get('hasDirtyAttributes'), 'The person is still dirty'); - assert.strictEqual(person.get('name'), 'Tomasz Dale', 'The local changes apply'); - assert.strictEqual( - person.get('city'), - 'Portland', - 'The updates from the server apply on top of the previous pushes' - ); + assert.true(person.hasDirtyAttributes, 'The person is still dirty'); + assert.strictEqual(person.name, 'Tomasz Dale', 'The local changes apply'); + assert.strictEqual(person.city, 'Portland', 'The updates from the server apply on top of the previous pushes'); }); }); }); @@ -181,9 +177,9 @@ module('unit/model/merge - Merging', function (hooks) { person.set('name', 'Tomasz Dale'); }); - assert.true(person.get('hasDirtyAttributes'), 'the person is currently dirty'); - assert.strictEqual(person.get('name'), 'Tomasz Dale', 'the update was effective'); - assert.strictEqual(person.get('city'), 'San Francisco', 'the original data applies'); + assert.true(person.hasDirtyAttributes, 'the person is currently dirty'); + assert.strictEqual(person.name, 'Tomasz Dale', 'the update was effective'); + assert.strictEqual(person.city, 'San Francisco', 'the original data applies'); run(() => { this.store.push({ @@ -198,9 +194,9 @@ module('unit/model/merge - Merging', function (hooks) { }); }); - assert.true(person.get('hasDirtyAttributes'), 'the local changes are reapplied'); - assert.strictEqual(person.get('name'), 'Tomasz Dale', 'the local changes are reapplied'); - assert.strictEqual(person.get('city'), 'Portland', 'if there are no local changes, the new data applied'); + assert.true(person.hasDirtyAttributes, 'the local changes are reapplied'); + assert.strictEqual(person.name, 'Tomasz Dale', 'the local changes are reapplied'); + assert.strictEqual(person.city, 'Portland', 'if there are no local changes, the new data applied'); }); test('When a record is invalid, pushes are overridden by local changes', async function (assert) { @@ -235,10 +231,10 @@ module('unit/model/merge - Merging', function (hooks) { } catch (e) { assert.ok(true, 'We rejected the save'); } - assert.false(person.get('isValid'), 'the person is currently invalid'); - assert.true(person.get('hasDirtyAttributes'), 'the person is currently dirty'); - assert.strictEqual(person.get('name'), 'Brondan McLoughlin', 'the update was effective'); - assert.strictEqual(person.get('city'), 'Boston', 'the original data applies'); + assert.false(person.isValid, 'the person is currently invalid'); + assert.true(person.hasDirtyAttributes, 'the person is currently dirty'); + assert.strictEqual(person.name, 'Brondan McLoughlin', 'the update was effective'); + assert.strictEqual(person.city, 'Boston', 'the original data applies'); run(() => { this.store.push({ @@ -253,10 +249,10 @@ module('unit/model/merge - Merging', function (hooks) { }); }); - assert.true(person.get('hasDirtyAttributes'), 'the local changes are reapplied'); - assert.false(person.get('isValid'), 'record is still invalid'); - assert.strictEqual(person.get('name'), 'Brondan McLoughlin', 'the local changes are reapplied'); - assert.strictEqual(person.get('city'), 'Prague', 'if there are no local changes, the new data applied'); + assert.true(person.hasDirtyAttributes, 'the local changes are reapplied'); + assert.false(person.isValid, 'record is still invalid'); + assert.strictEqual(person.name, 'Brondan McLoughlin', 'the local changes are reapplied'); + assert.strictEqual(person.city, 'Prague', 'if there are no local changes, the new data applied'); }); test('A record with no changes can still be saved', function (assert) { @@ -284,7 +280,7 @@ module('unit/model/merge - Merging', function (hooks) { return run(() => { return person.save().then((foo) => { - assert.strictEqual(person.get('name'), 'Thomas Dale', 'the updates occurred'); + assert.strictEqual(person.name, 'Thomas Dale', 'the updates occurred'); }); }); }); @@ -319,9 +315,9 @@ module('unit/model/merge - Merging', function (hooks) { return run(() => { return person.reload().then(() => { - assert.true(person.get('hasDirtyAttributes'), 'the person is dirty'); - assert.strictEqual(person.get('name'), 'Tomasz Dale', 'the local changes remain'); - assert.strictEqual(person.get('city'), 'Portland', 'the new changes apply'); + assert.true(person.hasDirtyAttributes, 'the person is dirty'); + assert.strictEqual(person.name, 'Tomasz Dale', 'the local changes remain'); + assert.strictEqual(person.city, 'Portland', 'the new changes apply'); }); }); }); diff --git a/packages/-ember-data/tests/unit/model/relationships/belongs-to-test.js b/packages/-ember-data/tests/unit/model/relationships/belongs-to-test.js index 1098eb46c29..8b73d401a54 100644 --- a/packages/-ember-data/tests/unit/model/relationships/belongs-to-test.js +++ b/packages/-ember-data/tests/unit/model/relationships/belongs-to-test.js @@ -20,7 +20,7 @@ module('unit/model/relationships - belongsTo', function (hooks) { this.owner.register('serializer:application', class extends JSONAPISerializer {}); }); - test('belongsTo lazily loads relationships as needed', function (assert) { + test('belongsTo lazily loads relationships as needed', async function (assert) { assert.expect(5); const Tag = Model.extend({ @@ -41,61 +41,55 @@ module('unit/model/relationships - belongsTo', function (hooks) { adapter.shouldBackgroundReloadRecord = () => false; - run(() => { - store.push({ - data: [ - { - type: 'tag', - id: '5', - attributes: { - name: 'friendly', - }, + store.push({ + data: [ + { + type: 'tag', + id: '5', + attributes: { + name: 'friendly', }, - { - type: 'tag', - id: '2', - attributes: { - name: 'smarmy', - }, + }, + { + type: 'tag', + id: '2', + attributes: { + name: 'smarmy', }, - { - type: 'tag', - id: '12', - attributes: { - name: 'oohlala', - }, + }, + { + type: 'tag', + id: '12', + attributes: { + name: 'oohlala', }, - { - type: 'person', - id: '1', - attributes: { - name: 'Tom Dale', - }, - relationships: { - tag: { - data: { type: 'tag', id: '5' }, - }, + }, + { + type: 'person', + id: '1', + attributes: { + name: 'Tom Dale', + }, + relationships: { + tag: { + data: { type: 'tag', id: '5' }, }, }, - ], - }); + }, + ], }); + const person = await store.findRecord('person', '1'); + assert.strictEqual(get(person, 'name'), 'Tom Dale', 'precond - retrieves person record from store'); - return run(() => { - return store.findRecord('person', 1).then((person) => { - assert.strictEqual(get(person, 'name'), 'Tom Dale', 'precond - retrieves person record from store'); - - assert.true(get(person, 'tag') instanceof Tag, 'the tag property should return a tag'); - assert.strictEqual(get(person, 'tag.name'), 'friendly', 'the tag shuld have name'); + assert.true(person.tag instanceof Tag, 'the tag property should return a tag'); + assert.strictEqual(person.tag.name, 'friendly', 'the tag shuld have name'); - assert.strictEqual(get(person, 'tag'), get(person, 'tag'), 'the returned object is always the same'); - assert.asyncEqual( - get(person, 'tag'), - store.findRecord('tag', 5), - 'relationship object is the same as object retrieved directly' - ); - }); - }); + assert.strictEqual(person.tag, person.tag, 'the returned object is always the same'); + assert.strictEqual( + person.tag, + await store.findRecord('tag', 5), + 'relationship object is the same as object retrieved directly' + ); }); test('belongsTo does not notify when it is initially reified', function (assert) { @@ -157,7 +151,7 @@ module('unit/model/relationships - belongsTo', function (hooks) { person.addObserver('tag', tagDidChange); - assert.strictEqual(person.get('tag.name'), 'whatever', 'relationship is correct'); + assert.strictEqual(person.tag.name, 'whatever', 'relationship is correct'); // This needs to be removed so it is not triggered when test context is torn down person.removeObserver('tag', tagDidChange); @@ -525,7 +519,7 @@ module('unit/model/relationships - belongsTo', function (hooks) { return run(() => { return store.findRecord('person', '1').then((person) => { - assert.strictEqual(person.get('tag'), null, 'undefined values should return null relationships'); + assert.strictEqual(person.tag, null, 'undefined values should return null relationships'); }); }); }); @@ -640,10 +634,10 @@ module('unit/model/relationships - belongsTo', function (hooks) { }); }); - run(() => store.peekRecord('person', 1).get('occupation')); + run(() => store.peekRecord('person', 1).occupation); }); - test('belongsTo supports relationships to models with id 0', function (assert) { + test('belongsTo supports relationships to models with id 0', async function (assert) { assert.expect(5); const Tag = Model.extend({ @@ -664,61 +658,56 @@ module('unit/model/relationships - belongsTo', function (hooks) { adapter.shouldBackgroundReloadRecord = () => false; - run(() => { - store.push({ - data: [ - { - type: 'tag', - id: '0', - attributes: { - name: 'friendly', - }, + store.push({ + data: [ + { + type: 'tag', + id: '0', + attributes: { + name: 'friendly', }, - { - type: 'tag', - id: '2', - attributes: { - name: 'smarmy', - }, + }, + { + type: 'tag', + id: '2', + attributes: { + name: 'smarmy', }, - { - type: 'tag', - id: '12', - attributes: { - name: 'oohlala', - }, + }, + { + type: 'tag', + id: '12', + attributes: { + name: 'oohlala', }, - { - type: 'person', - id: '1', - attributes: { - name: 'Tom Dale', - }, - relationships: { - tag: { - data: { type: 'tag', id: '0' }, - }, + }, + { + type: 'person', + id: '1', + attributes: { + name: 'Tom Dale', + }, + relationships: { + tag: { + data: { type: 'tag', id: '0' }, }, }, - ], - }); + }, + ], }); - return run(() => { - return store.findRecord('person', 1).then((person) => { - assert.strictEqual(get(person, 'name'), 'Tom Dale', 'precond - retrieves person record from store'); + const person = await store.findRecord('person', '1'); + assert.strictEqual(person.name, 'Tom Dale', 'precond - retrieves person record from store'); - assert.true(get(person, 'tag') instanceof Tag, 'the tag property should return a tag'); - assert.strictEqual(get(person, 'tag.name'), 'friendly', 'the tag should have name'); + assert.true(person.tag instanceof Tag, 'the tag property should return a tag'); + assert.strictEqual(person.tag.name, 'friendly', 'the tag should have name'); - assert.strictEqual(get(person, 'tag'), get(person, 'tag'), 'the returned object is always the same'); - assert.asyncEqual( - get(person, 'tag'), - store.findRecord('tag', 0), - 'relationship object is the same as object retrieved directly' - ); - }); - }); + assert.strictEqual(person.tag, person.tag, 'the returned object is always the same'); + assert.strictEqual( + person.tag, + await store.findRecord('tag', 0), + 'relationship object is the same as object retrieved directly' + ); }); testInDebug('belongsTo gives a warning when provided with a serialize option', function (assert) { @@ -859,6 +848,6 @@ module('unit/model/relationships - belongsTo', function (hooks) { let person = store.createRecord('person'); - assert.ok(person.get('tag') instanceof DS.PromiseObject, 'tag should be an async relationship'); + assert.ok(person.tag instanceof DS.PromiseObject, 'tag should be an async relationship'); }); }); diff --git a/packages/-ember-data/tests/unit/model/relationships/has-many-test.js b/packages/-ember-data/tests/unit/model/relationships/has-many-test.js index 0b78b88cee5..0ee6e9fb916 100644 --- a/packages/-ember-data/tests/unit/model/relationships/has-many-test.js +++ b/packages/-ember-data/tests/unit/model/relationships/has-many-test.js @@ -302,7 +302,7 @@ module('unit/model/relationships - hasMany', function (hooks) { assert.ok(false, 'observer is not called'); }); - assert.deepEqual(tag.get('people').mapBy('name'), ['David J. Hamilton'], 'relationship is correct'); + assert.deepEqual(tag.people.mapBy('name'), ['David J. Hamilton'], 'relationship is correct'); }); }); @@ -347,7 +347,7 @@ module('unit/model/relationships - hasMany', function (hooks) { return run(() => { let tag = store.peekRecord('tag', 1); - assert.strictEqual(tag.get('people.length'), 0, 'relationship is correct'); + assert.strictEqual(tag.people.length, 0, 'relationship is correct'); }); }); @@ -432,8 +432,8 @@ module('unit/model/relationships - hasMany', function (hooks) { let tag = store.peekRecord('tag', 1); let person = store.peekRecord('person', 1); - assert.strictEqual(person.get('tag'), null, 'relationship is empty'); - assert.strictEqual(tag.get('people.length'), 0, 'relationship is correct'); + assert.strictEqual(person.tag, null, 'relationship is empty'); + assert.strictEqual(tag.people.length, 0, 'relationship is correct'); }); }); @@ -509,7 +509,7 @@ module('unit/model/relationships - hasMany', function (hooks) { run(() => { let tag = store.peekRecord('tag', 1); - assert.strictEqual(tag.get('people.length'), 1, 'relationship does not contain duplicates'); + assert.strictEqual(tag.people.length, 1, 'relationship does not contain duplicates'); }); }); @@ -637,11 +637,11 @@ module('unit/model/relationships - hasMany', function (hooks) { run(() => { let tag = store.peekRecord('tag', 1); - assert.strictEqual(tag.get('people.length'), 2, 'relationship does contain all data'); + assert.strictEqual(tag.people.length, 2, 'relationship does contain all data'); let person1 = store.peekRecord('person', 1); - assert.strictEqual(person1.get('tags.length'), 2, 'relationship does contain all data'); + assert.strictEqual(person1.tags.length, 2, 'relationship does contain all data'); let person2 = store.peekRecord('person', 2); - assert.strictEqual(person2.get('tags.length'), 2, 'relationship does contain all data'); + assert.strictEqual(person2.tags.length, 2, 'relationship does contain all data'); }); }); @@ -710,7 +710,7 @@ module('unit/model/relationships - hasMany', function (hooks) { let person = store.peekRecord('person', 1); let tag = store.peekRecord('tag', 1); - assert.strictEqual(person.get('tag'), tag, 'relationship is not empty'); + assert.strictEqual(person.tag, tag, 'relationship is not empty'); }); run(() => { @@ -735,8 +735,8 @@ module('unit/model/relationships - hasMany', function (hooks) { let person = store.peekRecord('person', 1); let tag = store.peekRecord('tag', 1); - assert.strictEqual(person.get('tag'), null, 'relationship is now empty'); - assert.strictEqual(tag.get('people.length'), 0, 'relationship is correct'); + assert.strictEqual(person.tag, null, 'relationship is now empty'); + assert.strictEqual(tag.people.length, 0, 'relationship is correct'); }); }); @@ -778,11 +778,7 @@ module('unit/model/relationships - hasMany', function (hooks) { }); let eddy = store.peekRecord('person', 1); - assert.deepEqual( - eddy.get('trueFriends').mapBy('name'), - ['Edward II'], - 'hasMany supports reflexive self-relationships' - ); + assert.deepEqual(eddy.trueFriends.mapBy('name'), ['Edward II'], 'hasMany supports reflexive self-relationships'); }); test('hasMany lazily loads async relationships', function (assert) { @@ -897,7 +893,7 @@ module('unit/model/relationships - hasMany', function (hooks) { return hash({ wycats, - tags: wycats.get('tags'), + tags: wycats.tags, }); }) .then((records) => { @@ -1132,7 +1128,7 @@ module('unit/model/relationships - hasMany', function (hooks) { .then((person) => { assert.strictEqual(get(person, 'name'), 'Tom Dale', 'The person is now populated'); - return run(() => person.get('tags')); + return run(() => person.tags); }) .then((tags) => { assert.strictEqual(get(tags, 'length'), 2, 'the tags object still exists'); @@ -1268,7 +1264,7 @@ module('unit/model/relationships - hasMany', function (hooks) { }); const person = store.peekRecord('person', '1'); - const pets = run(() => person.get('pets')); + const pets = run(() => person.pets); const shen = pets.objectAt(0); const rambo = store.peekRecord('pet', '2'); @@ -1367,7 +1363,7 @@ module('unit/model/relationships - hasMany', function (hooks) { }); const person = store.peekRecord('person', '1'); - const pets = run(() => person.get('pets')); + const pets = run(() => person.pets); const shen = pets.objectAt(0); const rebel = store.peekRecord('pet', '3'); @@ -1485,7 +1481,7 @@ module('unit/model/relationships - hasMany', function (hooks) { }); const person = store.peekRecord('person', '1'); - const pets = run(() => person.get('pets')); + const pets = run(() => person.pets); const shen = pets.objectAt(0); const rebel = store.peekRecord('pet', '3'); @@ -1604,7 +1600,7 @@ module('unit/model/relationships - hasMany', function (hooks) { return run(() => { const person = store.peekRecord('person', '1'); - const petsProxy = run(() => person.get('pets')); + const petsProxy = run(() => person.pets); return petsProxy.then((pets) => { const shen = pets.objectAt(0); @@ -1697,7 +1693,7 @@ module('unit/model/relationships - hasMany', function (hooks) { }); const person = store.peekRecord('person', '1'); - let dog = run(() => person.get('dog')); + let dog = run(() => person.dog); const shen = store.peekRecord('dog', '1'); const rambo = store.peekRecord('dog', '2'); @@ -1708,12 +1704,12 @@ module('unit/model/relationships - hasMany', function (hooks) { person.set('dog', rambo); }); - dog = person.get('dog'); + dog = person.dog; assert.strictEqual(dog, rambo, 'precond2 - relationship was updated'); return run(() => { return shen.destroyRecord({}).then(() => { - dog = person.get('dog'); + dog = person.dog; assert.strictEqual(dog, rambo, 'The currentState of the belongsTo was preserved after the delete'); }); }); @@ -1780,18 +1776,18 @@ module('unit/model/relationships - hasMany', function (hooks) { const shen = store.peekRecord('dog', '1'); const rambo = store.peekRecord('dog', '2'); - return person.get('dog').then((dog) => { + return person.dog.then((dog) => { assert.strictEqual(dog, shen, 'precond - the belongsTo points to the correct dog'); assert.strictEqual(get(dog, 'name'), 'Shenanigans', 'precond - relationships work'); person.set('dog', rambo); - dog = person.get('dog.content'); + dog = person.dog.content; assert.strictEqual(dog, rambo, 'precond2 - relationship was updated'); return shen.destroyRecord({}).then(() => { - dog = person.get('dog.content'); + dog = person.dog.content; assert.strictEqual(dog, rambo, 'The currentState of the belongsTo was preserved after the delete'); }); }); @@ -1855,7 +1851,7 @@ module('unit/model/relationships - hasMany', function (hooks) { }); const person = store.peekRecord('person', '1'); - let dog = run(() => person.get('dog')); + let dog = run(() => person.dog); const shen = store.peekRecord('dog', '1'); const rambo = store.peekRecord('dog', '2'); @@ -1866,12 +1862,12 @@ module('unit/model/relationships - hasMany', function (hooks) { person.set('dog', rambo); }); - dog = person.get('dog'); + dog = person.dog; assert.strictEqual(dog, rambo, 'precond2 - relationship was updated'); return run(() => { return rambo.destroyRecord({}).then(() => { - dog = person.get('dog'); + dog = person.dog; assert.strictEqual(dog, null, 'The current state of the belongsTo was clearer'); }); }); @@ -1925,18 +1921,18 @@ module('unit/model/relationships - hasMany', function (hooks) { }); let person = store.peekRecord('person', 1); - let cars = person.get('cars'); + let cars = person.cars; - assert.strictEqual(cars.get('length'), 2); + assert.strictEqual(cars.length, 2); run(() => { - cars.get('firstObject').unloadRecord(); - assert.strictEqual(cars.get('length'), 1); // unload now.. - assert.strictEqual(person.get('cars.length'), 1); // unload now.. + cars.firstObject.unloadRecord(); + assert.strictEqual(cars.length, 1); // unload now.. + assert.strictEqual(person.cars.length, 1); // unload now.. }); - assert.strictEqual(cars.get('length'), 1); // unload now.. - assert.strictEqual(person.get('cars.length'), 1); // unload now.. + assert.strictEqual(cars.length, 1); // unload now.. + assert.strictEqual(person.cars.length, 1); // unload now.. }); /* @@ -2032,7 +2028,7 @@ module('unit/model/relationships - hasMany', function (hooks) { }); const person = store.peekRecord('person', '1'); - const pets = run(() => person.get('pets')); + const pets = run(() => person.pets); const shen = store.peekRecord('pet', '1'); const rebel = store.peekRecord('pet', '3'); @@ -2134,13 +2130,13 @@ module('unit/model/relationships - hasMany', function (hooks) { run(() => { tom = store.peekRecord('person', '1'); sylvain = store.peekRecord('person', '2'); - // Test that since sylvain.get('tags') instanceof ManyArray, - // addInternalModels on Relationship iterates correctly. - tom.get('tags').setObjects(sylvain.get('tags')); + // Test that since sylvain.tags instanceof ManyArray, + // adding records on Relationship iterates correctly. + tom.tags.setObjects(sylvain.tags); }); - assert.strictEqual(tom.get('tags.length'), 1); - assert.strictEqual(tom.get('tags.firstObject'), store.peekRecord('tag', 2)); + assert.strictEqual(tom.tags.length, 1); + assert.strictEqual(tom.tags.firstObject, store.peekRecord('tag', 2)); }); test('Replacing `has-many` with non-array will throw assertion', function (assert) { @@ -2199,7 +2195,7 @@ module('unit/model/relationships - hasMany', function (hooks) { run(() => { tom = store.peekRecord('person', '1'); assert.expectAssertion(() => { - tom.get('tags').setObjects(store.peekRecord('tag', '2')); + tom.tags.setObjects(store.peekRecord('tag', '2')); }, /The third argument to replace needs to be an array./); }); }); @@ -2322,7 +2318,7 @@ module('unit/model/relationships - hasMany', function (hooks) { let store = this.owner.lookup('service:store'); let tag = store.createRecord('tag'); - assert.ok(tag.get('people') instanceof PromiseManyArray, 'people should be an async relationship'); + assert.ok(tag.people instanceof PromiseManyArray, 'people should be an async relationship'); }); test('hasMany is stable', function (assert) { @@ -2341,20 +2337,20 @@ module('unit/model/relationships - hasMany', function (hooks) { let store = this.owner.lookup('service:store'); let tag = store.createRecord('tag'); - let people = tag.get('people'); - let peopleCached = tag.get('people'); + let people = tag.people; + let peopleCached = tag.people; assert.strictEqual(people, peopleCached); tag.notifyPropertyChange('people'); - let notifiedPeople = tag.get('people'); + let notifiedPeople = tag.people; assert.strictEqual(people, notifiedPeople); return EmberPromise.all([people]); }); - test('hasMany proxy is destroyed', function (assert) { + test('hasMany proxy is destroyed', async function (assert) { const Tag = Model.extend({ name: attr('string'), people: hasMany('person'), @@ -2370,22 +2366,17 @@ module('unit/model/relationships - hasMany', function (hooks) { let store = this.owner.lookup('service:store'); let tag = store.createRecord('tag'); - let peopleProxy = tag.get('people'); + let peopleProxy = tag.people; + let people = await peopleProxy; - return peopleProxy.then((people) => { - run(() => { - tag.unloadRecord(); - // TODO Check all unloading behavior - assert.false(people.isDestroying, 'people is NOT destroying sync after unloadRecord'); - assert.false(people.isDestroyed, 'people is NOT destroyed sync after unloadRecord'); + tag.unloadRecord(); + assert.true(people.isDestroying, 'people is destroying sync after unloadRecord'); + assert.true(peopleProxy.isDestroying, 'peopleProxy is destroying after the run post unloadRecord'); + assert.true(peopleProxy.isDestroyed, 'peopleProxy is destroyed after the run post unloadRecord'); - assert.false(peopleProxy.isDestroying, 'peopleProxy is NOT destroying sync after unloadRecord'); - assert.false(peopleProxy.isDestroyed, 'peopleProxy is NOT destroyed sync after unloadRecord'); - }); + await settled(); - assert.true(peopleProxy.isDestroying, 'peopleProxy is destroying after the run post unloadRecord'); - assert.true(peopleProxy.isDestroyed, 'peopleProxy is destroyed after the run post unloadRecord'); - }); + assert.true(people.isDestroyed, 'people is destroyed after unloadRecord'); }); test('findHasMany - can push the same record in twice and fetch the link', async function (assert) { @@ -2735,7 +2726,7 @@ module('unit/model/relationships - hasMany', function (hooks) { assert.strictEqual(get(tag, 'name'), 'second', 'The tag is now loaded'); return run(() => - person.get('tags').then((tags) => { + person.tags.then((tags) => { assert.strictEqual(get(tags, 'length'), 3, 'the tags are all loaded'); }) ); 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 aa191f8049a..4efd7aba314 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 @@ -55,16 +55,7 @@ module('unit/model/relationships - RecordArray', function (hooks) { let tag = tags.objectAt(0); assert.strictEqual(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) - ) - ); + set(tags, 'content', A(records.map(recordIdentifierFor).slice(1, 3))); tag = tags.objectAt(0); assert.strictEqual(get(tag, 'name'), 'smarmy', 'the lookup was updated'); @@ -102,10 +93,10 @@ module('unit/model/relationships - RecordArray', function (hooks) { }); let person = await store.findRecord('person', 1); - person.get('tags').createRecord({ name: 'cool' }); + person.tags.createRecord({ name: 'cool' }); assert.strictEqual(get(person, 'name'), 'Tom Dale', 'precond - retrieves person record from store'); assert.strictEqual(get(person, 'tags.length'), 1, 'tag is added to the parent record'); - assert.strictEqual(get(person, 'tags').objectAt(0).get('name'), 'cool', 'tag values are passed along'); + assert.strictEqual(get(person, 'tags').objectAt(0).name, 'cool', 'tag values are passed along'); }); }); diff --git a/packages/-ember-data/tests/unit/model/rollback-attributes-test.js b/packages/-ember-data/tests/unit/model/rollback-attributes-test.js index 0ceaf1e48a5..b68ea1e617d 100644 --- a/packages/-ember-data/tests/unit/model/rollback-attributes-test.js +++ b/packages/-ember-data/tests/unit/model/rollback-attributes-test.js @@ -56,12 +56,12 @@ module('unit/model/rollbackAttributes - model.rollbackAttributes()', function (h return person; }); - assert.strictEqual(person.get('firstName'), 'Thomas', 'PreCond: we mutated firstName'); + assert.strictEqual(person.firstName, 'Thomas', 'PreCond: we mutated firstName'); run(() => person.rollbackAttributes()); - assert.strictEqual(person.get('firstName'), 'Tom', 'We rolled back firstName'); - assert.false(person.get('hasDirtyAttributes'), 'We expect the record to be clean'); + assert.strictEqual(person.firstName, 'Tom', 'We rolled back firstName'); + assert.false(person.hasDirtyAttributes, 'We expect the record to be clean'); }); test('changes to unassigned attributes can be rolled back', function (assert) { @@ -84,12 +84,12 @@ module('unit/model/rollbackAttributes - model.rollbackAttributes()', function (h return person; }); - assert.strictEqual(person.get('firstName'), 'Thomas'); + assert.strictEqual(person.firstName, 'Thomas'); run(() => person.rollbackAttributes()); - assert.strictEqual(person.get('firstName'), undefined); - assert.false(person.get('hasDirtyAttributes')); + assert.strictEqual(person.firstName, undefined); + assert.false(person.hasDirtyAttributes); }); test('changes to attributes made after a record is in-flight only rolls back the local changes', function (assert) { @@ -122,20 +122,20 @@ module('unit/model/rollbackAttributes - model.rollbackAttributes()', function (h return run(() => { let saving = person.save(); - assert.strictEqual(person.get('firstName'), 'Thomas'); + assert.strictEqual(person.firstName, 'Thomas'); person.set('lastName', 'Dolly'); - assert.strictEqual(person.get('lastName'), 'Dolly'); + assert.strictEqual(person.lastName, 'Dolly'); person.rollbackAttributes(); - assert.strictEqual(person.get('firstName'), 'Thomas'); - assert.strictEqual(person.get('lastName'), 'Dale'); - assert.true(person.get('isSaving')); + assert.strictEqual(person.firstName, 'Thomas'); + assert.strictEqual(person.lastName, 'Dale'); + assert.true(person.isSaving); return saving.then(() => { - assert.false(person.get('hasDirtyAttributes'), 'The person is now clean'); + assert.false(person.hasDirtyAttributes, 'The person is now clean'); }); }); }); @@ -170,14 +170,14 @@ module('unit/model/rollbackAttributes - model.rollbackAttributes()', function (h run(function () { person.save().then(null, function () { - assert.true(person.get('isError')); + assert.true(person.isError); assert.deepEqual(person.changedAttributes().firstName, ['Tom', 'Thomas']); run(function () { person.rollbackAttributes(); }); - assert.strictEqual(person.get('firstName'), 'Tom'); - assert.false(person.get('isError')); + assert.strictEqual(person.firstName, 'Tom'); + assert.false(person.isError); assert.strictEqual(Object.keys(person.changedAttributes()).length, 0); }); }); @@ -210,25 +210,25 @@ module('unit/model/rollbackAttributes - model.rollbackAttributes()', function (h run(() => person.deleteRecord()); - assert.strictEqual(people.get('length'), 1, 'a deleted record appears in record array until it is saved'); + assert.strictEqual(people.length, 1, 'a deleted record appears in record array until it is saved'); assert.strictEqual(people.objectAt(0), person, 'a deleted record appears in record array until it is saved'); return run(() => { return person .save() .catch(() => { - assert.true(person.get('isError')); - assert.true(person.get('isDeleted')); + assert.true(person.isError); + assert.true(person.isDeleted); run(() => person.rollbackAttributes()); - assert.false(person.get('isDeleted')); - assert.false(person.get('isError')); - assert.false(person.get('hasDirtyAttributes'), 'must be not dirty'); + assert.false(person.isDeleted); + assert.false(person.isError); + assert.false(person.hasDirtyAttributes, 'must be not dirty'); }) .then(() => { assert.strictEqual( - people.get('length'), + people.length, 1, 'the underlying record array is updated accordingly in an asynchronous way' ); @@ -240,14 +240,14 @@ module('unit/model/rollbackAttributes - model.rollbackAttributes()', function (h let store = this.owner.lookup('service:store'); let person = store.createRecord('person', { id: 1 }); - assert.true(person.get('isNew'), 'must be new'); - assert.true(person.get('hasDirtyAttributes'), 'must be dirty'); + assert.true(person.isNew, 'must be new'); + assert.true(person.hasDirtyAttributes, 'must be dirty'); run(person, 'rollbackAttributes'); - assert.false(person.get('isNew'), 'must not be new'); - assert.false(person.get('hasDirtyAttributes'), 'must not be dirty'); - assert.true(person.get('isDeleted'), 'must be deleted'); + assert.false(person.isNew, 'must not be new'); + assert.false(person.hasDirtyAttributes, 'must not be dirty'); + assert.true(person.isDeleted, 'must be deleted'); }); test(`invalid new record's attributes can be rollbacked`, function (assert) { @@ -270,19 +270,19 @@ module('unit/model/rollbackAttributes - model.rollbackAttributes()', function (h let store = this.owner.lookup('service:store'); let person = store.createRecord('person', { id: 1 }); - assert.true(person.get('isNew'), 'must be new'); - assert.true(person.get('hasDirtyAttributes'), 'must be dirty'); + assert.true(person.isNew, 'must be new'); + assert.true(person.hasDirtyAttributes, 'must be dirty'); return run(() => { return person.save().catch((reason) => { assert.strictEqual(error, reason); - assert.false(person.get('isValid')); + assert.false(person.isValid); run(() => person.rollbackAttributes()); - assert.false(person.get('isNew'), 'must not be new'); - assert.false(person.get('hasDirtyAttributes'), 'must not be dirty'); - assert.true(person.get('isDeleted'), 'must be deleted'); + assert.false(person.isNew, 'must not be new'); + assert.false(person.hasDirtyAttributes, 'must not be dirty'); + assert.true(person.isDeleted, 'must be deleted'); }); }); }); @@ -316,22 +316,22 @@ module('unit/model/rollbackAttributes - model.rollbackAttributes()', function (h }); return run(() => { - assert.strictEqual(person.get('firstName'), 'updated name', 'precondition: firstName is changed'); + assert.strictEqual(person.firstName, 'updated name', 'precondition: firstName is changed'); return person .save() .catch(() => { - assert.true(person.get('hasDirtyAttributes'), 'has dirty attributes'); - assert.strictEqual(person.get('firstName'), 'updated name', 'firstName is still changed'); + assert.true(person.hasDirtyAttributes, 'has dirty attributes'); + assert.strictEqual(person.firstName, 'updated name', 'firstName is still changed'); return person.save(); }) .catch(() => { run(() => person.rollbackAttributes()); - assert.false(person.get('hasDirtyAttributes'), 'has no dirty attributes'); + assert.false(person.hasDirtyAttributes, 'has no dirty attributes'); assert.strictEqual( - person.get('firstName'), + person.firstName, 'original name', 'after rollbackAttributes() firstName has the original value' ); @@ -356,16 +356,16 @@ module('unit/model/rollbackAttributes - model.rollbackAttributes()', function (h person.deleteRecord(); }); - assert.strictEqual(people.get('length'), 1, 'a deleted record appears in the record array until it is saved'); + assert.strictEqual(people.length, 1, 'a deleted record appears in the record array until it is saved'); assert.strictEqual(people.objectAt(0), person, 'a deleted record appears in the record array until it is saved'); - assert.true(person.get('isDeleted'), 'must be deleted'); + assert.true(person.isDeleted, 'must be deleted'); run(() => person.rollbackAttributes()); - assert.strictEqual(people.get('length'), 1, 'the rollbacked record should appear again in the record array'); - assert.false(person.get('isDeleted'), 'must not be deleted'); - assert.false(person.get('hasDirtyAttributes'), 'must not be dirty'); + assert.strictEqual(people.length, 1, 'the rollbacked record should appear again in the record array'); + assert.false(person.isDeleted, 'must not be deleted'); + assert.false(person.hasDirtyAttributes, 'must not be dirty'); }); test("invalid record's attributes can be rollbacked", async function (assert) { @@ -406,7 +406,7 @@ module('unit/model/rollbackAttributes - model.rollbackAttributes()', function (h }); if (!gte('4.0.0')) { - dog.get('errors').addArrayObserver( + dog.errors.addArrayObserver( {}, { willChange() { @@ -428,10 +428,10 @@ module('unit/model/rollbackAttributes - model.rollbackAttributes()', function (h dog.rollbackAttributes(); await settled(); - assert.false(dog.get('hasDirtyAttributes'), 'must not be dirty'); - assert.strictEqual(dog.get('name'), 'Pluto', 'Name is rolled back'); - assert.notOk(dog.get('errors.name'), 'We have no errors for name anymore'); - assert.ok(dog.get('isValid'), 'We are now in a valid state'); + assert.false(dog.hasDirtyAttributes, 'must not be dirty'); + assert.strictEqual(dog.name, 'Pluto', 'Name is rolled back'); + assert.notOk(dog.errors.name, 'We have no errors for name anymore'); + assert.ok(dog.isValid, 'We are now in a valid state'); } if (!gte('4.0.0')) { @@ -473,8 +473,8 @@ module('unit/model/rollbackAttributes - model.rollbackAttributes()', function (h }, }); - dog.set('name', 'is a dwarf planet'); - dog.set('breed', 'planet'); + dog.name = 'is a dwarf planet'; + dog.breed = 'planet'; addObserver(dog, 'errors.name', function () { assert.ok(true, 'errors.name did change'); @@ -484,25 +484,25 @@ module('unit/model/rollbackAttributes - model.rollbackAttributes()', function (h await dog.save(); } catch (reason) { assert.strictEqual(reason, thrownAdapterError); - assert.strictEqual(dog.get('name'), 'is a dwarf planet'); - assert.strictEqual(dog.get('breed'), 'planet'); - assert.ok(isPresent(dog.get('errors.name'))); - assert.strictEqual(dog.get('errors.name.length'), 1); + assert.strictEqual(dog.name, 'is a dwarf planet'); + assert.strictEqual(dog.breed, 'planet'); + assert.ok(isPresent(dog.errors.get('name'))); + assert.strictEqual(dog.errors.get('name.length'), 1); dog.set('name', 'Seymour Asses'); await settled(); - assert.strictEqual(dog.get('name'), 'Seymour Asses'); - assert.strictEqual(dog.get('breed'), 'planet'); + assert.strictEqual(dog.name, 'Seymour Asses'); + assert.strictEqual(dog.breed, 'planet'); dog.rollbackAttributes(); await settled(); - assert.strictEqual(dog.get('name'), 'Pluto'); - assert.strictEqual(dog.get('breed'), 'Disney'); - assert.false(dog.get('hasDirtyAttributes'), 'must not be dirty'); - assert.notOk(dog.get('errors.name')); - assert.ok(dog.get('isValid')); + assert.strictEqual(dog.name, 'Pluto'); + assert.strictEqual(dog.breed, 'Disney'); + assert.false(dog.hasDirtyAttributes, 'must not be dirty'); + assert.notOk(dog.errors.get('name')); + assert.ok(dog.isValid); } }); @@ -547,17 +547,17 @@ module('unit/model/rollbackAttributes - model.rollbackAttributes()', function (h return dog.destroyRecord().catch((reason) => { assert.strictEqual(reason, error); - assert.false(dog.get('isError'), 'must not be error'); - assert.true(dog.get('isDeleted'), 'must be deleted'); - assert.false(dog.get('isValid'), 'must not be valid'); - assert.ok(dog.get('errors.length') > 0, 'must have errors'); + assert.false(dog.isError, 'must not be error'); + assert.true(dog.isDeleted, 'must be deleted'); + assert.false(dog.isValid, 'must not be valid'); + assert.ok(dog.errors.length > 0, 'must have errors'); dog.rollbackAttributes(); - assert.false(dog.get('isError'), 'must not be error after `rollbackAttributes`'); - assert.false(dog.get('isDeleted'), 'must not be deleted after `rollbackAttributes`'); - assert.true(dog.get('isValid'), 'must be valid after `rollbackAttributes`'); - assert.strictEqual(dog.get('errors.length'), 0, 'must not have errors'); + assert.false(dog.isError, 'must not be error after `rollbackAttributes`'); + assert.false(dog.isDeleted, 'must not be deleted after `rollbackAttributes`'); + assert.true(dog.isValid, 'must be valid after `rollbackAttributes`'); + assert.strictEqual(dog.errors.length, 0, 'must not have errors'); }); }); }); diff --git a/packages/-ember-data/tests/unit/private-test.js b/packages/-ember-data/tests/unit/private-test.js deleted file mode 100644 index b66d7782d92..00000000000 --- a/packages/-ember-data/tests/unit/private-test.js +++ /dev/null @@ -1,9 +0,0 @@ -import { module, test } from 'qunit'; - -import { InternalModel } from '@ember-data/store/-private'; - -module('-private', function () { - test('`InternalModel` is accessible via private import', function (assert) { - assert.ok(!!InternalModel); - }); -}); diff --git a/packages/-ember-data/tests/unit/promise-proxies-test.js b/packages/-ember-data/tests/unit/promise-proxies-test.js index 374a028a218..19edc249f06 100644 --- a/packages/-ember-data/tests/unit/promise-proxies-test.js +++ b/packages/-ember-data/tests/unit/promise-proxies-test.js @@ -175,7 +175,7 @@ module('unit/PromiseBelongsTo', function (hooks) { const belongsToProxy = parent.child; assert.expectAssertion(() => { - belongsToProxy.get('meta'); + belongsToProxy.meta; }, 'You attempted to access meta on the promise for the async belongsTo relationship ' + `child:child'.` + '\nUse `record.belongsTo(relationshipName).meta()` instead.'); assert.strictEqual(parent.belongsTo('child').meta(), meta); }); 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 b3aff671f3d..09ecb063c48 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 @@ -29,12 +29,12 @@ module('unit/record-arrays/adapter-populated-record-array - DS.AdapterPopulatedR store: null, }); - assert.false(recordArray.get('isLoaded'), 'expected isLoaded to be false'); - assert.strictEqual(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.false(recordArray.isLoaded, 'expected isLoaded to be false'); + assert.strictEqual(recordArray.modelName, 'recordType', 'has modelName'); + assert.deepEqual(recordArray.content, [], 'has no content'); + assert.strictEqual(recordArray.query, null, 'no query'); + assert.strictEqual(recordArray.store, null, 'no store'); + assert.strictEqual(recordArray.links, null, 'no links'); }); test('custom initial state', async function (assert) { @@ -48,13 +48,13 @@ module('unit/record-arrays/adapter-populated-record-array - DS.AdapterPopulatedR query: 'some-query', links: 'foo', }); - assert.true(recordArray.get('isLoaded')); - assert.false(recordArray.get('isUpdating')); - assert.strictEqual(recordArray.get('modelName'), 'apple'); - assert.deepEqual(recordArray.get('content'), content); - assert.strictEqual(recordArray.get('store'), store); - assert.strictEqual(recordArray.get('query'), 'some-query'); - assert.strictEqual(recordArray.get('links'), 'foo'); + assert.true(recordArray.isLoaded); + assert.false(recordArray.isUpdating); + assert.strictEqual(recordArray.modelName, 'apple'); + assert.deepEqual(recordArray.content, content); + assert.strictEqual(recordArray.store, store); + assert.strictEqual(recordArray.query, 'some-query'); + assert.strictEqual(recordArray.links, 'foo'); }); test('#replace() throws error', function (assert) { @@ -92,7 +92,7 @@ module('unit/record-arrays/adapter-populated-record-array - DS.AdapterPopulatedR query: 'some-query', }); - assert.false(recordArray.get('isUpdating'), 'should not yet be updating'); + assert.false(recordArray.isUpdating, 'should not yet be updating'); assert.strictEqual(queryCalled, 0); @@ -102,11 +102,11 @@ module('unit/record-arrays/adapter-populated-record-array - DS.AdapterPopulatedR const expectedResult = A(); deferred.resolve(expectedResult); - assert.true(recordArray.get('isUpdating'), 'should be updating'); + assert.true(recordArray.isUpdating, 'should be updating'); const result = await updateResult; assert.strictEqual(result, expectedResult); - assert.false(recordArray.get('isUpdating'), 'should no longer be updating'); + assert.false(recordArray.isUpdating, 'should no longer be updating'); }); // TODO: is this method required, i suspect store._query should be refactor so this is not needed @@ -167,8 +167,8 @@ module('unit/record-arrays/adapter-populated-record-array - DS.AdapterPopulatedR assert.strictEqual(didAddRecord, 2, 'two records should have been added'); assert.deepEqual(recordArray.toArray(), [record1, record2], 'should now contain the loaded records by identifier'); - assert.strictEqual(recordArray.get('links').foo, 1, 'has links'); - assert.strictEqual(recordArray.get('meta').bar, 2, 'has meta'); + assert.strictEqual(recordArray.links.foo, 1, 'has links'); + assert.strictEqual(recordArray.meta.bar, 2, 'has meta'); await settled(); }); @@ -252,8 +252,8 @@ module('unit/record-arrays/adapter-populated-record-array - DS.AdapterPopulatedR assert.strictEqual(addAmt, 2, 'expected addAmt'); }); - assert.true(recordArray.get('isLoaded'), 'should be considered loaded'); - assert.false(recordArray.get('isUpdating'), 'should not yet be updating'); + assert.true(recordArray.isLoaded, 'should be considered loaded'); + assert.false(recordArray.isUpdating, 'should not yet be updating'); assert.strictEqual(arrayDidChange, 0); assert.strictEqual(contentDidChange, 0, 'recordArray.content should not have changed'); @@ -284,8 +284,8 @@ module('unit/record-arrays/adapter-populated-record-array - DS.AdapterPopulatedR recordArray._setIdentifiers([recordIdentifierFor(record3), recordIdentifierFor(record4)], {}); assert.strictEqual(didAddRecord, 2, 'expected 2 didAddRecords'); - assert.true(recordArray.get('isLoaded'), 'should be considered loaded'); - assert.false(recordArray.get('isUpdating'), 'should no longer be updating'); + assert.true(recordArray.isLoaded, 'should be considered loaded'); + assert.false(recordArray.isUpdating, 'should no longer be updating'); assert.strictEqual(arrayDidChange, 1, 'record array should have omitted ONE change event'); assert.strictEqual(contentDidChange, 0, 'recordArray.content should not have changed'); @@ -309,8 +309,8 @@ module('unit/record-arrays/adapter-populated-record-array - DS.AdapterPopulatedR }); // re-query - assert.true(recordArray.get('isLoaded'), 'should be considered loaded'); - assert.false(recordArray.get('isUpdating'), 'should not yet be updating'); + assert.true(recordArray.isLoaded, 'should be considered loaded'); + assert.false(recordArray.isUpdating, 'should not yet be updating'); assert.strictEqual(arrayDidChange, 0, 'record array should not yet have omitted a change event'); assert.strictEqual(contentDidChange, 0, 'recordArray.content should not have changed'); @@ -331,8 +331,8 @@ module('unit/record-arrays/adapter-populated-record-array - DS.AdapterPopulatedR assert.strictEqual(didAddRecord, 1, 'expected 0 didAddRecord'); - assert.true(recordArray.get('isLoaded'), 'should be considered loaded'); - assert.false(recordArray.get('isUpdating'), 'should not longer be updating'); + assert.true(recordArray.isLoaded, 'should be considered loaded'); + assert.false(recordArray.isUpdating, 'should not longer be updating'); assert.strictEqual(arrayDidChange, 1, 'record array should have emitted one change event'); assert.strictEqual(contentDidChange, 0, 'recordArray.content should not have changed'); 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 6f934aa5fac..dd9aba05111 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 @@ -178,11 +178,11 @@ module('unit/record-arrays/record-array - DS.RecordArray', function (hooks) { let model3 = { lid: '@lid:model-3' }; assert.strictEqual(recordArray._pushIdentifiers([model1]), undefined, '_pushIdentifiers has no return value'); - assert.deepEqual(recordArray.get('content'), [model1], 'now contains model1'); + assert.deepEqual(recordArray.content, [model1], 'now contains model1'); recordArray._pushIdentifiers([model1]); assert.deepEqual( - recordArray.get('content'), + recordArray.content, [model1, model1], 'allows duplicates, because record-array-manager ensures no duplicates, this layer should not double check' ); @@ -192,7 +192,7 @@ module('unit/record-arrays/record-array - DS.RecordArray', function (hooks) { // can add multiple models at once recordArray._pushIdentifiers([model2, model3]); - assert.deepEqual(recordArray.get('content'), [model1, model2, model3], 'now contains model1, model2, model3'); + assert.deepEqual(recordArray.content, [model1, model2, model3], 'now contains model1, model2, model3'); }); test('#_removeIdentifiers', async function (assert) { @@ -205,17 +205,17 @@ module('unit/record-arrays/record-array - DS.RecordArray', function (hooks) { let model2 = { lid: '@lid:model-2' }; let model3 = { lid: '@lid:model-3' }; - assert.strictEqual(recordArray.get('content').length, 0); + assert.strictEqual(recordArray.content.length, 0); assert.strictEqual(recordArray._removeIdentifiers([model1]), undefined, '_removeIdentifiers has no return value'); - assert.deepEqual(recordArray.get('content'), [], 'now contains no models'); + assert.deepEqual(recordArray.content, [], 'now contains no models'); recordArray._pushIdentifiers([model1, model2]); - assert.deepEqual(recordArray.get('content'), [model1, model2], 'now contains model1, model2,'); + assert.deepEqual(recordArray.content, [model1, model2], 'now contains model1, model2,'); assert.strictEqual(recordArray._removeIdentifiers([model1]), undefined, '_removeIdentifiers has no return value'); - assert.deepEqual(recordArray.get('content'), [model2], 'now only contains model2'); + assert.deepEqual(recordArray.content, [model2], 'now only contains model2'); assert.strictEqual(recordArray._removeIdentifiers([model2]), undefined, '_removeIdentifiers has no return value'); - assert.deepEqual(recordArray.get('content'), [], 'now contains no models'); + assert.deepEqual(recordArray.content, [], 'now contains no models'); recordArray._pushIdentifiers([model1, model2, model3]); @@ -225,9 +225,9 @@ module('unit/record-arrays/record-array - DS.RecordArray', function (hooks) { '_removeIdentifiers has no return value' ); - assert.deepEqual(recordArray.get('content'), [model2], 'now contains model2'); + assert.deepEqual(recordArray.content, [model2], 'now contains model2'); assert.strictEqual(recordArray._removeIdentifiers([model2]), undefined, '_removeIdentifiers has no return value'); - assert.deepEqual(recordArray.get('content'), [], 'now contains no models'); + assert.deepEqual(recordArray.content, [], 'now contains no models'); }); test('#save', async function (assert) { @@ -366,12 +366,12 @@ module('unit/record-arrays/record-array - DS.RecordArray', function (hooks) { assert.strictEqual( snapshot1.id, String(model1.id), - 'record array snapshot should contain the first internalModel.createSnapshot result' + 'record array snapshot should contain the first createSnapshot result' ); assert.strictEqual( snapshot2.id, String(model2.id), - 'record array snapshot should contain the second internalModel.createSnapshot result' + 'record array snapshot should contain the second createSnapshot result' ); }); diff --git a/packages/-ember-data/tests/unit/store/adapter-interop-test.js b/packages/-ember-data/tests/unit/store/adapter-interop-test.js index 3289c9b869c..6136d9867f8 100644 --- a/packages/-ember-data/tests/unit/store/adapter-interop-test.js +++ b/packages/-ember-data/tests/unit/store/adapter-interop-test.js @@ -1,5 +1,6 @@ import { get, set } from '@ember/object'; import { later, run } from '@ember/runloop'; +import { settled } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { all, Promise as EmberPromise, resolve } from 'rsvp'; @@ -185,7 +186,7 @@ module('unit/store/adapter-interop - Store working with a Adapter', function (ho return store .findRecord('test', 1) .then((object) => { - assert.strictEqual(typeof object.get('id'), 'string', 'id was coerced to a string'); + assert.strictEqual(typeof object.id, 'string', 'id was coerced to a string'); run(() => { store.push({ data: { @@ -203,7 +204,7 @@ module('unit/store/adapter-interop - Store working with a Adapter', function (ho .then((object) => { assert.ok(object, 'object was found'); assert.strictEqual( - typeof object.get('id'), + typeof object.id, 'string', 'id is a string despite being supplied and searched for as a number' ); @@ -523,12 +524,9 @@ module('unit/store/adapter-interop - Store working with a Adapter', function (ho return run(() => { return store.findRecord('person', 1, { preload: { friend: 2 } }).then(() => { - return store - .peekRecord('person', 1) - .get('friend') - .then((friend) => { - assert.strictEqual(friend.get('id'), '2', 'Preloaded belongsTo set'); - }); + return store.peekRecord('person', 1).friend.then((friend) => { + assert.strictEqual(friend.id, '2', 'Preloaded belongsTo set'); + }); }); }); }); @@ -679,7 +677,7 @@ module('unit/store/adapter-interop - Store working with a Adapter', function (ho return run(() => { return all([tom.save(), yehuda.save()]).then(() => { people.forEach((person, index) => { - assert.strictEqual(person.get('id'), String(index + 1), `The record's id should be correct.`); + assert.strictEqual(person.id, String(index + 1), `The record's id should be correct.`); }); }); }); @@ -991,7 +989,7 @@ module('unit/store/adapter-interop - Store working with a Adapter', function (ho }); return store.findRecord('person', 1).then((record) => { - assert.strictEqual(record.get('name'), 'Tom'); + assert.strictEqual(record.name, 'Tom'); }); }); }); @@ -1031,7 +1029,7 @@ module('unit/store/adapter-interop - Store working with a Adapter', function (ho }); return store.findRecord('person', 1).then((record) => { - assert.strictEqual(record.get('name'), 'Tom'); + assert.strictEqual(record.name, 'Tom'); }); }); }); @@ -1065,7 +1063,7 @@ module('unit/store/adapter-interop - Store working with a Adapter', function (ho }); return store.findRecord('person', 1).then((record) => { - assert.strictEqual(record.get('name'), undefined); + assert.strictEqual(record.name, undefined); }); }); }); @@ -1099,11 +1097,11 @@ module('unit/store/adapter-interop - Store working with a Adapter', function (ho }); return store.findRecord('person', 1).then((record) => { - assert.strictEqual(record.get('name'), undefined); + assert.strictEqual(record.name, undefined); }); }); - assert.strictEqual(store.peekRecord('person', 1).get('name'), 'Tom'); + assert.strictEqual(store.peekRecord('person', 1).name, 'Tom'); return done; }); @@ -1159,7 +1157,7 @@ module('unit/store/adapter-interop - Store working with a Adapter', function (ho return run(() => { return store.findAll('person').then((records) => { - assert.strictEqual(records.get('firstObject.name'), 'Tom'); + assert.strictEqual(records.firstObject.name, 'Tom'); }); }); }); @@ -1192,7 +1190,7 @@ module('unit/store/adapter-interop - Store working with a Adapter', function (ho return run(() => { return store.findAll('person').then((records) => { - assert.strictEqual(records.get('firstObject.name'), 'Tom'); + assert.strictEqual(records.firstObject.name, 'Tom'); }); }); }); @@ -1223,12 +1221,12 @@ module('unit/store/adapter-interop - Store working with a Adapter', function (ho return run(() => { return store.findAll('person').then((records) => { - assert.strictEqual(records.get('firstObject'), undefined); + assert.strictEqual(records.firstObject, undefined); }); }); }); - test('store should reload all records in the background when `shouldBackgroundReloadAll` is true', function (assert) { + test('store should reload all records in the background when `shouldBackgroundReloadAll` is true', async function (assert) { assert.expect(5); const Person = Model.extend({ @@ -1245,8 +1243,12 @@ module('unit/store/adapter-interop - Store working with a Adapter', function (ho return true; }, findAll() { - assert.ok(true, 'find should not be called'); - return { data: [{ id: 1, type: 'person', attributes: { name: 'Tom' } }] }; + assert.ok(true, 'findAll should be called'); + return new Promise((resolve) => setTimeout(resolve, 1)).then(() => { + return { + data: [{ id: 1, type: 'person', attributes: { name: 'Tom' } }], + }; + }); }, }); @@ -1256,15 +1258,14 @@ module('unit/store/adapter-interop - Store working with a Adapter', function (ho let store = this.owner.lookup('service:store'); - let done = run(() => { - return store.findAll('person').then((records) => { - assert.strictEqual(records.get('firstObject.name'), undefined); - }); - }); + store.push({ data: [{ id: '1', type: 'person', attributes: { name: 'John' } }] }); - assert.strictEqual(store.peekRecord('person', 1).get('name'), 'Tom'); + const records = await store.findAll('person'); - return done; + assert.strictEqual(records.firstObject.name, 'John', 'on initial load name is stale'); + + await settled(); + assert.strictEqual(store.peekRecord('person', 1).name, 'Tom', 'after background reload name is loaded'); }); testInDebug('Calling adapterFor with a model class should assert', function (assert) { diff --git a/packages/-ember-data/tests/unit/store/create-record-test.js b/packages/-ember-data/tests/unit/store/create-record-test.js index ae9ef6c08b0..b1181e9c9b7 100644 --- a/packages/-ember-data/tests/unit/store/create-record-test.js +++ b/packages/-ember-data/tests/unit/store/create-record-test.js @@ -103,14 +103,14 @@ module('unit/store/createRecord - Store creating records', function (hooks) { let records = store.peekAll('record').toArray(); let storage = store.createRecord('storage', { name: 'Great store', records: records }); - assert.strictEqual(storage.get('name'), 'Great store', 'The attribute is well defined'); + assert.strictEqual(storage.name, 'Great store', 'The attribute is well defined'); assert.strictEqual( - storage.get('records').findBy('id', '1'), + storage.records.findBy('id', '1'), records.find((r) => r.id === '1'), 'Defined relationships are allowed in createRecord' ); assert.strictEqual( - storage.get('records').findBy('id', '2'), + storage.records.findBy('id', '2'), records.find((r) => r.id === '2'), 'Defined relationships are allowed in createRecord' ); @@ -131,7 +131,7 @@ module('unit/store/createRecord - Store with models by dash', function (hooks) { let attributes = { foo: 'bar' }; let record = store.createRecord('some-thing', attributes); - assert.strictEqual(record.get('foo'), attributes.foo, 'The record is created'); + assert.strictEqual(record.foo, attributes.foo, 'The record is created'); assert.strictEqual(store.modelFor('some-thing').modelName, 'some-thing'); }); }); diff --git a/packages/-ember-data/tests/unit/store/finders-test.js b/packages/-ember-data/tests/unit/store/finders-test.js index d7e5551e595..ca8f0393758 100644 --- a/packages/-ember-data/tests/unit/store/finders-test.js +++ b/packages/-ember-data/tests/unit/store/finders-test.js @@ -141,7 +141,7 @@ module('unit/store/finders', function (hooks) { }, }); - let storePromise = this.store.peekRecord('person', 1).get('dogs'); + let storePromise = this.store.peekRecord('person', 1).dogs; assert.false(serializerLoaded, 'serializer is not eagerly loaded'); deferedFind.resolve({ @@ -194,7 +194,7 @@ module('unit/store/finders', function (hooks) { }, }); - let storePromise = this.store.peekRecord('person', 1).get('favoriteDog'); + let storePromise = this.store.peekRecord('person', 1).favoriteDog; assert.false(serializerLoaded, 'serializer is not eagerly loaded'); diff --git a/packages/-ember-data/tests/unit/store/push-test.js b/packages/-ember-data/tests/unit/store/push-test.js index 910f3e15111..4f7144f911c 100644 --- a/packages/-ember-data/tests/unit/store/push-test.js +++ b/packages/-ember-data/tests/unit/store/push-test.js @@ -45,12 +45,12 @@ module('unit/store/push - Store#push', function (hooks) { }, }); - assert.strictEqual(person.get('firstName'), 'original first name', 'initial first name is correct'); - assert.strictEqual(person.get('currentState.stateName'), 'root.loaded.saved', 'initial state name is correct'); + assert.strictEqual(person.firstName, 'original first name', 'initial first name is correct'); + assert.strictEqual(person.currentState.stateName, 'root.loaded.saved', 'initial state name is correct'); person.set('firstName', 'updated first name'); - assert.strictEqual(person.get('firstName'), 'updated first name', 'mutated first name is correct'); + assert.strictEqual(person.firstName, 'updated first name', 'mutated first name is correct'); assert.strictEqual( person.currentState.stateName, 'root.loaded.updated.uncommitted', @@ -73,8 +73,8 @@ module('unit/store/push - Store#push', function (hooks) { }, }); - assert.strictEqual(person.get('firstName'), 'updated first name'); - assert.strictEqual(person.get('currentState.stateName'), 'root.loaded.saved'); + assert.strictEqual(person.firstName, 'updated first name'); + assert.strictEqual(person.currentState.stateName, 'root.loaded.saved'); assert.false(person.currentState.isDirty, 'currentState is not Dirty after push'); assert.notOk(person.changedAttributes().firstName); }); @@ -186,8 +186,8 @@ module('unit/store/push - Store#push', function (hooks) { ); }); - assert.strictEqual(person.get('firstName'), 'Jacquie', 'you can push raw JSON into the store'); - assert.strictEqual(person.get('lastName'), 'Jackson', 'existing fields are untouched'); + assert.strictEqual(person.firstName, 'Jacquie', 'you can push raw JSON into the store'); + assert.strictEqual(person.lastName, 'Jackson', 'existing fields are untouched'); }); test('Calling push with a normalized hash containing IDs of related records returns a record', function (assert) { @@ -255,7 +255,7 @@ module('unit/store/push - Store#push', function (hooks) { }); let person = store.push(normalized); - return person.get('phoneNumbers').then((phoneNumbers) => { + return person.phoneNumbers.then((phoneNumbers) => { let items = phoneNumbers.map((item) => { return item ? item.getProperties('id', 'number', 'person') : null; }); @@ -370,7 +370,7 @@ module('unit/store/push - Store#push', function (hooks) { let person = store.peekRecord('person', 1); - assert.strictEqual(person.get('phoneNumbers.length'), 1); + assert.strictEqual(person.phoneNumbers.length, 1); assert.strictEqual(person.get('phoneNumbers.firstObject.number'), '1-800-DATA'); // GET /persons/1 @@ -390,7 +390,7 @@ module('unit/store/push - Store#push', function (hooks) { }); }); - assert.strictEqual(person.get('phoneNumbers.length'), 1); + assert.strictEqual(person.phoneNumbers.length, 1); assert.strictEqual(person.get('phoneNumbers.firstObject.number'), '1-800-DATA'); } ); @@ -438,7 +438,7 @@ module('unit/store/push - Store#push', function (hooks) { let person = store.peekRecord('person', 1); - assert.strictEqual(person.get('firstName'), 'Tan', 'you can use links containing an object'); + assert.strictEqual(person.firstName, 'Tan', 'you can use links containing an object'); }); test('Calling push with a link containing the value null', function (assert) { @@ -464,7 +464,7 @@ module('unit/store/push - Store#push', function (hooks) { let person = store.peekRecord('person', 1); - assert.strictEqual(person.get('firstName'), 'Tan', 'you can use links that contain null as a value'); + assert.strictEqual(person.firstName, 'Tan', 'you can use links that contain null as a value'); }); testInDebug('calling push with hasMany relationship the value must be an array', function (assert) { @@ -633,7 +633,7 @@ module('unit/store/push - Store#pushPayload', function (hooks) { let post = store.peekRecord('post', 1); - assert.strictEqual(post.get('postTitle'), 'Ember rocks', 'you can push raw JSON into the store'); + assert.strictEqual(post.postTitle, 'Ember rocks', 'you can push raw JSON into the store'); run(() => { store.pushPayload('post', { @@ -646,7 +646,7 @@ module('unit/store/push - Store#pushPayload', function (hooks) { }); }); - assert.strictEqual(post.get('postTitle'), 'Ember rocks (updated)', 'You can update data in the store'); + assert.strictEqual(post.postTitle, 'Ember rocks (updated)', 'You can update data in the store'); }); test('Calling pushPayload allows pushing singular payload properties', function (assert) { @@ -663,7 +663,7 @@ module('unit/store/push - Store#pushPayload', function (hooks) { let post = store.peekRecord('post', 1); - assert.strictEqual(post.get('postTitle'), 'Ember rocks', 'you can push raw JSON into the store'); + assert.strictEqual(post.postTitle, 'Ember rocks', 'you can push raw JSON into the store'); run(() => { store.pushPayload('post', { @@ -674,7 +674,7 @@ module('unit/store/push - Store#pushPayload', function (hooks) { }); }); - assert.strictEqual(post.get('postTitle'), 'Ember rocks (updated)', 'You can update data in the store'); + assert.strictEqual(post.postTitle, 'Ember rocks (updated)', 'You can update data in the store'); }); test(`Calling pushPayload should use the type's serializer for normalizing`, function (assert) { @@ -726,11 +726,11 @@ module('unit/store/push - Store#pushPayload', function (hooks) { let post = store.peekRecord('post', '1'); - assert.strictEqual(post.get('postTitle'), 'Ember rocks', 'you can push raw JSON into the store'); + assert.strictEqual(post.postTitle, 'Ember rocks', 'you can push raw JSON into the store'); let person = store.peekRecord('person', '2'); - assert.strictEqual(person.get('firstName'), 'Yehuda', 'you can push raw JSON into the store'); + assert.strictEqual(person.firstName, 'Yehuda', 'you can push raw JSON into the store'); }); test(`Calling pushPayload without a type uses application serializer's pushPayload method`, function (assert) { @@ -803,11 +803,11 @@ module('unit/store/push - Store#pushPayload', function (hooks) { var post = store.peekRecord('post', 1); - assert.strictEqual(post.get('postTitle'), 'Ember rocks', 'you can push raw JSON into the store'); + assert.strictEqual(post.postTitle, 'Ember rocks', 'you can push raw JSON into the store'); var person = store.peekRecord('person', 2); - assert.strictEqual(person.get('firstName'), 'Yehuda', 'you can push raw JSON into the store'); + assert.strictEqual(person.firstName, 'Yehuda', 'you can push raw JSON into the store'); }); test('Calling pushPayload allows partial updates with raw JSON', function (assert) { @@ -835,8 +835,8 @@ module('unit/store/push - Store#pushPayload', function (hooks) { let person = store.peekRecord('person', 1); - assert.strictEqual(person.get('firstName'), 'Robert', 'you can push raw JSON into the store'); - assert.strictEqual(person.get('lastName'), 'Jackson', 'you can push raw JSON into the store'); + assert.strictEqual(person.firstName, 'Robert', 'you can push raw JSON into the store'); + assert.strictEqual(person.lastName, 'Jackson', 'you can push raw JSON into the store'); run(() => { store.pushPayload('person', { @@ -849,8 +849,8 @@ module('unit/store/push - Store#pushPayload', function (hooks) { }); }); - assert.strictEqual(person.get('firstName'), 'Jacquie', 'you can push raw JSON into the store'); - assert.strictEqual(person.get('lastName'), 'Jackson', 'existing fields are untouched'); + assert.strictEqual(person.firstName, 'Jacquie', 'you can push raw JSON into the store'); + assert.strictEqual(person.lastName, 'Jackson', 'existing fields are untouched'); }); testInDebug( @@ -979,10 +979,10 @@ module('unit/store/push - Store#push with JSON-API', function (hooks) { }); let tom = store.peekRecord('person', 1); - assert.strictEqual(tom.get('name'), 'Tom Dale', 'Tom should be in the store'); + assert.strictEqual(tom.name, 'Tom Dale', 'Tom should be in the store'); let tomster = store.peekRecord('person', 2); - assert.strictEqual(tomster.get('name'), 'Tomster', 'Tomster should be in the store'); + assert.strictEqual(tomster.name, 'Tomster', 'Tomster should be in the store'); }); test('Should support pushing included models into the store', function (assert) { @@ -1032,9 +1032,9 @@ module('unit/store/push - Store#push with JSON-API', function (hooks) { }); let tomster = store.peekRecord('person', 1); - assert.strictEqual(tomster.get('name'), 'Tomster', 'Tomster should be in the store'); + assert.strictEqual(tomster.name, 'Tomster', 'Tomster should be in the store'); let car = store.peekRecord('car', 1); - assert.strictEqual(car.get('model'), 'Neon', "Tomster's car should be in the store"); + assert.strictEqual(car.model, 'Neon', "Tomster's car should be in the store"); }); }); diff --git a/packages/-ember-data/tests/unit/store/unload-test.js b/packages/-ember-data/tests/unit/store/unload-test.js index dc4182d5d75..f1929498d5b 100644 --- a/packages/-ember-data/tests/unit/store/unload-test.js +++ b/packages/-ember-data/tests/unit/store/unload-test.js @@ -9,6 +9,7 @@ import { setupTest } from 'ember-qunit'; import Adapter from '@ember-data/adapter'; import Model, { attr, belongsTo } from '@ember-data/model'; import JSONAPISerializer from '@ember-data/serializer/json-api'; +import { recordIdentifierFor } from '@ember-data/store'; import testInDebug from '@ember-data/unpublished-test-infra/test-support/test-in-debug'; let store, tryToFind; @@ -70,7 +71,7 @@ module('unit/store/unload - Store unloading records', function (hooks) { function () { record.unloadRecord(); }, - 'You can only unload a record which is not inFlight. `' + record._internalModel.toString() + '`', + `You can only unload a record which is not inFlight. '` + recordIdentifierFor(record).toString() + `'`, 'can not unload dirty record' ); @@ -212,7 +213,7 @@ module('Store - unload record with relationships', function (hooks) { }) .then((product) => { assert.strictEqual( - product.get('description'), + product.description, 'cuisinart', "The record was unloaded and the adapter's `findRecord` was called" ); diff --git a/packages/-ember-data/tests/unit/system/relationships/polymorphic-relationship-payloads-test.js b/packages/-ember-data/tests/unit/system/relationships/polymorphic-relationship-payloads-test.js index 90a8056e8a3..8d3dfffd1ad 100644 --- a/packages/-ember-data/tests/unit/system/relationships/polymorphic-relationship-payloads-test.js +++ b/packages/-ember-data/tests/unit/system/relationships/polymorphic-relationship-payloads-test.js @@ -64,7 +64,7 @@ module('unit/relationships/relationship-payloads-manager (polymorphic)', functio const user = run(() => this.store.push(userData)); - const finalResult = user.get('hats').mapBy('type'); + const finalResult = user.hats.mapBy('type'); assert.deepEqual(finalResult, ['hat', 'big-hat', 'small-hat'], 'We got all our hats!'); }); @@ -118,7 +118,7 @@ module('unit/relationships/relationship-payloads-manager (polymorphic)', functio }; const user = run(() => this.store.push(userData)), - finalResult = user.get('hats').mapBy('type'), + finalResult = user.hats.mapBy('type'), expectedResults = included.map((m) => m.type); assert.deepEqual(finalResult, expectedResults, 'We got all our hats!'); @@ -174,7 +174,7 @@ module('unit/relationships/relationship-payloads-manager (polymorphic)', functio }; const user = run(() => this.store.push(userData)), - finalResult = user.get('hats').mapBy('type'), + finalResult = user.hats.mapBy('type'), expectedResults = included.map((m) => m.type); assert.deepEqual(finalResult, expectedResults, 'We got all our hats!'); @@ -230,7 +230,7 @@ module('unit/relationships/relationship-payloads-manager (polymorphic)', functio const expectedAlienResults = alienIncluded.map((m) => m.type), alien = run(() => this.store.push(alienData)), - alienFinalHats = alien.get('hats').mapBy('type'); + alienFinalHats = alien.hats.mapBy('type'); assert.deepEqual(alienFinalHats, expectedAlienResults, 'We got all alien hats!'); }); @@ -309,8 +309,8 @@ module('unit/relationships/relationship-payloads-manager (polymorphic)', functio return this.store.push(smallPersonData); }); - const finalBigResult = bigPerson.get('hats').toArray(); - const finalSmallResult = smallPerson.get('hats').toArray(); + const finalBigResult = bigPerson.hats.toArray(); + const finalSmallResult = smallPerson.hats.toArray(); assert.strictEqual(finalBigResult.length, 4, 'We got all our hats!'); assert.strictEqual(finalSmallResult.length, 2, 'We got all our hats!'); @@ -403,13 +403,10 @@ module('unit/relationships/relationship-payloads-manager (polymorphic)', functio return this.store.push(payload); }); - const familyResultReferences = boyInstance - .get('family') - .toArray() - .map((i) => { - return { type: i.constructor.modelName, id: i.id }; - }); - const twinResult = boyInstance.get('twin'); + const familyResultReferences = boyInstance.family.toArray().map((i) => { + return { type: i.constructor.modelName, id: i.id }; + }); + const twinResult = boyInstance.twin; const twinResultReference = { type: twinResult.constructor.modelName, id: twinResult.id }; assert.deepEqual(familyResultReferences, expectedFamilyReferences, 'We linked family correctly'); @@ -506,13 +503,10 @@ module('unit/relationships/relationship-payloads-manager (polymorphic)', functio return this.store.push(payload); }); - const familyResultReferences = boyInstance - .get('family') - .toArray() - .map((i) => { - return { type: i.constructor.modelName, id: i.id }; - }); - const twinResult = boyInstance.get('twin'); + const familyResultReferences = boyInstance.family.toArray().map((i) => { + return { type: i.constructor.modelName, id: i.id }; + }); + const twinResult = boyInstance.twin; const twinResultReference = twinResult && { type: twinResult.constructor.modelName, id: twinResult.id, @@ -558,13 +552,10 @@ module('unit/relationships/relationship-payloads-manager (polymorphic)', functio const expectedHatReference = { id: '2', type: 'big-hat' }; const expectedHatsReferences = [{ id: '1', type: 'big-hat' }]; - const finalHatsReferences = hat2 - .get('hats') - .toArray() - .map((i) => { - return { type: i.constructor.modelName, id: i.id }; - }); - const hatResult = hat1.get('hat'); + const finalHatsReferences = hat2.hats.toArray().map((i) => { + return { type: i.constructor.modelName, id: i.id }; + }); + const hatResult = hat1.hat; const finalHatReference = hatResult && { type: hatResult.constructor.modelName, id: hatResult.id, @@ -605,13 +596,10 @@ module('unit/relationships/relationship-payloads-manager (polymorphic)', functio const expectedHatReference = { id: '1', type: 'big-hat' }; const expectedHatsReferences = [{ id: '1', type: 'big-hat' }]; - const finalHatsReferences = hat - .get('hats') - .toArray() - .map((i) => { - return { type: i.constructor.modelName, id: i.id }; - }); - const hatResult = hat.get('hat'); + const finalHatsReferences = hat.hats.toArray().map((i) => { + return { type: i.constructor.modelName, id: i.id }; + }); + const hatResult = hat.hat; const finalHatReference = hatResult && { type: hatResult.constructor.modelName, id: hatResult.id, @@ -666,7 +654,7 @@ module('unit/relationships/relationship-payloads-manager (polymorphic)', functio }) ); - const hats = user.get('hats'); + const hats = user.hats; assert.deepEqual( hats.map((h) => h.constructor.modelName), @@ -722,7 +710,7 @@ module('unit/relationships/relationship-payloads-manager (polymorphic)', functio }) ); - const hats = user.get('hats'); + const hats = user.hats; assert.deepEqual( hats.map((h) => h.constructor.modelName), @@ -810,11 +798,11 @@ module('unit/relationships/relationship-payloads-manager (polymorphic)', functio return this.store.push(smallPersonData); }); - const finalBigResult = bigPerson.get('hats').toArray(); - const finalSmallResult = smallPerson.get('hats').toArray(); + const finalBigResult = bigPerson.hats.toArray(); + const finalSmallResult = smallPerson.hats.toArray(); assert.deepEqual( - finalBigResult.map((h) => ({ type: h.constructor.modelName, id: h.get('id') })), + finalBigResult.map((h) => ({ type: h.constructor.modelName, id: h.id })), [ { type: 'big-hat', id: '1' }, { type: 'small-hat', id: '1' }, @@ -825,7 +813,7 @@ module('unit/relationships/relationship-payloads-manager (polymorphic)', functio ); assert.deepEqual( - finalSmallResult.map((h) => ({ type: h.constructor.modelName, id: h.get('id') })), + finalSmallResult.map((h) => ({ type: h.constructor.modelName, id: h.id })), [ { type: 'big-hat', id: '3' }, { type: 'small-hat', id: '3' }, diff --git a/packages/-ember-data/tests/unit/utils-test.js b/packages/-ember-data/tests/unit/utils-test.js index 89080884fca..e3d8ce15d3a 100644 --- a/packages/-ember-data/tests/unit/utils-test.js +++ b/packages/-ember-data/tests/unit/utils-test.js @@ -1,12 +1,11 @@ import Mixin from '@ember/object/mixin'; -import { run } from '@ember/runloop'; -import { module, test } from 'qunit'; +import { module } from 'qunit'; import { setupTest } from 'ember-qunit'; -import Model, { attr, belongsTo, hasMany } from '@ember-data/model'; -import { modelHasAttributeOrRelationshipNamedType } from '@ember-data/serializer/-private'; +import Model, { hasMany } from '@ember-data/model'; +import { recordIdentifierFor } from '@ember-data/store'; import { assertPolymorphicType } from '@ember-data/store/-debug'; import testInDebug from '@ember-data/unpublished-test-infra/test-support/test-in-debug'; @@ -37,41 +36,33 @@ module('unit/utils', function (hooks) { }); testInDebug('assertPolymorphicType works for subclasses', function (assert) { - let user, post, person; let store = this.owner.lookup('service:store'); - - run(() => { - store.push({ - data: [ - { - type: 'user', - id: '1', - relationships: { - messages: { - data: [], - }, + let [user, post, person] = store.push({ + data: [ + { + type: 'user', + id: '1', + relationships: { + messages: { + data: [], }, }, - { - type: 'post', - id: '1', - }, - { - type: 'person', - id: '1', - }, - ], - }); - - user = store.peekRecord('user', 1); - post = store.peekRecord('post', 1); - person = store.peekRecord('person', 1); + }, + { + type: 'post', + id: '1', + }, + { + type: 'person', + id: '1', + }, + ], }); let relationship = user.relationshipFor('messages'); - user = user._internalModel.identifier; - post = post._internalModel.identifier; - person = person._internalModel.identifier; + user = recordIdentifierFor(user); + post = recordIdentifierFor(post); + person = recordIdentifierFor(person); try { assertPolymorphicType(user, relationship, post, store); @@ -84,60 +75,29 @@ module('unit/utils', function (hooks) { }, "The 'person' type does not implement 'message' and thus cannot be assigned to the 'messages' relationship in 'user'. Make it a descendant of 'message' or use a mixin of the same name."); }); - test('modelHasAttributeOrRelationshipNamedType', function (assert) { - class Blank extends Model {} - class ModelWithTypeAttribute extends Model { - @attr type; - } - class ModelWithTypeBelongsTo extends Model { - @belongsTo type; - } - class ModelWithTypeHasMany extends Model { - @hasMany type; - } - this.owner.register('model:blank', Blank); - this.owner.register('model:with-attr', ModelWithTypeAttribute); - this.owner.register('model:with-belongs-to', ModelWithTypeBelongsTo); - this.owner.register('model:with-has-many', ModelWithTypeHasMany); - const store = this.owner.lookup('service:store'); - - assert.false(modelHasAttributeOrRelationshipNamedType(store.modelFor('blank'))); - - assert.true(modelHasAttributeOrRelationshipNamedType(store.modelFor('with-attr'))); - assert.true(modelHasAttributeOrRelationshipNamedType(store.modelFor('with-belongs-to'))); - assert.true(modelHasAttributeOrRelationshipNamedType(store.modelFor('with-has-many'))); - }); - testInDebug('assertPolymorphicType works for mixins', function (assert) { - let post, video, person; let store = this.owner.lookup('service:store'); - - run(() => { - store.push({ - data: [ - { - type: 'post', - id: '1', - }, - { - type: 'video', - id: '1', - }, - { - type: 'person', - id: '1', - }, - ], - }); - post = store.peekRecord('post', 1); - video = store.peekRecord('video', 1); - person = store.peekRecord('person', 1); + let [post, video, person] = store.push({ + data: [ + { + type: 'post', + id: '1', + }, + { + type: 'video', + id: '1', + }, + { + type: 'person', + id: '1', + }, + ], }); let relationship = post.relationshipFor('medias'); - post = post._internalModel.identifier; - video = video._internalModel.identifier; - person = person._internalModel.identifier; + post = recordIdentifierFor(post); + video = recordIdentifierFor(video); + person = recordIdentifierFor(person); try { assertPolymorphicType(post, relationship, video, store); diff --git a/packages/adapter/addon/-private/build-url-mixin.ts b/packages/adapter/addon/-private/build-url-mixin.ts index e7692e7fb99..d949bd6ecff 100644 --- a/packages/adapter/addon/-private/build-url-mixin.ts +++ b/packages/adapter/addon/-private/build-url-mixin.ts @@ -3,8 +3,8 @@ import { camelize } from '@ember/string'; import { pluralize } from 'ember-inflector'; -import type Snapshot from '@ember-data/store/-private/snapshot'; -import type SnapshotRecordArray from '@ember-data/store/-private/snapshot-record-array'; +import type Snapshot from '@ember-data/store/-private/network/snapshot'; +import type SnapshotRecordArray from '@ember-data/store/-private/network/snapshot-record-array'; import type { Dict } from '@ember-data/types/q/utils'; /** diff --git a/packages/adapter/addon/-private/utils/serialize-into-hash.ts b/packages/adapter/addon/-private/utils/serialize-into-hash.ts index e82b8339753..92cbfbdac6d 100644 --- a/packages/adapter/addon/-private/utils/serialize-into-hash.ts +++ b/packages/adapter/addon/-private/utils/serialize-into-hash.ts @@ -3,7 +3,7 @@ import { assert } from '@ember/debug'; import type { Snapshot } from 'ember-data/-private'; import type Store from '@ember-data/store'; -import type ShimModelClass from '@ember-data/store/-private/model/shim-model-class'; +import type ShimModelClass from '@ember-data/store/-private/legacy-model-support/shim-model-class'; import type { DSModelSchema } from '@ember-data/types/q/ds-model'; import type { MinimumSerializerInterface } from '@ember-data/types/q/minimum-serializer-interface'; diff --git a/packages/adapter/addon/error.js b/packages/adapter/addon/error.js index 9dedfe0a64f..8f88239ad9f 100644 --- a/packages/adapter/addon/error.js +++ b/packages/adapter/addon/error.js @@ -298,7 +298,7 @@ ForbiddenError.prototype.code = 'ForbiddenError'; export default class PostRoute extends Route { @service store; model(params) { - return this.get('store').findRecord('post', params.post_id); + return this.store.findRecord('post', params.post_id); } @action error(error, transition) { diff --git a/packages/adapter/addon/index.ts b/packages/adapter/addon/index.ts index 76ed5357b5b..bf7e8a64b8e 100644 --- a/packages/adapter/addon/index.ts +++ b/packages/adapter/addon/index.ts @@ -143,8 +143,8 @@ import { Promise as RSVPPromise } from 'rsvp'; import type Store from '@ember-data/store'; import type { Snapshot } from '@ember-data/store/-private'; -import type ShimModelClass from '@ember-data/store/-private/model/shim-model-class'; -import type SnapshotRecordArray from '@ember-data/store/-private/snapshot-record-array'; +import type ShimModelClass from '@ember-data/store/-private/legacy-model-support/shim-model-class'; +import type SnapshotRecordArray from '@ember-data/store/-private/network/snapshot-record-array'; import type { AdapterPayload, MinimumAdapterInterface } from '@ember-data/types/q/minimum-adapter-interface'; import type { Dict } from '@ember-data/types/q/utils'; diff --git a/packages/adapter/addon/json-api.ts b/packages/adapter/addon/json-api.ts index 9ddcc2d1c61..5a61d9f6dac 100644 --- a/packages/adapter/addon/json-api.ts +++ b/packages/adapter/addon/json-api.ts @@ -7,8 +7,8 @@ import { dasherize } from '@ember/string'; import { pluralize } from 'ember-inflector'; import type Store from '@ember-data/store'; -import type ShimModelClass from '@ember-data/store/-private/model/shim-model-class'; -import type Snapshot from '@ember-data/store/-private/snapshot'; +import type ShimModelClass from '@ember-data/store/-private/legacy-model-support/shim-model-class'; +import type Snapshot from '@ember-data/store/-private/network/snapshot'; import type { AdapterPayload } from '@ember-data/types/q/minimum-adapter-interface'; import { serializeIntoHash } from './-private'; @@ -200,7 +200,7 @@ class JSONAPIAdapter extends RESTAdapter { } ``` - By default calling `post.get('comments')` will trigger the following requests(assuming the + By default calling `post.comments` will trigger the following requests(assuming the comments haven't been loaded before): ``` diff --git a/packages/adapter/addon/rest.ts b/packages/adapter/addon/rest.ts index 33e8cca63d8..a45441e03ce 100644 --- a/packages/adapter/addon/rest.ts +++ b/packages/adapter/addon/rest.ts @@ -10,9 +10,9 @@ import { DEBUG } from '@glimmer/env'; import { Promise as RSVPPromise } from 'rsvp'; import type Store from '@ember-data/store'; -import type ShimModelClass from '@ember-data/store/-private/model/shim-model-class'; -import type Snapshot from '@ember-data/store/-private/snapshot'; -import type SnapshotRecordArray from '@ember-data/store/-private/snapshot-record-array'; +import type ShimModelClass from '@ember-data/store/-private/legacy-model-support/shim-model-class'; +import type Snapshot from '@ember-data/store/-private/network/snapshot'; +import type SnapshotRecordArray from '@ember-data/store/-private/network/snapshot-record-array'; import type { AdapterPayload } from '@ember-data/types/q/minimum-adapter-interface'; import type { Dict } from '@ember-data/types/q/utils'; @@ -295,7 +295,7 @@ declare const jQuery: JQueryStatic | undefined; export default class ApplicationAdapter extends RESTAdapter { headers: computed('session.authToken', function() { return { - 'API_KEY': this.get('session.authToken'), + 'API_KEY': this.session.authToken, 'ANOTHER_HEADER': 'Some header value' }; }) @@ -311,13 +311,12 @@ declare const jQuery: JQueryStatic | undefined; ```app/adapters/application.js import RESTAdapter from '@ember-data/adapter/rest'; - import { get } from '@ember/object'; import { computed } from '@ember/object'; export default class ApplicationAdapter extends RESTAdapter { headers: computed(function() { return { - 'API_KEY': get(document.cookie.match(/apiKey\=([^;]*)/), '1'), + 'API_KEY': document.cookie.match(/apiKey\=([^;]*)/)['1'], 'ANOTHER_HEADER': 'Some header value' }; }).volatile() @@ -438,7 +437,7 @@ class RESTAdapter extends Adapter.extend(BuildURLMixin) { } ``` - By default calling `post.get('comments')` will trigger the following requests(assuming the + By default calling `post.comments` will trigger the following requests(assuming the comments haven't been loaded before): ``` diff --git a/packages/debug/addon/index.js b/packages/debug/addon/index.js index a8a99fdd748..d259f1f563a 100644 --- a/packages/debug/addon/index.js +++ b/packages/debug/addon/index.js @@ -26,7 +26,6 @@ import { A } from '@ember/array'; import { assert } from '@ember/debug'; import DataAdapter from '@ember/debug/data-adapter'; -import { get } from '@ember/object'; import { addObserver, removeObserver } from '@ember/object/observers'; import { inject as service } from '@ember/service'; import { capitalize, underscore } from '@ember/string'; @@ -64,7 +63,7 @@ export default DataAdapter.extend({ }, _nameToClass(type) { - return get(this, 'store').modelFor(type); + return this.store.modelFor(type); }, /** @@ -80,8 +79,8 @@ export default DataAdapter.extend({ @return {Function} Method to call to remove all observers */ watchModelTypes(typesAdded, typesUpdated) { - const store = get(this, 'store'); - const __createRecordData = store._instanceCache._createRecordData; + const { store } = this; + const __getRecordData = store._instanceCache.getRecordData; const _releaseMethods = []; const discoveredTypes = typesMapFor(store); @@ -91,15 +90,15 @@ export default DataAdapter.extend({ }); // Overwrite _createRecordData so newly added models will get added to the list - store._instanceCache._createRecordData = (identifier) => { + store._instanceCache.getRecordData = (identifier) => { // defer to ensure first-create does not result in an infinite loop, see https://github.com/emberjs/data/issues/8006 next(() => this.watchTypeIfUnseen(store, discoveredTypes, identifier.type, typesAdded, typesUpdated, _releaseMethods)); - return __createRecordData.call(store._instanceCache, identifier); + return __getRecordData.call(store._instanceCache, identifier); }; let release = () => { _releaseMethods.forEach((fn) => fn()); - store._instanceCache._createRecordData = __createRecordData; + store._instanceCache.getRecordData = __getRecordData; // reset the list so the models can be added if the inspector is re-opened // the entries are set to false instead of removed, since the models still exist in the app // we just need the inspector to become aware of them @@ -166,7 +165,7 @@ export default DataAdapter.extend({ ]; let count = 0; let self = this; - get(typeClass, 'attributes').forEach((meta, name) => { + typeClass.attributes.forEach((meta, name) => { if (count++ > self.attributeLimit) { return false; } @@ -199,7 +198,7 @@ export default DataAdapter.extend({ } } assert('Cannot find model name. Please upgrade to Ember.js >= 1.13 for Ember Inspector support', !!modelName); - return this.get('store').peekAll(modelName); + return this.store.peekAll(modelName); }, /** @@ -213,13 +212,13 @@ export default DataAdapter.extend({ */ getRecordColumnValues(record) { let count = 0; - let columnValues = { id: get(record, 'id') }; + let columnValues = { id: record.id }; record.eachAttribute((key) => { if (count++ > this.attributeLimit) { return false; } - columnValues[key] = get(record, key); + columnValues[key] = record[key]; }); return columnValues; }, @@ -236,7 +235,7 @@ export default DataAdapter.extend({ let keywords = []; let keys = A(['id']); record.eachAttribute((key) => keys.push(key)); - keys.forEach((key) => keywords.push(get(record, key))); + keys.forEach((key) => keywords.push(record[key])); return keywords; }, @@ -251,9 +250,9 @@ export default DataAdapter.extend({ */ getRecordFilterValues(record) { return { - isNew: record.get('isNew'), - isModified: record.get('hasDirtyAttributes') && !record.get('isNew'), - isClean: !record.get('hasDirtyAttributes'), + isNew: record.isNew, + isModified: record.hasDirtyAttributes && !record.isNew, + isClean: !record.hasDirtyAttributes, }; }, @@ -268,9 +267,9 @@ export default DataAdapter.extend({ */ getRecordColor(record) { let color = 'black'; - if (record.get('isNew')) { + if (record.isNew) { color = 'green'; - } else if (record.get('hasDirtyAttributes')) { + } else if (record.hasDirtyAttributes) { color = 'blue'; } return color; diff --git a/packages/model/addon/-private/attr.js b/packages/model/addon/-private/attr.js index acfeb3ce57f..cba8de9a1f4 100644 --- a/packages/model/addon/-private/attr.js +++ b/packages/model/addon/-private/attr.js @@ -130,13 +130,24 @@ function attr(type, options) { return computed({ get(key) { if (DEBUG) { - if (['_internalModel', 'currentState'].indexOf(key) !== -1) { + if (['currentState'].indexOf(key) !== -1) { throw new Error( `'${key}' is a reserved property name on instances of classes extending Model. Please choose a different property name for your attr on ${this.constructor.toString()}` ); } } + if (this.isDestroyed || this.isDestroying) { + return; + } let recordData = recordDataFor(this); + // TODO hasAttr is not spec'd + // essentially this is needed because + // there is a difference between "undefined" meaning never set + // and "undefined" meaning set to "undefined". In the "key present" + // case we want to return undefined. In the "key absent" case + // we want to return getDefaultValue. RecordDataV2 can fix this + // by providing the attributes blob such that we can make our + // own determination. if (recordData.hasAttr(key)) { return recordData.getAttr(key); } else { @@ -145,7 +156,7 @@ function attr(type, options) { }, set(key, value) { if (DEBUG) { - if (['_internalModel', 'currentState'].indexOf(key) !== -1) { + if (['currentState'].indexOf(key) !== -1) { throw new Error( `'${key}' is a reserved property name on instances of classes extending Model. Please choose a different property name for your attr on ${this.constructor.toString()}` ); diff --git a/packages/model/addon/-private/belongs-to.js b/packages/model/addon/-private/belongs-to.js index c501ad3cd89..3e2e4c32c5f 100644 --- a/packages/model/addon/-private/belongs-to.js +++ b/packages/model/addon/-private/belongs-to.js @@ -98,7 +98,7 @@ import { computedMacroWithOptionalParams } from './util'; a related resource is known to exist and it has not been loaded. ``` - let post = comment.get('post'); + let post = comment.post; ``` @@ -143,10 +143,16 @@ function belongsTo(modelName, options) { return computed({ get(key) { + // this is a legacy behavior we may not carry into a new model setup + // it's better to error on disconnected records so users find errors + // in their logic. + if (this.isDestroying || this.isDestroyed) { + return null; + } const support = LEGACY_SUPPORT.lookup(this); if (DEBUG) { - if (['_internalModel', 'recordData', 'currentState'].indexOf(key) !== -1) { + if (['currentState'].indexOf(key) !== -1) { throw new Error( `'${key}' is a reserved property name on instances of classes extending Model. Please choose a different property name for your belongsTo on ${this.constructor.toString()}` ); @@ -177,7 +183,7 @@ function belongsTo(modelName, options) { set(key, value) { const support = LEGACY_SUPPORT.lookup(this); if (DEBUG) { - if (['_internalModel', 'recordData', 'currentState'].indexOf(key) !== -1) { + if (['currentState'].indexOf(key) !== -1) { throw new Error( `'${key}' is a reserved property name on instances of classes extending Model. Please choose a different property name for your belongsTo on ${this.constructor.toString()}` ); diff --git a/packages/model/addon/-private/errors.ts b/packages/model/addon/-private/errors.ts index 6ac3f451c3a..4b2d9ad056d 100644 --- a/packages/model/addon/-private/errors.ts +++ b/packages/model/addon/-private/errors.ts @@ -123,7 +123,7 @@ export default class Errors extends ArrayProxyWithCustomOverrides undefined - errors.get('messages') + errors.messages // => [] ``` @method clear @@ -406,7 +406,7 @@ export default class Errors extends ArrayProxyWithCustomOverrides { + post.comments.forEach((comment) => { }); @@ -183,17 +184,20 @@ function hasMany(type, options) { return computed({ get(key) { if (DEBUG) { - if (['_internalModel', 'currentState'].indexOf(key) !== -1) { + if (['currentState'].indexOf(key) !== -1) { throw new Error( `'${key}' is a reserved property name on instances of classes extending Model. Please choose a different property name for your hasMany on ${this.constructor.toString()}` ); } } + if (this.isDestroying || this.isDestroyed) { + return A(); + } return LEGACY_SUPPORT.lookup(this).getHasMany(key); }, set(key, records) { if (DEBUG) { - if (['_internalModel', 'currentState'].indexOf(key) !== -1) { + if (['currentState'].indexOf(key) !== -1) { throw new Error( `'${key}' is a reserved property name on instances of classes extending Model. Please choose a different property name for your hasMany on ${this.constructor.toString()}` ); diff --git a/packages/model/addon/-private/legacy-data-fetch.js b/packages/model/addon/-private/legacy-data-fetch.js index f9eea8f6c45..f47ac0e28ff 100644 --- a/packages/model/addon/-private/legacy-data-fetch.js +++ b/packages/model/addon/-private/legacy-data-fetch.js @@ -8,6 +8,7 @@ import { DEPRECATE_RSVP_PROMISE } from '@ember-data/private-build-infra/deprecat import { iterateData, normalizeResponseHelper } from './legacy-data-utils'; export function _findHasMany(adapter, store, identifier, link, relationship, options) { + const record = store._instanceCache.getRecord(identifier); const snapshot = store._instanceCache.createSnapshot(identifier, options); let modelClass = store.modelFor(relationship.type); let useLink = !link || typeof link === 'string'; @@ -18,7 +19,7 @@ export function _findHasMany(adapter, store, identifier, link, relationship, opt promise = guardDestroyedStore(promise, store, label); promise = promise.then( (adapterPayload) => { - if (!_objectIsAlive(store._instanceCache.getInternalModel(identifier))) { + if (!_objectIsAlive(record)) { if (DEPRECATE_RSVP_PROMISE) { deprecate( `A Promise for fetching ${relationship.type} did not resolve by the time your model was destroyed. This will error in a future release.`, @@ -57,13 +58,14 @@ export function _findHasMany(adapter, store, identifier, link, relationship, opt ); if (DEPRECATE_RSVP_PROMISE) { - promise = _guard(promise, _bind(_objectIsAlive, store._instanceCache.getInternalModel(identifier))); + promise = _guard(promise, _bind(_objectIsAlive, record)); } return promise; } export function _findBelongsTo(store, identifier, link, relationship, options) { + const record = store._instanceCache.getRecord(identifier); let adapter = store.adapterFor(identifier.type); assert(`You tried to load a belongsTo relationship but you have no adapter (for ${identifier.type})`, adapter); @@ -79,11 +81,11 @@ export function _findBelongsTo(store, identifier, link, relationship, options) { let label = `DS: Handle Adapter#findBelongsTo of ${identifier.type} : ${relationship.type}`; promise = guardDestroyedStore(promise, store, label); - promise = _guard(promise, _bind(_objectIsAlive, store._instanceCache.getInternalModel(identifier))); + promise = _guard(promise, _bind(_objectIsAlive, record)); promise = promise.then( (adapterPayload) => { - if (!_objectIsAlive(store._instanceCache.getInternalModel(identifier))) { + if (!_objectIsAlive(record)) { if (DEPRECATE_RSVP_PROMISE) { deprecate( `A Promise for fetching ${relationship.type} did not resolve by the time your model was destroyed. This will error in a future release.`, @@ -123,7 +125,7 @@ export function _findBelongsTo(store, identifier, link, relationship, options) { ); if (DEPRECATE_RSVP_PROMISE) { - promise = _guard(promise, _bind(_objectIsAlive, store._instanceCache.getInternalModel(identifier))); + promise = _guard(promise, _bind(_objectIsAlive, record)); } return promise; @@ -229,8 +231,8 @@ function ensureRelationshipIsSetToParent(payload, parentIdentifier, store, paren } } -function getInverse(store, parentInternalModel, parentRelationship, type) { - return recordDataFindInverseRelationshipInfo(store, parentInternalModel, parentRelationship, type); +function getInverse(store, parentIdentifier, parentRelationship, type) { + return recordDataFindInverseRelationshipInfo(store, parentIdentifier, parentRelationship, type); } function recordDataFindInverseRelationshipInfo(store, parentIdentifier, parentRelationship, type) { diff --git a/packages/model/addon/-private/legacy-data-utils.ts b/packages/model/addon/-private/legacy-data-utils.ts index f2191052546..480bc9aa427 100644 --- a/packages/model/addon/-private/legacy-data-utils.ts +++ b/packages/model/addon/-private/legacy-data-utils.ts @@ -2,7 +2,7 @@ import { assert } from '@ember/debug'; import { DEBUG } from '@glimmer/env'; import type Store from '@ember-data/store'; -import type ShimModelClass from '@ember-data/store/-private/model/shim-model-class'; +import type ShimModelClass from '@ember-data/store/-private/legacy-model-support/shim-model-class'; import type { JsonApiDocument } from '@ember-data/types/q/ember-data-json-api'; import type { StableExistingRecordIdentifier, StableRecordIdentifier } from '@ember-data/types/q/identifier'; import type { AdapterPayload } from '@ember-data/types/q/minimum-adapter-interface'; diff --git a/packages/model/addon/-private/legacy-relationships-support.ts b/packages/model/addon/-private/legacy-relationships-support.ts index 672dbd66331..4e8fcc4e420 100644 --- a/packages/model/addon/-private/legacy-relationships-support.ts +++ b/packages/model/addon/-private/legacy-relationships-support.ts @@ -13,11 +13,10 @@ import type { import type { UpgradedMeta } from '@ember-data/record-data/-private/graph/-edge-definition'; import type { RelationshipState } from '@ember-data/record-data/-private/graph/-state'; import type Store from '@ember-data/store'; -import type { InternalModel } from '@ember-data/store/-private'; import { recordDataFor, recordIdentifierFor, storeFor } from '@ember-data/store/-private'; -import type { IdentifierCache } from '@ember-data/store/-private/identifier-cache'; +import { IdentifierCache } from '@ember-data/store/-private/caches/identifier-cache'; import type { DSModel } from '@ember-data/types/q/ds-model'; -import type { ResourceIdentifierObject } from '@ember-data/types/q/ember-data-json-api'; +import { ResourceIdentifierObject } from '@ember-data/types/q/ember-data-json-api'; import type { StableRecordIdentifier } from '@ember-data/types/q/identifier'; import type { RecordData } from '@ember-data/types/q/record-data'; import type { JsonApiRelationship } from '@ember-data/types/q/record-data-json-api'; @@ -141,7 +140,7 @@ export class LegacySupport { `You looked up the '${key}' relationship on a '${identifier.type}' with id ${ identifier.id || 'null' } but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async (\`belongsTo({ async: true })\`)`, - toReturn === null || !store._instanceCache.getInternalModel(relatedIdentifier).isEmpty + toReturn === null || store._instanceCache.recordIsLoaded(relatedIdentifier, true) ); return toReturn; } @@ -447,7 +446,7 @@ export class LegacySupport { return resolve(null); } - const internalModel = resource.data ? this.store._instanceCache._internalModelForResource(resource.data) : null; + const identifier = resource.data ? this.store.identifierCache.getOrCreateRecordIdentifier(resource.data) : null; let { isStale, hasDematerializedInverse, hasReceivedData, isEmpty, shouldForceReload } = resource._relationship .state as RelationshipState; @@ -458,9 +457,9 @@ export class LegacySupport { resource.links.related && (shouldForceReload || hasDematerializedInverse || isStale || (!allInverseRecordsAreLoaded && !isEmpty)); - if (internalModel) { + if (identifier) { // short circuit if we are already loading - let pendingRequest = this.store._fetchManager.getPendingFetch(internalModel.identifier, options); + let pendingRequest = this.store._fetchManager.getPendingFetch(identifier, options); if (pendingRequest) { return pendingRequest; } @@ -485,22 +484,21 @@ export class LegacySupport { return resolve(null); } - if (!internalModel) { - assert(`No InternalModel found for ${resource.lid}`, internalModel); + if (!identifier) { + assert(`No Information found for ${resource.lid}`, identifier); } - return this.store._instanceCache._fetchDataIfNeededForIdentifier(internalModel.identifier, options); + return this.store._instanceCache._fetchDataIfNeededForIdentifier(identifier, options); } let resourceIsLocal = !localDataIsEmpty && resource.data.id === null; - if (internalModel && resourceIsLocal) { - return resolve(internalModel.identifier); + if (identifier && resourceIsLocal) { + return resolve(identifier); } // fetch by data - if (internalModel && !localDataIsEmpty) { - let identifier = internalModel.identifier; + if (identifier && !localDataIsEmpty) { assertIdentifierHasId(identifier); return this.store._fetchManager.scheduleFetch(identifier, options); @@ -512,10 +510,6 @@ export class LegacySupport { } destroy() { - assert( - 'Cannot destroy an internalModel while its record is materialized', - !this.record || this.record.isDestroyed || this.record.isDestroying - ); this.isDestroying = true; const cache = this._manyArrayCache; @@ -625,7 +619,14 @@ function assertRecordsPassedToHasMany(records: RecordInstance[]) { .map((r) => `${typeof r}`) .join(', ')}`, (function () { - return records.every((record) => Object.prototype.hasOwnProperty.call(record, '_internalModel') === true); + return records.every((record) => { + try { + recordIdentifierFor(record); + return true; + } catch { + return false; + } + }); })() ); } @@ -634,7 +635,10 @@ function extractRecordDatasFromRecords(records: RecordInstance[]): RecordData[] return records.map(extractRecordDataFromRecord) as RecordData[]; } -type PromiseProxyRecord = { then(): void; get(str: 'content'): RecordInstance | null | undefined }; +type PromiseProxyRecord = { + then(): void; + content: RecordInstance | null | undefined; +}; function extractRecordDataFromRecord(recordOrPromiseRecord: PromiseProxyRecord | RecordInstance | null) { if (!recordOrPromiseRecord) { @@ -642,7 +646,7 @@ function extractRecordDataFromRecord(recordOrPromiseRecord: PromiseProxyRecord | } if (isPromiseRecord(recordOrPromiseRecord)) { - let content = recordOrPromiseRecord.get && recordOrPromiseRecord.get('content'); + let content = recordOrPromiseRecord.content; assert( 'You passed in a promise that did not originate from an EmberData relationship. You can only pass promises that come from a belongsTo or hasMany relationship to the get call.', content !== undefined @@ -659,24 +663,15 @@ function isPromiseRecord(record: PromiseProxyRecord | RecordInstance): record is function anyUnloaded(store: Store, relationship: ManyRelationship) { let state = relationship.currentState; + const cache = store._instanceCache; const unloaded = state.find((s) => { - let im = store._instanceCache.getInternalModel(s); - return im._isDematerializing || !im.isLoaded; + let isLoaded = cache.recordIsLoaded(s, true); + return !isLoaded; }); return unloaded || false; } -/** - * Flag indicating whether all inverse records are available - * - * true if the inverse exists and is loaded (not empty) - * true if there is no inverse - * false if the inverse exists and is not loaded (empty) - * - * @internal - * @return {boolean} - */ function areAllInverseRecordsLoaded(store: Store, resource: JsonApiRelationship): boolean { const cache = store.identifierCache; @@ -684,7 +679,7 @@ function areAllInverseRecordsLoaded(store: Store, resource: JsonApiRelationship) // treat as collection // check for unloaded records let hasEmptyRecords = resource.data.reduce((hasEmptyModel, resourceIdentifier) => { - return hasEmptyModel || internalModelForRelatedResource(store, cache, resourceIdentifier).isEmpty; + return hasEmptyModel || isEmpty(store, cache, resourceIdentifier); }, false); return !hasEmptyRecords; @@ -693,17 +688,13 @@ function areAllInverseRecordsLoaded(store: Store, resource: JsonApiRelationship) if (!resource.data) { return true; } else { - const internalModel = internalModelForRelatedResource(store, cache, resource.data); - return !internalModel.isEmpty; + return !isEmpty(store, cache, resource.data); } } } -function internalModelForRelatedResource( - store: Store, - cache: IdentifierCache, - resource: ResourceIdentifierObject -): InternalModel { +function isEmpty(store: Store, cache: IdentifierCache, resource: ResourceIdentifierObject): boolean { const identifier = cache.getOrCreateRecordIdentifier(resource); - return store._instanceCache._internalModelForResource(identifier); + const recordData = store._instanceCache.peek({ identifier, bucket: 'recordData' }); + return !recordData || !!recordData.isEmpty?.(); } diff --git a/packages/model/addon/-private/many-array.ts b/packages/model/addon/-private/many-array.ts index 3b1dc623cd2..877a860cd08 100644 --- a/packages/model/addon/-private/many-array.ts +++ b/packages/model/addon/-private/many-array.ts @@ -10,8 +10,8 @@ import { all } from 'rsvp'; import type Store from '@ember-data/store'; import { PromiseArray, recordDataFor } from '@ember-data/store/-private'; -import type { CreateRecordProperties } from '@ember-data/store/-private/core-store'; -import type ShimModelClass from '@ember-data/store/-private/model/shim-model-class'; +import type ShimModelClass from '@ember-data/store/-private/legacy-model-support/shim-model-class'; +import type { CreateRecordProperties } from '@ember-data/store/-private/store-service'; import type { DSModelSchema } from '@ember-data/types/q/ds-model'; import type { Links, PaginationLinks } from '@ember-data/types/q/ember-data-json-api'; import type { StableRecordIdentifier } from '@ember-data/types/q/identifier'; @@ -306,16 +306,16 @@ export default class ManyArray extends MutableArrayWithObject { + this.#notifications = notifications.subscribe(identity, (identifier, type, key) => { notifyChanges(identifier, type, key, this, store); }); } - willDestroy() { + destroy() { LEGACY_SUPPORT.get(this)?.destroy(); + this.___recordState?.destroy(); + const store = storeFor(this); + const identifier = recordIdentifierFor(this); + store._notificationManager.unsubscribe(this.#notifications); + // Legacy behavior is to notify the relationships on destroy + // such that they "clear". It's uncertain this behavior would + // be good for a new model paradigm, likely cheaper and safer + // to simply not notify, for this reason the store does not itself + // notify individual changes once the delete has been signaled, + // this decision is left to model instances. + notifyChanges(identifier, 'relationships', undefined, this, store); + super.destroy(); } /** @@ -197,10 +208,10 @@ class Model extends EmberObject { ```javascript let record = store.createRecord('model'); - record.get('isLoaded'); // true + record.isLoaded; // true store.findRecord('model', 1).then(function(model) { - model.get('isLoaded'); // true + model.isLoaded; // true }); ``` @@ -224,12 +235,12 @@ class Model extends EmberObject { ```javascript let record = store.createRecord('model'); - record.get('hasDirtyAttributes'); // true + record.hasDirtyAttributes; // true store.findRecord('model', 1).then(function(model) { - model.get('hasDirtyAttributes'); // false + model.hasDirtyAttributes; // false model.set('foo', 'some value'); - model.get('hasDirtyAttributes'); // true + model.hasDirtyAttributes; // true }); ``` @@ -254,11 +265,11 @@ class Model extends EmberObject { ```javascript let record = store.createRecord('model'); - record.get('isSaving'); // false + record.isSaving; // false let promise = record.save(); - record.get('isSaving'); // true + record.isSaving; // true promise.then(function() { - record.get('isSaving'); // false + record.isSaving; // false }); ``` @@ -284,24 +295,24 @@ class Model extends EmberObject { ```javascript let record = store.createRecord('model'); - record.get('isDeleted'); // false + record.isDeleted; // false record.deleteRecord(); // Locally deleted - record.get('isDeleted'); // true - record.get('hasDirtyAttributes'); // true - record.get('isSaving'); // false + record.isDeleted; // true + record.hasDirtyAttributes; // true + record.isSaving; // false // Persisting the deletion let promise = record.save(); - record.get('isDeleted'); // true - record.get('isSaving'); // true + record.isDeleted; // true + record.isSaving; // true // Deletion Persisted promise.then(function() { - record.get('isDeleted'); // true - record.get('isSaving'); // false - record.get('hasDirtyAttributes'); // false + record.isDeleted; // true + record.isSaving; // false + record.hasDirtyAttributes; // false }); ``` @@ -325,10 +336,10 @@ class Model extends EmberObject { ```javascript let record = store.createRecord('model'); - record.get('isNew'); // true + record.isNew; // true record.save().then(function(model) { - model.get('isNew'); // false + model.isNew; // false }); ``` @@ -371,7 +382,7 @@ class Model extends EmberObject { ```javascript let record = store.createRecord('model'); - record.get('dirtyType'); // 'created' + record.dirtyType; // 'created' ``` @property dirtyType @@ -392,10 +403,10 @@ class Model extends EmberObject { Example ```javascript - record.get('isError'); // false + record.isError; // false record.set('foo', 'valid value'); record.save().then(null, function() { - record.get('isError'); // true + record.isError; // true }); ``` @@ -441,10 +452,10 @@ class Model extends EmberObject { ```javascript let record = store.createRecord('model'); - record.get('id'); // null + record.id; // null store.findRecord('model', 1).then(function(model) { - model.get('id'); // '1' + model.id; // '1' }); ``` @@ -454,24 +465,38 @@ class Model extends EmberObject { */ @tagged get id() { - // the _internalModel guard exists, because some dev-only deprecation code + // this guard exists, because some dev-only deprecation code // (addListener via validatePropertyInjections) invokes toString before the // object is real. if (DEBUG) { - if (!this._internalModel) { + try { + return recordIdentifierFor(this).id; + } catch { return void 0; } } - return this._internalModel.id; + return recordIdentifierFor(this).id; } set id(id) { const normalizedId = coerceId(id); + const identifier = recordIdentifierFor(this); + let didChange = normalizedId !== identifier.id; + assert( + `Cannot set ${identifier.type} record's id to ${id}, because id is already ${identifier.id}`, + !didChange || identifier.id === null + ); - if (normalizedId !== null) { - this._internalModel.setId(normalizedId); + if (normalizedId !== null && didChange) { + this.store._instanceCache.setRecordId(identifier.type, normalizedId, identifier.lid); + this.store._notificationManager.notify(identifier, 'identity'); } } + // TODO just write a nice toString + toStringExtension() { + return this.id; + } + /** @property currentState @private @@ -494,12 +519,6 @@ class Model extends EmberObject { throw new Error('cannot set currentState'); } - /** - @property _internalModel - @private - @type {Object} - */ - /** The store service instance which created this record instance @@ -517,10 +536,10 @@ class Model extends EmberObject { - `attribute` The name of the property associated with this error message ```javascript - record.get('errors.length'); // 0 + record.errors.length; // 0 record.set('foo', 'invalid value'); record.save().catch(function() { - record.get('errors').get('foo'); + record.errors.foo; // [{message: 'foo should be a number.', attribute: 'foo'}] }); ``` @@ -794,7 +813,7 @@ class Model extends EmberObject { and value is an [oldProp, newProp] array. */ changedAttributes() { - return this._internalModel.changedAttributes(); + return recordDataFor(this).changedAttributes(); } /** @@ -804,11 +823,11 @@ class Model extends EmberObject { Example ```javascript - record.get('name'); // 'Untitled Document' + record.name; // 'Untitled Document' record.set('name', 'Doc 1'); - record.get('name'); // 'Doc 1' + record.name; // 'Doc 1' record.rollbackAttributes(); - record.get('name'); // 'Untitled Document' + record.name; // 'Untitled Document' ``` @since 1.13.0 @@ -817,8 +836,13 @@ class Model extends EmberObject { */ rollbackAttributes() { const { currentState } = this; - this._internalModel.rollbackAttributes(); + const { isNew } = currentState; + recordDataFor(this).rollbackAttributes(); + this.errors.clear(); currentState.cleanErrorRequests(); + if (isNew) { + this.unloadRecord(); + } } /** @@ -830,13 +854,6 @@ class Model extends EmberObject { return storeFor(this)._instanceCache.createSnapshot(recordIdentifierFor(this)); } - toStringExtension() { - // the _internalModel guard exists, because some dev-only deprecation code - // (addListener via validatePropertyInjections) invokes toString before the - // object is real. - return this._internalModel && this._internalModel.id; - } - /** Save the record and persist any changes to the record to an external source via the adapter. @@ -1491,10 +1508,10 @@ class Model extends EmberObject { import Post from 'app/models/post'; let relationships = Blog.relationships; - relationships.get('user'); + relationships.user; //=> [ { name: 'users', kind: 'hasMany' }, // { name: 'owner', kind: 'belongsTo' } ] - relationships.get('post'); + relationships.post; //=> [ { name: 'posts', kind: 'hasMany' } ] ``` @@ -1707,9 +1724,9 @@ class Model extends EmberObject { import Blog from 'app/models/blog'; let relationshipsByName = Blog.relationshipsByName; - relationshipsByName.get('users'); + relationshipsByName.users; //=> { key: 'users', kind: 'hasMany', type: 'user', options: Object, isRelationship: true } - relationshipsByName.get('owner'); + relationshipsByName.owner; //=> { key: 'owner', kind: 'belongsTo', type: 'user', options: Object, isRelationship: true } ``` @@ -1810,7 +1827,7 @@ class Model extends EmberObject { let fields = Blog.fields; fields.forEach(function(kind, field) { - console.log(field, kind); + // do thing }); // prints: @@ -1992,7 +2009,7 @@ class Model extends EmberObject { let attributes = Person.attributes attributes.forEach(function(meta, name) { - console.log(name, meta); + // do thing }); // prints: @@ -2069,7 +2086,7 @@ class Model extends EmberObject { let transformedAttributes = Person.transformedAttributes transformedAttributes.forEach(function(field, type) { - console.log(field, type); + // do thing }); // prints: @@ -2142,7 +2159,7 @@ class Model extends EmberObject { } PersonModel.eachAttribute(function(name, meta) { - console.log(name, meta); + // do thing }); // prints: @@ -2211,7 +2228,7 @@ class Model extends EmberObject { }); Person.eachTransformedAttribute(function(name, type) { - console.log(name, type); + // do thing }); // prints: @@ -2273,13 +2290,12 @@ class Model extends EmberObject { this.modelName ); } - return `model:${get(this, 'modelName')}`; + return `model:${this.modelName}`; } } // this is required to prevent `init` from passing // the values initialized during create to `setUnknownProperty` -Model.prototype._internalModel = null; Model.prototype._createProps = null; Model.prototype._secretInit = null; @@ -2359,27 +2375,11 @@ if (DEBUG) { } while (current !== null); return null; }; - let isBasicDesc = function isBasicDesc(desc) { - return ( - !desc || - (!desc.get && !desc.set && desc.enumerable === true && desc.writable === true && desc.configurable === true) - ); - }; - let isDefaultEmptyDescriptor = function isDefaultEmptyDescriptor(obj, keyName) { - let instanceDesc = lookupDescriptor(obj, keyName); - return isBasicDesc(instanceDesc) && lookupDescriptor(obj.constructor, keyName) === null; - }; Model.reopen({ init() { this._super(...arguments); - if (!isDefaultEmptyDescriptor(this, '_internalModel') || !(this._internalModel instanceof InternalModel)) { - throw new Error( - `'_internalModel' is a reserved property name on instances of classes extending Model. Please choose a different property name for ${this.constructor.toString()}` - ); - } - let ourDescriptor = lookupDescriptor(Model.prototype, 'currentState'); let theirDescriptor = lookupDescriptor(this, 'currentState'); let realState = this.___recordState; diff --git a/packages/model/addon/-private/notify-changes.ts b/packages/model/addon/-private/notify-changes.ts index 109a6b94825..8044c8f89ab 100644 --- a/packages/model/addon/-private/notify-changes.ts +++ b/packages/model/addon/-private/notify-changes.ts @@ -1,7 +1,7 @@ import { cacheFor } from '@ember/object/internals'; import type Store from '@ember-data/store'; -import type { NotificationType } from '@ember-data/store/-private/record-notification-manager'; +import type { NotificationType } from '@ember-data/store/-private/managers/record-notification-manager'; import type { StableRecordIdentifier } from '@ember-data/types/q/identifier'; import type Model from './model'; diff --git a/packages/model/addon/-private/promise-many-array.ts b/packages/model/addon/-private/promise-many-array.ts index da91a3739b0..ee55d1266cc 100644 --- a/packages/model/addon/-private/promise-many-array.ts +++ b/packages/model/addon/-private/promise-many-array.ts @@ -1,6 +1,6 @@ import ArrayMixin, { NativeArray } from '@ember/array'; import type ArrayProxy from '@ember/array/proxy'; -import { assert } from '@ember/debug'; +import { assert, deprecate } from '@ember/debug'; import { dependentKeyCompat } from '@ember/object/compat'; import { tracked } from '@glimmer/tracking'; import Ember from 'ember'; @@ -9,8 +9,10 @@ import { resolve } from 'rsvp'; import type { ManyArray } from 'ember-data/-private'; -import type { InternalModel } from '@ember-data/store/-private'; +import { DEPRECATE_PROMISE_MANY_ARRAY_BEHAVIORS } from '@ember-data/private-build-infra/deprecations'; +import { StableRecordIdentifier } from '@ember-data/types/q/identifier'; import type { RecordInstance } from '@ember-data/types/q/record-instance'; +import { FindOptions } from '@ember-data/types/q/store'; export interface HasManyProxyCreateArgs { promise: Promise; @@ -24,16 +26,10 @@ export interface HasManyProxyCreateArgs { This class is returned as the result of accessing an async hasMany relationship on an instance of a Model extending from `@ember-data/model`. - A PromiseManyArray is an array-like proxy that also proxies certain method calls - to the underlying ManyArray in addition to being "promisified". + A PromiseManyArray is an iterable proxy that allows templates to consume related + ManyArrays and update once their contents are no longer pending. - Right now we proxy: - - * `reload()` - * `createRecord()` - - This promise-proxy behavior is primarily to ensure that async relationship interact - nicely with templates. In your JS code you should resolve the promise first. + In your JS code you should resolve the promise first. ```js const comments = await post.comments; @@ -42,10 +38,14 @@ export interface HasManyProxyCreateArgs { @class PromiseManyArray @public */ -export default interface PromiseManyArray extends Omit, 'destroy'> {} +export default interface PromiseManyArray extends Omit, 'destroy'> { + createRecord(): RecordInstance; + reload(options: FindOptions): PromiseManyArray; +} export default class PromiseManyArray { declare promise: Promise | null; declare isDestroyed: boolean; + // @deprecated (isDestroyed is not deprecated) declare isDestroying: boolean; constructor(promise: Promise, content?: ManyArray) { @@ -90,6 +90,8 @@ export default class PromiseManyArray { /** * Iterate the proxied content. Called by the glimmer iterator in #each + * We do not guarantee that forEach will always be available. This + * may eventually be made to use Symbol.Iterator once glimmer supports it. * * @method forEach * @param cb @@ -103,6 +105,19 @@ export default class PromiseManyArray { } } + /** + * Reload the relationship + * @method reload + * @public + * @param options + * @returns + */ + reload(options: FindOptions) { + assert('You are trying to reload an async manyArray before it has been created', this.content); + this.content.reload(options); + return this; + } + //---- Properties/Methods from the PromiseProxyMixin that we will keep as our API /** @@ -201,19 +216,6 @@ export default class PromiseManyArray { return this.content ? this.content.meta : undefined; } - /** - * Reload the relationship - * @method reload - * @public - * @param options - * @returns - */ - reload(options) { - assert('You are trying to reload an async manyArray before it has been created', this.content); - this.content.reload(options); - return this; - } - //---- Our own stuff _update(promise: Promise, content?: ManyArray) { @@ -227,22 +229,55 @@ export default class PromiseManyArray { static create({ promise, content }: HasManyProxyCreateArgs): PromiseManyArray { return new this(promise, content); } +} - // Methods on ManyArray which people should resolve the relationship first before calling - createRecord(...args) { +if (DEPRECATE_PROMISE_MANY_ARRAY_BEHAVIORS) { + PromiseManyArray.prototype.createRecord = function createRecord(...args) { + deprecate( + `The createRecord method on ember-data's PromiseManyArray is deprecated. await the promise and work with the ManyArray directly.`, + false, + { + id: 'ember-data:deprecate-promise-many-array-behaviors', + until: '5.0', + since: { enabled: '4.8', available: '4.8' }, + for: 'ember-data', + } + ); assert('You are trying to createRecord on an async manyArray before it has been created', this.content); return this.content.createRecord(...args); - } - - // Properties/Methods on ArrayProxy we should deprecate - - get firstObject() { - return this.content ? this.content.firstObject : undefined; - } + }; - get lastObject() { - return this.content ? this.content.lastObject : undefined; - } + Object.defineProperty(PromiseManyArray.prototype, 'firstObject', { + get() { + deprecate( + `The firstObject property on ember-data's PromiseManyArray is deprecated. await the promise and work with the ManyArray directly.`, + false, + { + id: 'ember-data:deprecate-promise-many-array-behaviors', + until: '5.0', + since: { enabled: '4.8', available: '4.8' }, + for: 'ember-data', + } + ); + return this.content ? this.content.firstObject : undefined; + }, + }); + + Object.defineProperty(PromiseManyArray.prototype, 'lastObject', { + get() { + deprecate( + `The lastObject property on ember-data's PromiseManyArray is deprecated. await the promise and work with the ManyArray directly.`, + false, + { + id: 'ember-data:deprecate-promise-many-array-behaviors', + until: '5.0', + since: { enabled: '4.8', available: '4.8' }, + for: 'ember-data', + } + ); + return this.content ? this.content.lastObject : undefined; + }, + }); } function tapPromise(proxy: PromiseManyArray, promise: Promise) { @@ -268,78 +303,101 @@ function tapPromise(proxy: PromiseManyArray, promise: Promise) { ); } -const EmberObjectMethods = [ - 'addObserver', - 'cacheFor', - 'decrementProperty', - 'get', - 'getProperties', - 'incrementProperty', - 'notifyPropertyChange', - 'removeObserver', - 'set', - 'setProperties', - 'toggleProperty', -]; -EmberObjectMethods.forEach((method) => { - PromiseManyArray.prototype[method] = function delegatedMethod(...args) { - return Ember[method](this, ...args); - }; -}); - -const InheritedProxyMethods = [ - 'addArrayObserver', - 'addObject', - 'addObjects', - 'any', - 'arrayContentDidChange', - 'arrayContentWillChange', - 'clear', - 'compact', - 'every', - 'filter', - 'filterBy', - 'find', - 'findBy', - 'getEach', - 'includes', - 'indexOf', - 'insertAt', - 'invoke', - 'isAny', - 'isEvery', - 'lastIndexOf', - 'map', - 'mapBy', - 'objectAt', - 'objectsAt', - 'popObject', - 'pushObject', - 'pushObjects', - 'reduce', - 'reject', - 'rejectBy', - 'removeArrayObserver', - 'removeAt', - 'removeObject', - 'removeObjects', - 'replace', - 'reverseObjects', - 'setEach', - 'setObjects', - 'shiftObject', - 'slice', - 'sortBy', - 'toArray', - 'uniq', - 'uniqBy', - 'unshiftObject', - 'unshiftObjects', - 'without', -]; -InheritedProxyMethods.forEach((method) => { - PromiseManyArray.prototype[method] = function proxiedMethod(...args) { - assert(`Cannot call ${method} before content is assigned.`, this.content); - return this.content[method](...args); - }; -}); +if (DEPRECATE_PROMISE_MANY_ARRAY_BEHAVIORS) { + const EmberObjectMethods = [ + 'addObserver', + 'cacheFor', + 'decrementProperty', + 'get', + 'getProperties', + 'incrementProperty', + 'notifyPropertyChange', + 'removeObserver', + 'set', + 'setProperties', + 'toggleProperty', + ]; + EmberObjectMethods.forEach((method) => { + PromiseManyArray.prototype[method] = function delegatedMethod(...args) { + deprecate( + `The ${method} method on ember-data's PromiseManyArray is deprecated. await the promise and work with the ManyArray directly.`, + false, + { + id: 'ember-data:deprecate-promise-many-array-behaviors', + until: '5.0', + since: { enabled: '4.8', available: '4.8' }, + for: 'ember-data', + } + ); + return Ember[method](this, ...args); + }; + }); + + const InheritedProxyMethods = [ + 'addArrayObserver', + 'addObject', + 'addObjects', + 'any', + 'arrayContentDidChange', + 'arrayContentWillChange', + 'clear', + 'compact', + 'every', + 'filter', + 'filterBy', + 'find', + 'findBy', + 'getEach', + 'includes', + 'indexOf', + 'insertAt', + 'invoke', + 'isAny', + 'isEvery', + 'lastIndexOf', + 'map', + 'mapBy', + // TODO update RFC to note objectAt was deprecated (forEach was left for iteration) + 'objectAt', + 'objectsAt', + 'popObject', + 'pushObject', + 'pushObjects', + 'reduce', + 'reject', + 'rejectBy', + 'removeArrayObserver', + 'removeAt', + 'removeObject', + 'removeObjects', + 'replace', + 'reverseObjects', + 'setEach', + 'setObjects', + 'shiftObject', + 'slice', + 'sortBy', + 'toArray', + 'uniq', + 'uniqBy', + 'unshiftObject', + 'unshiftObjects', + 'without', + ]; + InheritedProxyMethods.forEach((method) => { + PromiseManyArray.prototype[method] = function proxiedMethod(...args) { + deprecate( + `The ${method} method on ember-data's PromiseManyArray is deprecated. await the promise and work with the ManyArray directly.`, + false, + { + id: 'ember-data:deprecate-promise-many-array-behaviors', + until: '5.0', + since: { enabled: '4.8', available: '4.8' }, + for: 'ember-data', + } + ); + assert(`Cannot call ${method} before content is assigned.`, this.content); + return this.content[method](...args); + }; + }); +} diff --git a/packages/model/addon/-private/record-state.ts b/packages/model/addon/-private/record-state.ts index 2c520c0caa6..58bbd711d13 100644 --- a/packages/model/addon/-private/record-state.ts +++ b/packages/model/addon/-private/record-state.ts @@ -6,8 +6,8 @@ import { cached, tracked } from '@glimmer/tracking'; import type Store from '@ember-data/store'; import { storeFor } from '@ember-data/store'; import { recordIdentifierFor } from '@ember-data/store/-private'; -import type { NotificationType } from '@ember-data/store/-private/record-notification-manager'; -import type RequestCache from '@ember-data/store/-private/request-cache'; +import type { NotificationType } from '@ember-data/store/-private/managers/record-notification-manager'; +import type RequestCache from '@ember-data/store/-private/network/request-cache'; import type { StableRecordIdentifier } from '@ember-data/types/q/identifier'; import type { RecordData } from '@ember-data/types/q/record-data'; @@ -104,8 +104,9 @@ export function tagged(_target, key, desc) { } /** -Historically InternalModel managed a state machine -the currentState for which was reflected onto Model. +Historically EmberData managed a state machine +for each record, the currentState for which +was reflected onto Model. This implements the flags and stateName for backwards compat with the state tree that used to be possible (listed below). @@ -153,6 +154,7 @@ export default class RecordState { declare recordData: RecordData; declare _errorRequests: any[]; declare _lastError: any; + declare handler: object; constructor(record: Model) { const store = storeFor(record)!; @@ -232,27 +234,34 @@ export default class RecordState { } } - notifications.subscribe(identity, (identifier: StableRecordIdentifier, type: NotificationType, key?: string) => { - switch (type) { - case 'state': - this.notify('isNew'); - this.notify('isDeleted'); - this.notify('isDirty'); - break; - case 'attributes': - this.notify('isEmpty'); - this.notify('isDirty'); - break; - case 'unload': - this.notify('isNew'); - this.notify('isDeleted'); - break; - case 'errors': - this.updateInvalidErrors(this.record.errors); - this.notify('isValid'); - break; + this.handler = notifications.subscribe( + identity, + (identifier: StableRecordIdentifier, type: NotificationType, key?: string) => { + switch (type) { + case 'state': + this.notify('isNew'); + this.notify('isDeleted'); + this.notify('isDirty'); + break; + case 'attributes': + this.notify('isEmpty'); + this.notify('isDirty'); + break; + case 'unload': + this.notify('isNew'); + this.notify('isDeleted'); + break; + case 'errors': + this.updateInvalidErrors(this.record.errors); + this.notify('isValid'); + break; + } } - }); + ); + } + + destroy() { + storeFor(this.record)!._notificationManager.unsubscribe(this.handler); } notify(key) { @@ -304,7 +313,6 @@ export default class RecordState { return !this.isLoaded && this.pendingCount > 0 && this.fulfilledCount === 0; } - // TODO @runspired handle "unloadRecord" see note in InternalModel @tagged get isLoaded() { if (this.isNew) { diff --git a/packages/model/addon/-private/references/belongs-to.ts b/packages/model/addon/-private/references/belongs-to.ts index e2762b0587e..9b0827e1db6 100644 --- a/packages/model/addon/-private/references/belongs-to.ts +++ b/packages/model/addon/-private/references/belongs-to.ts @@ -8,8 +8,8 @@ import type { BelongsToRelationship } from '@ember-data/record-data/-private'; import type Store from '@ember-data/store'; import { assertPolymorphicType } from '@ember-data/store/-debug'; import { recordIdentifierFor } from '@ember-data/store/-private'; -import type { NotificationType } from '@ember-data/store/-private/record-notification-manager'; -import type { DebugWeakCache } from '@ember-data/store/-private/weak-cache'; +import type { NotificationType } from '@ember-data/store/-private/managers/record-notification-manager'; +import type { DebugWeakCache } from '@ember-data/store/-private/utils/weak-cache'; import type { LinkObject, Links, @@ -125,7 +125,7 @@ export default class BelongsToReference { `type()` and `id()` methods form a composite key for the identity map. This can be used to access the id of an async relationship without triggering a fetch that would normally happen if you - attempted to use `record.get('relationship.id')`. + attempted to use `record.relationship.id`. Example @@ -277,7 +277,7 @@ export default class BelongsToReference { } _resource() { - return this.store._instanceCache.recordDataFor(this.#identifier, false).getBelongsTo(this.key); + return this.store._instanceCache.getRecordData(this.#identifier).getBelongsTo(this.key); } /** @@ -404,7 +404,7 @@ export default class BelongsToReference { /** `value()` synchronously returns the current value of the belongs-to - relationship. Unlike `record.get('relationshipName')`, calling + relationship. Unlike `record.relationshipName`, calling `value()` on a reference does not trigger a fetch if the async relationship is not yet loaded. If the relationship is not loaded it will always return `null`. diff --git a/packages/model/addon/-private/references/has-many.ts b/packages/model/addon/-private/references/has-many.ts index 62833eb020b..fdb3cc55e21 100644 --- a/packages/model/addon/-private/references/has-many.ts +++ b/packages/model/addon/-private/references/has-many.ts @@ -11,8 +11,8 @@ import type { ManyRelationship } from '@ember-data/record-data/-private'; import type Store from '@ember-data/store'; import { recordIdentifierFor } from '@ember-data/store'; import { assertPolymorphicType } from '@ember-data/store/-debug'; -import type { NotificationType } from '@ember-data/store/-private/record-notification-manager'; -import type { DebugWeakCache } from '@ember-data/store/-private/weak-cache'; +import type { NotificationType } from '@ember-data/store/-private/managers/record-notification-manager'; +import type { DebugWeakCache } from '@ember-data/store/-private/utils/weak-cache'; import type { CollectionResourceDocument, CollectionResourceRelationship, @@ -131,7 +131,7 @@ export default class HasManyReference { } _resource() { - return this.store._instanceCache.recordDataFor(this.#identifier, false).getHasMany(this.key); + return this.store._instanceCache.getRecordData(this.#identifier).getHasMany(this.key); } /** @@ -435,16 +435,14 @@ export default class HasManyReference { let members = this.hasManyRelationship.currentState; - //TODO @runspired determine isLoaded via a better means return members.every((identifier) => { - let internalModel = this.store._instanceCache._internalModelForResource(identifier); - return internalModel.isLoaded === true; + return this.store._instanceCache.recordIsLoaded(identifier, true) === true; }); } /** `value()` synchronously returns the current value of the has-many - relationship. Unlike `record.get('relationshipName')`, calling + relationship. Unlike `record.relationshipName`, calling `value()` on a reference does not trigger a fetch if the async relationship is not yet loaded. If the relationship is not loaded it will always return `null`. @@ -474,7 +472,7 @@ export default class HasManyReference { let commentsRef = post.hasMany('comments'); - post.get('comments').then(function(comments) { + post.comments.then(function(comments) { commentsRef.value() === comments }) ``` diff --git a/packages/private-build-infra/addon/current-deprecations.ts b/packages/private-build-infra/addon/current-deprecations.ts index f8b014e3a38..df8eea70cb6 100644 --- a/packages/private-build-infra/addon/current-deprecations.ts +++ b/packages/private-build-infra/addon/current-deprecations.ts @@ -44,11 +44,11 @@ export default { DEPRECATE_SNAPSHOT_MODEL_CLASS_ACCESS: '4.5', DEPRECATE_STORE_FIND: '4.5', DEPRECATE_HAS_RECORD: '4.5', - DEPRECATE_RECORD_WAS_INVALID: '4.5', DEPRECATE_STRING_ARG_SCHEMAS: '4.5', DEPRECATE_JSON_API_FALLBACK: '4.5', DEPRECATE_MODEL_REOPEN: '4.8', DEPRECATE_EARLY_STATIC: '4.8', DEPRECATE_CLASSIC: '4.9', DEPRECATE_HELPERS: '4.8', + DEPRECATE_PROMISE_MANY_ARRAY_BEHAVIORS: '4.8', }; diff --git a/packages/private-build-infra/addon/debugging.ts b/packages/private-build-infra/addon/debugging.ts new file mode 100644 index 00000000000..7b53cbaf3d8 --- /dev/null +++ b/packages/private-build-infra/addon/debugging.ts @@ -0,0 +1,5 @@ +export const LOG_NOTIFICATIONS = false; +export const LOG_REQUEST_STATUS = false; +export const LOG_IDENTIFIERS = false; +export const LOG_GRAPH = false; +export const LOG_INSTANCE_CACHE = false; diff --git a/packages/private-build-infra/addon/deprecations.ts b/packages/private-build-infra/addon/deprecations.ts index 2a21099af5c..6291518a758 100644 --- a/packages/private-build-infra/addon/deprecations.ts +++ b/packages/private-build-infra/addon/deprecations.ts @@ -13,10 +13,10 @@ export const DEPRECATE_RSVP_PROMISE = deprecationState('DEPRECATE_RSVP_PROMISE') export const DEPRECATE_SNAPSHOT_MODEL_CLASS_ACCESS = deprecationState('DEPRECATE_SNAPSHOT_MODEL_CLASS_ACCESS'); export const DEPRECATE_STORE_FIND = deprecationState('DEPRECATE_STORE_FIND'); export const DEPRECATE_HAS_RECORD = deprecationState('DEPRECATE_HAS_RECORD'); -export const DEPRECATE_RECORD_WAS_INVALID = deprecationState('DEPRECATE_RECORD_WAS_INVALID'); export const DEPRECATE_STRING_ARG_SCHEMAS = deprecationState('DEPRECATE_STRING_ARG_SCHEMAS'); export const DEPRECATE_JSON_API_FALLBACK = deprecationState('DEPRECATE_JSON_API_FALLBACK'); export const DEPRECATE_MODEL_REOPEN = deprecationState('DEPRECATE_MODEL_REOPEN'); export const DEPRECATE_EARLY_STATIC = deprecationState('DEPRECATE_EARLY_STATIC'); export const DEPRECATE_CLASSIC = deprecationState('DEPRECATE_CLASSIC'); export const DEPRECATE_HELPERS = deprecationState('DEPRECATE_HELPERS'); +export const DEPRECATE_PROMISE_MANY_ARRAY_BEHAVIORS = deprecationState('DEPRECATE_PROMISE_MANY_ARRAY_BEHAVIORS'); diff --git a/packages/private-build-infra/src/addon-build-config-for-data-package.js b/packages/private-build-infra/src/addon-build-config-for-data-package.js index 49b4c33ecf1..5c3409412f2 100644 --- a/packages/private-build-infra/src/addon-build-config-for-data-package.js +++ b/packages/private-build-infra/src/addon-build-config-for-data-package.js @@ -109,9 +109,9 @@ function addonBuildConfigForDataPackage(PackageName) { buildBabelOptions() { let babelOptions = this.options.babel || {}; let existingPlugins = babelOptions.plugins || []; - let compatVersion = this.getEmberDataConfig().compatWith || null; + let config = this.getEmberDataConfig(); - let customPlugins = require('./stripped-build-plugins')(process.env.EMBER_ENV, this._findHost(), compatVersion); + let customPlugins = require('./stripped-build-plugins')(process.env.EMBER_ENV, this._findHost(), config); let plugins = existingPlugins.map((plugin) => { return Array.isArray(plugin) ? plugin : [plugin]; }); @@ -211,8 +211,20 @@ function addonBuildConfigForDataPackage(PackageName) { let options = (app.options = app.options || {}); options.emberData = options.emberData || {}; - - return options.emberData; + options.emberData.debug = options.emberData.debug || {}; + const debugOptions = Object.assign( + { + LOG_NOTIFICATIONS: false, + LOG_REQUEST_STATUS: false, + LOG_IDENTIFIERS: false, + LOG_GRAPH: false, + LOG_INSTANCE_CACHE: false, + }, + options.emberData.debug + ); + options.emberData.debug = debugOptions; + + return Object.assign({ compatWith: null }, options.emberData); }, }; } diff --git a/packages/private-build-infra/src/debug-macros.js b/packages/private-build-infra/src/debug-macros.js index 9ad89421fcf..64631887822 100644 --- a/packages/private-build-infra/src/debug-macros.js +++ b/packages/private-build-infra/src/debug-macros.js @@ -1,9 +1,10 @@ 'use strict'; -module.exports = function debugMacros(app, isProd, compatVersion) { +module.exports = function debugMacros(app, isProd, config) { const PACKAGES = require('./packages')(app); const FEATURES = require('./features')(isProd); - const DEPRECATIONS = require('./deprecations')(compatVersion, isProd); + const DEBUG = require('./debugging')(config.debug, isProd); + const DEPRECATIONS = require('./deprecations')(config.compatWith, isProd); const debugMacrosPath = require.resolve('babel-plugin-debug-macros'); let plugins = [ [ @@ -42,6 +43,18 @@ module.exports = function debugMacros(app, isProd, compatVersion) { }, '@ember-data/deprecation-stripping', ], + [ + debugMacrosPath, + { + flags: [ + { + source: '@ember-data/private-build-infra/debugging', + flags: DEBUG, + }, + ], + }, + '@ember-data/debugging', + ], ]; return plugins; diff --git a/packages/private-build-infra/src/debugging.js b/packages/private-build-infra/src/debugging.js new file mode 100644 index 00000000000..a7823f80cec --- /dev/null +++ b/packages/private-build-infra/src/debugging.js @@ -0,0 +1,16 @@ +'use strict'; + +const requireModule = require('./utilities/require-module'); + +function getDebugFeatures(debugConfig, isProd) { + const { default: DEBUG_FEATURES } = requireModule('@ember-data/private-build-infra/addon/debugging.ts'); + const flags = {}; + + Object.keys(DEBUG_FEATURES).forEach((flag) => { + flags[flag] = isProd ? false : debugConfig[flag] || DEBUG_FEATURES[flag]; + }); + + return flags; +} + +module.exports = getDebugFeatures; diff --git a/packages/private-build-infra/src/stripped-build-plugins.js b/packages/private-build-infra/src/stripped-build-plugins.js index fcb0890bc4c..5e19ef5496f 100644 --- a/packages/private-build-infra/src/stripped-build-plugins.js +++ b/packages/private-build-infra/src/stripped-build-plugins.js @@ -7,10 +7,10 @@ function isProduction(environment) { return /production/.test(environment); } -module.exports = function (environment, app, compatVersion) { +module.exports = function (environment, app, config) { const isProd = isProduction(environment); let plugins = []; - const DebugMacros = require('./debug-macros')(app, isProd, compatVersion); + const DebugMacros = require('./debug-macros')(app, isProd, config); let postTransformPlugins = []; if (isProd) { diff --git a/packages/private-build-infra/src/utilities/require-module.js b/packages/private-build-infra/src/utilities/require-module.js index 5865d7240be..80164eb811f 100644 --- a/packages/private-build-infra/src/utilities/require-module.js +++ b/packages/private-build-infra/src/utilities/require-module.js @@ -3,7 +3,14 @@ const fs = require('node:fs'); module.exports = function requireModule(modulePath) { const path = require.resolve(modulePath); const fileContents = fs.readFileSync(path, { encoding: 'utf8' }); - const newContents = fileContents.replace('export default ', 'return '); + let newContents; + + if (fileContents.includes('export default')) { + newContents = fileContents.replace('export default ', 'return '); + } else { + newContents = replaceAll(fileContents, 'export const ', 'module.exports.'); + newContents = `const module = { exports: {} };\n${newContents}\nreturn module.exports;`; + } try { const func = new Function(newContents); return { default: func() }; @@ -12,3 +19,10 @@ module.exports = function requireModule(modulePath) { console.log(e); } }; + +function replaceAll(str, pattern, replacement) { + if (str.replaceAll) { + return str.replaceAll(pattern, replacement); + } + return str.replace(new RegExp(pattern, 'g'), replacement); +} diff --git a/packages/record-data/addon/-private/coerce-id.ts b/packages/record-data/addon/-private/coerce-id.ts index e9f341d774a..405d647bd64 100644 --- a/packages/record-data/addon/-private/coerce-id.ts +++ b/packages/record-data/addon/-private/coerce-id.ts @@ -8,7 +8,7 @@ import { DEBUG } from '@glimmer/env'; // corresponding record, we will not know if it is a string or a number. type Coercable = string | number | boolean | null | undefined | symbol; -function coerceId(id: Coercable): string | null { +export function coerceId(id: Coercable): string | null { if (id === null || id === undefined || id === '') { return null; } @@ -35,5 +35,3 @@ export function ensureStringId(id: Coercable): string { return normalized!; } - -export default coerceId; diff --git a/packages/record-data/addon/-private/graph/-utils.ts b/packages/record-data/addon/-private/graph/-utils.ts index 713b7ed913a..3033bf15b19 100644 --- a/packages/record-data/addon/-private/graph/-utils.ts +++ b/packages/record-data/addon/-private/graph/-utils.ts @@ -1,11 +1,12 @@ import { assert, inspect, warn } from '@ember/debug'; -import { coerceId, recordDataFor as peekRecordData } from '@ember-data/store/-private'; +import { recordDataFor as peekRecordData } from '@ember-data/store/-private'; import type { StableRecordIdentifier } from '@ember-data/types/q/identifier'; import type { RecordData } from '@ember-data/types/q/record-data'; import type { RelationshipRecordData } from '@ember-data/types/q/relationship-record-data'; import type { Dict } from '@ember-data/types/q/utils'; +import { coerceId } from '../coerce-id'; import type BelongsToRelationship from '../relationships/state/belongs-to'; import type ManyRelationship from '../relationships/state/has-many'; import type ImplicitRelationship from '../relationships/state/implicit'; diff --git a/packages/record-data/addon/-private/graph/index.ts b/packages/record-data/addon/-private/graph/index.ts index 66b2f8f596f..d0780707b66 100644 --- a/packages/record-data/addon/-private/graph/index.ts +++ b/packages/record-data/addon/-private/graph/index.ts @@ -1,6 +1,7 @@ import { assert } from '@ember/debug'; import { DEBUG } from '@glimmer/env'; +import { LOG_GRAPH } from '@ember-data/private-build-infra/debugging'; import type Store from '@ember-data/store'; import type { RecordDataStoreWrapper } from '@ember-data/store/-private'; import { WeakCache } from '@ember-data/store/-private'; @@ -50,6 +51,7 @@ function getWrapper(store: RecordDataStoreWrapper | Store): RecordDataStoreWrapp export function peekGraph(store: RecordDataStoreWrapper | Store): Graph | undefined { return Graphs.get(getWrapper(store)); } +export type peekGraph = typeof peekGraph; export function graphFor(store: RecordDataStoreWrapper | Store): Graph { return Graphs.lookup(getWrapper(store)); @@ -59,7 +61,7 @@ export function graphFor(store: RecordDataStoreWrapper | Store): Graph { * Graph acts as the cache for relationship data. It allows for * us to ask about and update relationships for a given Identifier * without requiring other objects for that Identifier to be - * instantiated (such as `InternalModel`, `RecordData` or a `Record`) + * instantiated (such as `RecordData` or a `Record`) * * This also allows for us to make more substantive changes to relationships * with increasingly minor alterations to other portions of the internals @@ -88,6 +90,7 @@ export class Graph { }; declare _updatedRelationships: Set; declare _transaction: Set | null; + declare _removing: StableRecordIdentifier | null; constructor(store: RecordDataStoreWrapper) { this._definitionCache = Object.create(null); @@ -99,6 +102,7 @@ export class Graph { this._pushedUpdates = { belongsTo: [], hasMany: [], deletions: [] }; this._updatedRelationships = new Set(); this._transaction = null; + this._removing = null; } has(identifier: StableRecordIdentifier, propertyName: string): boolean { @@ -207,6 +211,10 @@ export class Graph { } unload(identifier: StableRecordIdentifier) { + if (LOG_GRAPH) { + // eslint-disable-next-line no-console + console.log(`graph: unload ${String(identifier)}`); + } const relationships = this.identifiers.get(identifier); if (relationships) { @@ -223,14 +231,25 @@ export class Graph { } remove(identifier: StableRecordIdentifier) { + if (LOG_GRAPH) { + // eslint-disable-next-line no-console + console.log(`graph: remove ${String(identifier)}`); + } + assert(`Cannot remove ${String(identifier)} while still removing ${String(this._removing)}`, !this._removing); + this._removing = identifier; this.unload(identifier); this.identifiers.delete(identifier); + this._removing = null; } /* * Remote state changes */ push(op: RemoteRelationshipOperation) { + if (LOG_GRAPH) { + // eslint-disable-next-line no-console + console.log(`graph: push ${String(op.record)}`, op); + } if (op.op === 'deleteRecord') { this._pushedUpdates.deletions.push(op); } else if (op.op === 'replaceRelatedRecord') { @@ -260,6 +279,10 @@ export class Graph { `Cannot update an implicit relationship`, op.op === 'deleteRecord' || !isImplicit(this.get(op.record, op.field)) ); + if (LOG_GRAPH) { + // eslint-disable-next-line no-console + console.log(`graph: update (${isRemote ? 'remote' : 'local'}) ${String(op.record)}`, op); + } switch (op.op) { case 'updateRelationship': @@ -317,6 +340,10 @@ export class Graph { if (!this._willSyncRemote) { return; } + if (LOG_GRAPH) { + // eslint-disable-next-line no-console + console.groupCollapsed(`Graph: Initialized Transaction`); + } this._transaction = new Set(); this._willSyncRemote = false; const { deletions, hasMany, belongsTo } = this._pushedUpdates; @@ -340,6 +367,10 @@ export class Graph { _addToTransaction(relationship: ManyRelationship | BelongsToRelationship) { assert(`expected a transaction`, this._transaction !== null); + if (LOG_GRAPH) { + // eslint-disable-next-line no-console + console.log(`Graph: ${relationship.identifier} ${relationship.definition.key} added to transaction`); + } relationship.transactionRef++; this._transaction.add(relationship); } @@ -348,6 +379,12 @@ export class Graph { if (this._transaction) { this._transaction.forEach((v) => (v.transactionRef = 0)); this._transaction = null; + if (LOG_GRAPH) { + // eslint-disable-next-line no-console + console.log(`Graph: transaction finalized`); + // eslint-disable-next-line no-console + console.groupEnd(); + } } } @@ -382,9 +419,7 @@ export class Graph { // If the inverse is sync, unloading this record is treated as a client-side // delete, so we remove the inverse records from this relationship to // disconnect the graph. Because it's not async, we don't need to keep around -// the internalModel as an id-wrapper for references and because the graph is -// disconnected we can actually destroy the internalModel when checking for -// orphaned models. +// the identifier as an id-wrapper for references function destroyRelationship(rel) { if (isImplicit(rel)) { if (rel.graph.isReleasable(rel.identifier)) { @@ -400,9 +435,9 @@ function destroyRelationship(rel) { rel.clear(); // necessary to clear relationships in the ui from dematerialized records - // hasMany is managed by InternalModel which calls `retreiveLatest` after + // hasMany is managed by Model which calls `retreiveLatest` after // dematerializing the recordData instance. - // but sync belongsTo require this since they don't have a proxy to update. + // but sync belongsTo requires this since they don't have a proxy to update. // so we have to notify so it will "update" to null. // we should discuss whether we still care about this, probably fine to just // leave the ui relationship populated since the record is destroyed and diff --git a/packages/record-data/addon/-private/record-data.ts b/packages/record-data/addon/-private/record-data.ts index df5d1daec31..b9e639a9688 100644 --- a/packages/record-data/addon/-private/record-data.ts +++ b/packages/record-data/addon/-private/record-data.ts @@ -1,12 +1,10 @@ /** * @module @ember-data/record-data */ -import { assert } from '@ember/debug'; -import { _backburner as emberBackburner } from '@ember/runloop'; import { isEqual } from '@ember/utils'; import type { RecordDataStoreWrapper } from '@ember-data/store/-private'; -import { recordDataFor, recordIdentifierFor, removeRecordDataFor } from '@ember-data/store/-private'; +import { recordIdentifierFor } from '@ember-data/store/-private'; import type { CollectionResourceRelationship } from '@ember-data/types/q/ember-data-json-api'; import type { RecordIdentifier, StableRecordIdentifier } from '@ember-data/types/q/identifier'; import type { ChangedAttributesHash, RecordData } from '@ember-data/types/q/record-data'; @@ -16,14 +14,11 @@ import type { RelationshipRecordData, } from '@ember-data/types/q/relationship-record-data'; -import coerceId from './coerce-id'; import { isImplicit } from './graph/-utils'; import { graphFor } from './graph/index'; import type BelongsToRelationship from './relationships/state/belongs-to'; import type ManyRelationship from './relationships/state/has-many'; -let nextBfsId = 1; - const EMPTY_ITERATOR = { iterator() { return { @@ -45,36 +40,33 @@ const EMPTY_ITERATOR = { export default class RecordDataDefault implements RelationshipRecordData { declare _errors?: JsonApiValidationError[]; declare modelName: string; - declare clientId: string; + declare lid: string; declare identifier: StableRecordIdentifier; - declare id: string | null; declare isDestroyed: boolean; declare _isNew: boolean; - declare _bfsId: number; declare __attributes: any; declare __inFlightAttributes: any; declare __data: any; - declare _scheduledDestroy: any; declare _isDeleted: boolean; declare _isDeletionCommited: boolean; declare storeWrapper: RecordDataStoreWrapper; constructor(identifier: RecordIdentifier, storeWrapper: RecordDataStoreWrapper) { this.modelName = identifier.type; - this.clientId = identifier.lid; - this.id = identifier.id; + this.lid = identifier.lid; this.identifier = identifier; this.storeWrapper = storeWrapper; this.isDestroyed = false; this._isNew = false; this._isDeleted = false; - // Used during the mark phase of unloading to avoid checking the same internal - // model twice in the same scan - this._bfsId = 0; this.reset(); } + get id() { + return this.identifier.id; + } + // PUBLIC API getResourceIdentifier(): StableRecordIdentifier { return this.identifier; @@ -104,12 +96,6 @@ export default class RecordDataDefault implements RelationshipRecordData { this._setupRelationships(data); } - if (data.id) { - if (!this.id) { - this.id = coerceId(data.id); - } - } - if (changedKeys && changedKeys.length) { this._notifyAttributes(changedKeys); } @@ -129,7 +115,7 @@ export default class RecordDataDefault implements RelationshipRecordData { _clearErrors() { if (this._errors) { this._errors = undefined; - this.storeWrapper.notifyErrorsChange(this.modelName, this.id, this.clientId); + this.storeWrapper.notifyErrorsChange(this.modelName, this.id, this.lid); } } @@ -283,6 +269,10 @@ export default class RecordDataDefault implements RelationshipRecordData { this._clearErrors(); this.notifyStateChange(); + if (dirtyKeys && dirtyKeys.length) { + this._notifyAttributes(dirtyKeys); + } + return dirtyKeys; } @@ -301,8 +291,7 @@ export default class RecordDataDefault implements RelationshipRecordData { if (data) { if (data.id) { // didCommit provided an ID, notify the store of it - this.storeWrapper.setRecordId(this.modelName, data.id, this.clientId); - this.id = coerceId(data.id); + this.storeWrapper.setRecordId(this.modelName, data.id, this.lid); } if (data.relationships) { this._setupRelationships(data); @@ -324,7 +313,7 @@ export default class RecordDataDefault implements RelationshipRecordData { } notifyStateChange() { - this.storeWrapper.notifyStateChange(this.modelName, this.id, this.clientId); + this.storeWrapper.notifyStateChange(this.modelName, this.id, this.lid); } // get ResourceIdentifiers for "current state" @@ -377,7 +366,7 @@ export default class RecordDataDefault implements RelationshipRecordData { if (errors) { this._errors = errors; } - this.storeWrapper.notifyErrorsChange(this.modelName, this.id, this.clientId); + this.storeWrapper.notifyErrorsChange(this.modelName, this.id, this.lid); } getBelongsTo(key: string): DefaultSingleResourceRelationship { @@ -412,13 +401,6 @@ export default class RecordDataDefault implements RelationshipRecordData { } } - // internal set coming from the model - __setId(id: string) { - if (this.id !== id) { - this.id = id; - } - } - getAttr(key: string): string { if (key in this._attributes) { return this._attributes[key]; @@ -437,42 +419,29 @@ export default class RecordDataDefault implements RelationshipRecordData { if (this.isDestroyed) { return; } - graphFor(this.storeWrapper).unload(this.identifier); + const { storeWrapper } = this; + graphFor(storeWrapper).unload(this.identifier); this.reset(); - if (!this._scheduledDestroy) { - this._scheduledDestroy = emberBackburner.schedule('destroy', this, '_cleanupOrphanedRecordDatas'); - } - } - _cleanupOrphanedRecordDatas() { - let relatedRecordDatas = this._allRelatedRecordDatas(); - if (areAllModelsUnloaded(relatedRecordDatas)) { + let relatedIdentifiers = this._allRelatedRecordDatas(); + if (areAllModelsUnloaded(this.storeWrapper, relatedIdentifiers)) { // we don't have a backburner queue yet since // we scheduled this into ember's destroy // disconnectRecord called from destroy will teardown // relationships. We do this to queue that. this.storeWrapper._store._backburner.join(() => { - for (let i = 0; i < relatedRecordDatas.length; ++i) { - let recordData = relatedRecordDatas[i]; - if (!recordData.isDestroyed) { - // TODO @runspired we do not currently destroy RecordData instances *except* via this relationship - // traversal. This seems like an oversight since the store should be able to notify destroy. - removeRecordDataFor(recordData.identifier); - recordData.destroy(); - } + for (let i = 0; i < relatedIdentifiers.length; ++i) { + let identifier = relatedIdentifiers[i]; + storeWrapper.disconnectRecord(identifier.type, identifier.id, identifier.lid); } }); } - this._scheduledDestroy = null; - } - destroy() { this.isDestroyed = true; - this.storeWrapper.disconnectRecord(this.modelName, this.id, this.clientId); } isRecordInUse() { - return this.storeWrapper.isRecordInUse(this.modelName, this.id, this.clientId); + return this.storeWrapper.isRecordInUse(this.modelName, this.id, this.lid); } /* @@ -505,7 +474,7 @@ export default class RecordDataDefault implements RelationshipRecordData { while (k < members.length) { let member = members[k++]; if (member !== null) { - return recordDataFor(member); + return member; } } k = 0; @@ -530,34 +499,31 @@ export default class RecordDataDefault implements RelationshipRecordData { }; /* - Computes the set of internal models reachable from this internal model. + Computes the set of Identifiers reachable from this Identifier. Reachability is determined over the relationship graph (ie a graph where - nodes are internal models and edges are belongs to or has many + nodes are identifiers and edges are belongs to or has many relationships). - Returns an array including `this` and all internal models reachable - from `this`. + Returns an array including `this` and all identifiers reachable + from `this.identifier`. */ - _allRelatedRecordDatas(): RecordDataDefault[] { - let array: RecordDataDefault[] = []; - let queue: RecordDataDefault[] = []; - let bfsId = nextBfsId++; - queue.push(this); - this._bfsId = bfsId; + _allRelatedRecordDatas(): StableRecordIdentifier[] { + let array: StableRecordIdentifier[] = []; + let queue: StableRecordIdentifier[] = []; + let seen = new Set(); + queue.push(this.identifier); while (queue.length > 0) { - let node = queue.shift() as RecordDataDefault; - array.push(node); + let identifier = queue.shift()!; + array.push(identifier); + seen.add(identifier); const iterator = this._directlyRelatedRecordDatasIterable().iterator(); for (let obj = iterator.next(); !obj.done; obj = iterator.next()) { - const recordData = obj.value; - if (recordData && recordData instanceof RecordDataDefault) { - assert('Internal Error: seen a future bfs iteration', recordData._bfsId <= bfsId); - if (recordData._bfsId < bfsId) { - queue.push(recordData); - recordData._bfsId = bfsId; - } + const identifier = obj.value; + if (identifier && !seen.has(identifier)) { + seen.add(identifier); + queue.push(identifier); } } } @@ -565,20 +531,6 @@ export default class RecordDataDefault implements RelationshipRecordData { return array; } - isAttrDirty(key: string): boolean { - if (this._attributes[key] === undefined) { - return false; - } - let originalValue; - if (this._inFlightAttributes[key] !== undefined) { - originalValue = this._inFlightAttributes[key]; - } else { - originalValue = this._data[key]; - } - - return originalValue !== this._attributes[key]; - } - get _attributes() { if (this.__attributes === null) { this.__attributes = Object.create(null); @@ -637,7 +589,6 @@ export default class RecordDataDefault implements RelationshipRecordData { let propertyValue = options[name]; if (name === 'id') { - this.id = propertyValue; continue; } @@ -671,20 +622,6 @@ export default class RecordDataDefault implements RelationshipRecordData { return createOptions; } - /* - TODO IGOR AND DAVID this shouldn't be public - This method should only be called by records in the `isNew()` state OR once the record - has been deleted and that deletion has been persisted. - - It will remove this record from any associated relationships. - - If `isNew` is true (default false), it will also completely reset all - relationships to an empty state as well. - - @method removeFromInverseRelationships - @param {Boolean} isNew whether to unload from the `isNew` perspective - @private - */ removeFromInverseRelationships() { graphFor(this.storeWrapper).push({ op: 'deleteRecord', @@ -698,7 +635,7 @@ export default class RecordDataDefault implements RelationshipRecordData { } /* - Ember Data has 3 buckets for storing the value of an attribute on an internalModel. + Ember Data has 3 buckets for storing the value of an attribute. `_data` holds all of the attributes that have been acknowledged by a backend via the adapter. When rollbackAttributes is called on a model all @@ -784,9 +721,10 @@ export default class RecordDataDefault implements RelationshipRecordData { } } -function areAllModelsUnloaded(recordDatas) { - for (let i = 0; i < recordDatas.length; ++i) { - if (recordDatas[i].isRecordInUse()) { +function areAllModelsUnloaded(wrapper: RecordDataStoreWrapper, identifiers: StableRecordIdentifier[]): boolean { + for (let i = 0; i < identifiers.length; ++i) { + let identifer = identifiers[i]; + if (wrapper.isRecordInUse(identifer.type, identifer.id, identifer.lid)) { return false; } } diff --git a/packages/record-data/addon/-private/relationships/state/belongs-to.ts b/packages/record-data/addon/-private/relationships/state/belongs-to.ts index 1cc8592637c..672d7f6959b 100644 --- a/packages/record-data/addon/-private/relationships/state/belongs-to.ts +++ b/packages/record-data/addon/-private/relationships/state/belongs-to.ts @@ -1,3 +1,4 @@ +import { LOG_GRAPH } from '@ember-data/private-build-infra/debugging'; import type { RecordDataStoreWrapper } from '@ember-data/store/-private'; import type { Links, Meta, PaginationLinks } from '@ember-data/types/q/ember-data-json-api'; import type { StableRecordIdentifier } from '@ember-data/types/q/identifier'; @@ -149,8 +150,22 @@ export default class BelongsToRelationship { } notifyBelongsToChange() { - let recordData = this.identifier; - this.store.notifyBelongsToChange(recordData.type, recordData.id, recordData.lid, this.definition.key); + const identifier = this.identifier; + if (identifier === this.graph._removing) { + if (LOG_GRAPH) { + // eslint-disable-next-line no-console + console.log( + `Graph: ignoring belongsToChange for removed identifier ${String(identifier)} ${this.definition.key}` + ); + } + return; + } + if (LOG_GRAPH) { + // eslint-disable-next-line no-console + console.log(`Graph: notifying belongsToChange for ${String(identifier)} ${this.definition.key}`); + } + + this.store.notifyBelongsToChange(identifier.type, identifier.id, identifier.lid, this.definition.key); } clear() { diff --git a/packages/record-data/addon/-private/relationships/state/has-many.ts b/packages/record-data/addon/-private/relationships/state/has-many.ts index fabbed87401..05a186e4027 100755 --- a/packages/record-data/addon/-private/relationships/state/has-many.ts +++ b/packages/record-data/addon/-private/relationships/state/has-many.ts @@ -1,5 +1,6 @@ import { assert } from '@ember/debug'; +import { LOG_GRAPH } from '@ember-data/private-build-infra/debugging'; import type { RecordDataStoreWrapper } from '@ember-data/store/-private'; import type { CollectionResourceRelationship, @@ -90,26 +91,26 @@ export default class ManyRelationship { }); } - forAllMembers(callback: (im: StableRecordIdentifier | null) => void) { + forAllMembers(callback: (identifier: StableRecordIdentifier | null) => void) { // ensure we don't walk anything twice if an entry is // in both members and canonicalMembers let seen = Object.create(null); for (let i = 0; i < this.currentState.length; i++) { - const inverseInternalModel = this.currentState[i]; - const id = inverseInternalModel.lid; + const inverseIdentifier = this.currentState[i]; + const id = inverseIdentifier.lid; if (!seen[id]) { seen[id] = true; - callback(inverseInternalModel); + callback(inverseIdentifier); } } for (let i = 0; i < this.canonicalState.length; i++) { - const inverseInternalModel = this.canonicalState[i]; - const id = inverseInternalModel.lid; + const inverseIdentifier = this.canonicalState[i]; + const id = inverseIdentifier.lid; if (!seen[id]) { seen[id] = true; - callback(inverseInternalModel); + callback(inverseIdentifier); } } } @@ -121,14 +122,14 @@ export default class ManyRelationship { this.canonicalState = []; } - inverseDidDematerialize(inverseRecordData: StableRecordIdentifier) { - if (!this.definition.isAsync || (inverseRecordData && isNew(inverseRecordData))) { + inverseDidDematerialize(inverseIdentifier: StableRecordIdentifier) { + if (!this.definition.isAsync || (inverseIdentifier && isNew(inverseIdentifier))) { // unloading inverse of a sync relationship is treated as a client-side // delete, so actually remove the models don't merely invalidate the cp // cache. // if the record being unloaded only exists on the client, we similarly // treat it as a client side delete - this.removeCompletelyFromOwn(inverseRecordData); + this.removeCompletelyFromOwn(inverseIdentifier); } else { this.state.hasDematerializedInverse = true; } @@ -142,16 +143,16 @@ export default class ManyRelationship { This method is useful when either a deletion or a rollback on a new record needs to entirely purge itself from an inverse relationship. */ - removeCompletelyFromOwn(recordData: StableRecordIdentifier) { - this.canonicalMembers.delete(recordData); - this.members.delete(recordData); + removeCompletelyFromOwn(identifier: StableRecordIdentifier) { + this.canonicalMembers.delete(identifier); + this.members.delete(identifier); - const canonicalIndex = this.canonicalState.indexOf(recordData); + const canonicalIndex = this.canonicalState.indexOf(identifier); if (canonicalIndex !== -1) { this.canonicalState.splice(canonicalIndex, 1); } - const currentIndex = this.currentState.indexOf(recordData); + const currentIndex = this.currentState.indexOf(identifier); if (currentIndex !== -1) { this.currentState.splice(currentIndex, 1); // This allows dematerialized inverses to be rematerialized @@ -162,8 +163,21 @@ export default class ManyRelationship { } notifyHasManyChange() { - const { store, identifier: recordData } = this; - store.notifyHasManyChange(recordData.type, recordData.id, recordData.lid, this.definition.key); + const { store, identifier } = this; + if (identifier === this.graph._removing) { + if (LOG_GRAPH) { + // eslint-disable-next-line no-console + console.log( + `Graph: ignoring hasManyChange for removed identifier ${String(identifier)} ${this.definition.key}` + ); + } + return; + } + if (LOG_GRAPH) { + // eslint-disable-next-line no-console + console.log(`Graph: notifying hasManyChange for ${String(identifier)} ${this.definition.key}`); + } + store.notifyHasManyChange(identifier.type, identifier.id, identifier.lid, this.definition.key); } getData(): CollectionResourceRelationship { diff --git a/packages/record-data/ember-cli-build.js b/packages/record-data/ember-cli-build.js index a9115bf0b39..563d528c6e7 100644 --- a/packages/record-data/ember-cli-build.js +++ b/packages/record-data/ember-cli-build.js @@ -12,7 +12,9 @@ module.exports = function (defaults) { babel: { // this ensures that the same build-time code stripping that is done // for library packages is also done for our tests and dummy app - plugins: [...require('@ember-data/private-build-infra/src/debug-macros')(null, isProd, compatWith)], + plugins: [ + ...require('@ember-data/private-build-infra/src/debug-macros')(null, isProd, { compatWith, debug: {} }), + ], }, 'ember-cli-babel': { throwUnlessParallelizable: true, diff --git a/packages/record-data/index.js b/packages/record-data/index.js index 3aa9a41fc1c..bd11f8d898e 100644 --- a/packages/record-data/index.js +++ b/packages/record-data/index.js @@ -21,6 +21,7 @@ module.exports = Object.assign({}, addonBaseConfig, { '@ember-data/store/-private', '@ember-data/store', '@ember-data/canary-features', + '@ember-data/private-build-infra/debugging', ]; }, }); diff --git a/packages/record-data/tests/test-helper.js b/packages/record-data/tests/test-helper.js index 057ae6a25c6..147cf2a3d4d 100644 --- a/packages/record-data/tests/test-helper.js +++ b/packages/record-data/tests/test-helper.js @@ -7,7 +7,6 @@ import RSVP from 'rsvp'; import { start } from 'ember-qunit'; import assertAllDeprecations from '@ember-data/unpublished-test-infra/test-support/assert-all-deprecations'; -import additionalLegacyAsserts from '@ember-data/unpublished-test-infra/test-support/legacy'; import configureAsserts from '@ember-data/unpublished-test-infra/test-support/qunit-asserts'; import customQUnitAdapter from '@ember-data/unpublished-test-infra/test-support/testem/custom-qunit-adapter'; @@ -26,7 +25,6 @@ if (QUnit.urlParams.enableoptionalfeatures) { setup(QUnit.assert); configureAsserts(); -additionalLegacyAsserts(); setApplication(Application.create(config.APP)); diff --git a/packages/serializer/addon/-private/embedded-records-mixin.js b/packages/serializer/addon/-private/embedded-records-mixin.js index afe2bd864e8..47054e9f065 100644 --- a/packages/serializer/addon/-private/embedded-records-mixin.js +++ b/packages/serializer/addon/-private/embedded-records-mixin.js @@ -1,6 +1,5 @@ import { A } from '@ember/array'; import { warn } from '@ember/debug'; -import { get, set } from '@ember/object'; import Mixin from '@ember/object/mixin'; import { camelize } from '@ember/string'; import { typeOf } from '@ember/utils'; @@ -544,7 +543,7 @@ export default Mixin.create({ }, attrsOption(attr) { - let attrs = this.get('attrs'); + let attrs = this.attrs; return attrs && (attrs[camelize(attr)] || attrs[attr]); }, @@ -571,7 +570,7 @@ export default Mixin.create({ @private */ _extractEmbeddedHasMany(store, key, hash, relationshipMeta) { - let relationshipHash = get(hash, `data.relationships.${key}.data`); + let relationshipHash = hash.data?.relationships?.[key]?.data; if (!relationshipHash) { return; @@ -592,7 +591,7 @@ export default Mixin.create({ } let relationship = { data: hasMany }; - set(hash, `data.relationships.${key}`, relationship); + hash.data.relationships[key] = relationship; }, /** @@ -600,7 +599,7 @@ export default Mixin.create({ @private */ _extractEmbeddedBelongsTo(store, key, hash, relationshipMeta) { - let relationshipHash = get(hash, `data.relationships.${key}.data`); + let relationshipHash = hash.data?.relationships?.[key]?.data; if (!relationshipHash) { return; } @@ -615,7 +614,7 @@ export default Mixin.create({ let belongsTo = { id: data.id, type: data.type }; let relationship = { data: belongsTo }; - set(hash, `data.relationships.${key}`, relationship); + hash.data.relationships[key] = relationship; }, /** diff --git a/packages/serializer/addon/-private/index.js b/packages/serializer/addon/-private/index.js index 2cb1bad39f0..cc440faf03e 100644 --- a/packages/serializer/addon/-private/index.js +++ b/packages/serializer/addon/-private/index.js @@ -3,7 +3,6 @@ */ export { default as EmbeddedRecordsMixin } from './embedded-records-mixin'; -export { modelHasAttributeOrRelationshipNamedType } from './utils'; export { default as Transform } from './transforms/transform'; export { default as BooleanTransform } from './transforms/boolean'; diff --git a/packages/serializer/addon/-private/utils.js b/packages/serializer/addon/-private/utils.js deleted file mode 100644 index 182cdb86ddc..00000000000 --- a/packages/serializer/addon/-private/utils.js +++ /dev/null @@ -1,10 +0,0 @@ -import { get } from '@ember/object'; - -/* - Check if the passed model has a `type` attribute or a relationship named `type`. - */ -function modelHasAttributeOrRelationshipNamedType(modelClass) { - return get(modelClass, 'attributes').has('type') || get(modelClass, 'relationshipsByName').has('type'); -} - -export { modelHasAttributeOrRelationshipNamedType }; diff --git a/packages/serializer/addon/json-api.js b/packages/serializer/addon/json-api.js index f503c839b6c..5c225dd6b1d 100644 --- a/packages/serializer/addon/json-api.js +++ b/packages/serializer/addon/json-api.js @@ -710,7 +710,7 @@ const JSONAPISerializer = JSONSerializer.extend({ } // only serialize has many relationships that are not new - let nonNewHasMany = hasMany.filter((item) => item.record && !item.record.get('isNew')); + let nonNewHasMany = hasMany.filter((item) => item.record && !item.record.isNew); let data = new Array(nonNewHasMany.length); for (let i = 0; i < nonNewHasMany.length; i++) { diff --git a/packages/serializer/addon/json.js b/packages/serializer/addon/json.js index aa63ab6c14d..32b79d87960 100644 --- a/packages/serializer/addon/json.js +++ b/packages/serializer/addon/json.js @@ -3,15 +3,12 @@ */ import { getOwner } from '@ember/application'; import { assert, warn } from '@ember/debug'; -import { get } from '@ember/object'; import { dasherize } from '@ember/string'; import { isNone, typeOf } from '@ember/utils'; import Serializer from '@ember-data/serializer'; import { coerceId } from '@ember-data/store/-private'; -import { modelHasAttributeOrRelationshipNamedType } from './-private'; - const SOURCE_POINTER_REGEXP = /^\/?data\/(attributes|relationships)\/(.*)/; const SOURCE_POINTER_PRIMARY_REGEXP = /^\/?data/; const PRIMARY_ATTRIBUTE_KEY = 'base'; @@ -196,7 +193,7 @@ const JSONSerializer = Serializer.extend({ @return {Object} data The transformed data object */ applyTransforms(typeClass, data) { - let attributes = get(typeClass, 'attributes'); + let attributes = typeClass.attributes; typeClass.eachTransformedAttribute((key, typeClass) => { if (data[key] === undefined) { @@ -576,7 +573,7 @@ const JSONSerializer = Serializer.extend({ export default class ApplicationSerializer extends JSONSerializer { normalize(typeClass, hash) { - let fields = get(typeClass, 'fields'); + let fields = typeClass.fields; fields.forEach(function(type, field) { let payloadField = underscore(field); @@ -629,7 +626,7 @@ const JSONSerializer = Serializer.extend({ @return {String} */ extractId(modelClass, resourceHash) { - let primaryKey = get(this, 'primaryKey'); + let primaryKey = this.primaryKey; let id = resourceHash[primaryKey]; return coerceId(id); }, @@ -685,7 +682,7 @@ const JSONSerializer = Serializer.extend({ } let modelClass = this.store.modelFor(relationshipModelName); - if (relationshipHash.type && !modelHasAttributeOrRelationshipNamedType(modelClass)) { + if (relationshipHash.type && !modelClass.fields.has('type')) { relationshipHash.type = this.modelNameFromPayloadKey(relationshipHash.type); } @@ -830,7 +827,7 @@ const JSONSerializer = Serializer.extend({ @private */ normalizeUsingDeclaredMapping(modelClass, hash) { - let attrs = get(this, 'attrs'); + let attrs = this.attrs; let normalizedKey; let payloadKey; @@ -842,11 +839,11 @@ const JSONSerializer = Serializer.extend({ continue; } - if (get(modelClass, 'attributes').has(key)) { + if (modelClass.attributes.has(key)) { normalizedKey = this.keyForAttribute(key, 'deserialize'); } - if (get(modelClass, 'relationshipsByName').has(key)) { + if (modelClass.relationshipsByName.has(key)) { normalizedKey = this.keyForRelationship(key, modelClass, 'deserialize'); } @@ -874,13 +871,13 @@ const JSONSerializer = Serializer.extend({ '` on `' + modelClass.modelName + '`. Check your serializers attrs hash.', - get(modelClass, 'attributes').has(key) || get(modelClass, 'relationshipsByName').has(key), + modelClass.attributes.has(key) || modelClass.relationshipsByName.has(key), { id: 'ds.serializer.no-mapped-attrs-key', } ); - let attrs = get(this, 'attrs'); + let attrs = this.attrs; let mappedKey; if (attrs && attrs[key]) { mappedKey = attrs[key]; @@ -907,7 +904,7 @@ const JSONSerializer = Serializer.extend({ @return {boolean} true if the key can be serialized */ _canSerialize(key) { - let attrs = get(this, 'attrs'); + let attrs = this.attrs; return !attrs || !attrs[key] || attrs[key].serialize !== false; }, @@ -923,7 +920,7 @@ const JSONSerializer = Serializer.extend({ @return {boolean} true if the key must be serialized */ _mustSerialize(key) { - let attrs = get(this, 'attrs'); + let attrs = this.attrs; return attrs && attrs[key] && attrs[key].serialize === true; }, @@ -1110,7 +1107,7 @@ const JSONSerializer = Serializer.extend({ if (options && options.includeId) { const id = snapshot.id; if (id) { - json[get(this, 'primaryKey')] = id; + json[this.primaryKey] = id; } } diff --git a/packages/serializer/addon/rest.js b/packages/serializer/addon/rest.js index e107f9fd7cb..56c1c63a23f 100644 --- a/packages/serializer/addon/rest.js +++ b/packages/serializer/addon/rest.js @@ -11,8 +11,6 @@ import { singularize } from 'ember-inflector'; import JSONSerializer from '@ember-data/serializer/json'; import { coerceId } from '@ember-data/store/-private'; -import { modelHasAttributeOrRelationshipNamedType } from './-private'; - function makeArray(value) { return Array.isArray(value) ? value : [value]; } @@ -198,7 +196,7 @@ const RESTSerializer = JSONSerializer.extend({ let serializer = primarySerializer; let modelClass = primaryModelClass; - let primaryHasTypeAttribute = modelHasAttributeOrRelationshipNamedType(primaryModelClass); + let primaryHasTypeAttribute = primaryModelClass.fields.has('type'); if (!primaryHasTypeAttribute && hash.type) { // Support polymorphic records in async relationships diff --git a/packages/store/addon/-debug/index.js b/packages/store/addon/-debug/index.js index 8bc49a30cd4..c0e32c40e09 100644 --- a/packages/store/addon/-debug/index.js +++ b/packages/store/addon/-debug/index.js @@ -9,7 +9,7 @@ import { DEBUG } from '@glimmer/env'; relationship (specified via `relationshipMeta`) of the `record`. This utility should only be used internally, as both record parameters must - be an InternalModel and the `relationshipMeta` needs to be the meta + be stable record identifiers and the `relationshipMeta` needs to be the meta information about the relationship, retrieved via `record.relationshipFor(key)`. */ diff --git a/packages/store/addon/-private/identifier-cache.ts b/packages/store/addon/-private/caches/identifier-cache.ts similarity index 86% rename from packages/store/addon/-private/identifier-cache.ts rename to packages/store/addon/-private/caches/identifier-cache.ts index d5043a35757..dd205cb64d7 100644 --- a/packages/store/addon/-private/identifier-cache.ts +++ b/packages/store/addon/-private/caches/identifier-cache.ts @@ -4,6 +4,7 @@ import { assert, warn } from '@ember/debug'; import { DEBUG } from '@glimmer/env'; +import { LOG_IDENTIFIERS } from '@ember-data/private-build-infra/debugging'; import type { ExistingResourceObject, ResourceIdentifierObject } from '@ember-data/types/q/ember-data-json-api'; import type { ForgetMethod, @@ -18,11 +19,11 @@ import type { } from '@ember-data/types/q/identifier'; import type { ConfidentDict } from '@ember-data/types/q/utils'; -import coerceId from './coerce-id'; -import { DEBUG_CLIENT_ORIGINATED, DEBUG_IDENTIFIER_BUCKET } from './identifer-debug-consts'; -import normalizeModelName from './normalize-model-name'; -import isNonEmptyString from './utils/is-non-empty-string'; -import WeakCache from './weak-cache'; +import coerceId from '../utils/coerce-id'; +import { DEBUG_CLIENT_ORIGINATED, DEBUG_IDENTIFIER_BUCKET } from '../utils/identifer-debug-consts'; +import isNonEmptyString from '../utils/is-non-empty-string'; +import normalizeModelName from '../utils/normalize-model-name'; +import WeakCache from '../utils/weak-cache'; const IDENTIFIERS = new WeakSet(); @@ -141,8 +142,8 @@ export class IdentifierCache { /** * Internal hook to allow management of merge conflicts with identifiers. * - * we allow late binding of this private internal merge so that `internalModelFactory` - * can insert itself here to handle elimination of duplicates + * we allow late binding of this private internal merge so that + * the cache can insert itself here to handle elimination of duplicates * * @method __configureMerge * @private @@ -172,6 +173,10 @@ export class IdentifierCache { throw new Error(`The supplied identifier ${resource} does not belong to this store instance`); } } + if (LOG_IDENTIFIERS) { + // eslint-disable-next-line no-console + console.log(`Identifiers: Peeked Identifier was already Stable ${String(resource)}`); + } return resource; } @@ -179,9 +184,18 @@ export class IdentifierCache { let identifier: StableRecordIdentifier | undefined = lid !== null ? this._cache.lids[lid] : undefined; if (identifier !== undefined) { + if (LOG_IDENTIFIERS) { + // eslint-disable-next-line no-console + console.log(`Identifiers: cache HIT ${identifier}`, resource); + } return identifier; } + if (LOG_IDENTIFIERS) { + // eslint-disable-next-line no-console + console.groupCollapsed(`Identifiers: ${shouldGenerate ? 'Generating' : 'Peeking'} Identifier`, resource); + } + if (shouldGenerate === false) { if (!('type' in resource) || !('id' in resource) || !resource.type || !resource.id) { return; @@ -211,6 +225,10 @@ export class IdentifierCache { // we have definitely not seen this resource before // so we allow the user configured `GenerationMethod` to tell us let newLid = this._generate(resource, 'record'); + if (LOG_IDENTIFIERS) { + // eslint-disable-next-line no-console + console.log(`Identifiers: lid ${newLid} determined for resource`, resource); + } // we do this _even_ when `lid` is present because secondary lookups // may need to be populated, but we enforce not giving us something @@ -247,6 +265,17 @@ export class IdentifierCache { // TODO exists temporarily to support `peekAll` // but likely to move keyOptions._allIdentifiers.push(identifier); + + if (LOG_IDENTIFIERS && shouldGenerate) { + // eslint-disable-next-line no-console + console.log(`Identifiers: generated ${String(identifier)} for`, resource); + if (resource[DEBUG_IDENTIFIER_BUCKET]) { + // eslint-disable-next-line no-console + console.trace( + `[WARNING] Identifiers: generated a new identifier from a previously used identifier. This is likely a bug.` + ); + } + } } // populate our own secondary lookup table @@ -266,6 +295,15 @@ export class IdentifierCache { } } + if (LOG_IDENTIFIERS) { + if (!identifier && !shouldGenerate) { + // eslint-disable-next-line no-console + console.log(`Identifiers: cache MISS`, resource); + } + // eslint-disable-next-line no-console + console.groupEnd(); + } + return identifier; } @@ -334,6 +372,11 @@ export class IdentifierCache { // TODO move this outta here? keyOptions._allIdentifiers.push(identifier); + if (LOG_IDENTIFIERS) { + // eslint-disable-next-line no-console + console.log(`Identifiers: createded identifier ${String(identifier)} for newly generated resource`, data); + } + return identifier; } @@ -378,7 +421,21 @@ export class IdentifierCache { if (existingIdentifier) { let keyOptions = getTypeIndex(this._cache.types, identifier.type); - identifier = this._mergeRecordIdentifiers(keyOptions, identifier, existingIdentifier, data, newId as string); + let generatedIdentifier = identifier; + identifier = this._mergeRecordIdentifiers( + keyOptions, + generatedIdentifier, + existingIdentifier, + data, + newId as string + ); + if (LOG_IDENTIFIERS) { + // eslint-disable-next-line no-console + console.log( + `Identifiers: merged identifiers ${generatedIdentifier.lid} and ${existingIdentifier.lid} for resource into ${identifier.lid}`, + data + ); + } } let id = identifier.id; @@ -387,12 +444,22 @@ export class IdentifierCache { // add to our own secondary lookup table if (id !== newId && newId !== null) { + if (LOG_IDENTIFIERS) { + // eslint-disable-next-line no-console + console.log( + `Identifiers: updated id for identifier ${identifier.lid} from '${id}' to '${newId}' for resource`, + data + ); + } let keyOptions = getTypeIndex(this._cache.types, identifier.type); keyOptions.id[newId] = identifier; if (id !== null) { delete keyOptions.id[id]; } + } else if (LOG_IDENTIFIERS) { + // eslint-disable-next-line no-console + console.log(`Identifiers: updated identifier ${identifier.lid} resource`, data); } return identifier; @@ -454,6 +521,10 @@ export class IdentifierCache { IDENTIFIERS.delete(identifierObject); this._forget(identifier, 'record'); + if (LOG_IDENTIFIERS) { + // eslint-disable-next-line no-console + console.log(`Identifiers: released identifier ${identifierObject.lid}`); + } } destroy() { @@ -507,6 +578,10 @@ function makeStableRecordIdentifier( let { type, id, lid } = recordIdentifier; return `${clientOriginated ? '[CLIENT_ORIGINATED] ' : ''}${type}:${id} (${lid})`; }, + toJSON() { + let { type, id, lid } = recordIdentifier; + return { type, id, lid }; + }, }; wrapper[DEBUG_CLIENT_ORIGINATED] = clientOriginated; wrapper[DEBUG_IDENTIFIER_BUCKET] = bucket; diff --git a/packages/store/addon/-private/caches/instance-cache.ts b/packages/store/addon/-private/caches/instance-cache.ts new file mode 100644 index 00000000000..a3340537d99 --- /dev/null +++ b/packages/store/addon/-private/caches/instance-cache.ts @@ -0,0 +1,759 @@ +import { assert, warn } from '@ember/debug'; +import { DEBUG } from '@glimmer/env'; + +import { importSync } from '@embroider/macros'; +import { resolve } from 'rsvp'; + +import { HAS_RECORD_DATA_PACKAGE } from '@ember-data/private-build-infra'; +import { LOG_INSTANCE_CACHE } from '@ember-data/private-build-infra/debugging'; +import type { Graph, peekGraph } from '@ember-data/record-data/-private/graph/index'; +import type { ExistingResourceObject, ResourceIdentifierObject } from '@ember-data/types/q/ember-data-json-api'; +import type { + RecordIdentifier, + StableExistingRecordIdentifier, + StableRecordIdentifier, +} from '@ember-data/types/q/identifier'; +import type { RecordData } from '@ember-data/types/q/record-data'; +import { JsonApiRelationship, JsonApiResource } from '@ember-data/types/q/record-data-json-api'; +import { RelationshipSchema } from '@ember-data/types/q/record-data-schemas'; +import type { RecordInstance } from '@ember-data/types/q/record-instance'; +import type { FindOptions } from '@ember-data/types/q/store'; +import { Dict } from '@ember-data/types/q/utils'; + +import RecordReference from '../legacy-model-support/record-reference'; +import RecordDataStoreWrapper from '../managers/record-data-store-wrapper'; +import Snapshot from '../network/snapshot'; +import type { CreateRecordProperties } from '../store-service'; +import type Store from '../store-service'; +import { assertIdentifierHasId } from '../store-service'; +import coerceId, { ensureStringId } from '../utils/coerce-id'; +import constructResource from '../utils/construct-resource'; +import normalizeModelName from '../utils/normalize-model-name'; +import WeakCache, { DebugWeakCache } from '../utils/weak-cache'; +import recordDataFor, { removeRecordDataFor, setRecordDataFor } from './record-data-for'; + +let _peekGraph: peekGraph; +if (HAS_RECORD_DATA_PACKAGE) { + let __peekGraph: peekGraph; + _peekGraph = (wrapper: Store | RecordDataStoreWrapper): Graph | undefined => { + let a = (importSync('@ember-data/record-data/-private') as { peekGraph: peekGraph }).peekGraph; + __peekGraph = __peekGraph || a; + return __peekGraph(wrapper); + }; +} + +/** + @module @ember-data/store +*/ + +const RecordCache = new WeakCache(DEBUG ? 'identifier' : ''); +if (DEBUG) { + RecordCache._expectMsg = (key: RecordInstance | RecordData) => + `${String(key)} is not a record instantiated by @ember-data/store`; +} + +export function peekRecordIdentifier(record: RecordInstance | RecordData): StableRecordIdentifier | undefined { + return RecordCache.get(record); +} + +/** + Retrieves the unique referentially-stable [RecordIdentifier](/ember-data/release/classes/StableRecordIdentifier) + assigned to the given record instance. + ```js + import { recordIdentifierFor } from "@ember-data/store"; + // ... gain access to a record, for instance with peekRecord or findRecord + const record = store.peekRecord("user", "1"); + // get the identifier for the record (see docs for StableRecordIdentifier) + const identifier = recordIdentifierFor(record); + // access the identifier's properties. + const { id, type, lid } = identifier; + ``` + @method recordIdentifierFor + @public + @static + @for @ember-data/store + @param {Object} record a record instance previously obstained from the store. + @returns {StableRecordIdentifier} + */ +export function recordIdentifierFor(record: RecordInstance | RecordData): StableRecordIdentifier { + return RecordCache.getWithError(record); +} + +export function setRecordIdentifier(record: RecordInstance | RecordData, identifier: StableRecordIdentifier): void { + if (DEBUG && RecordCache.has(record) && RecordCache.get(record) !== identifier) { + throw new Error(`${String(record)} was already assigned an identifier`); + } + + /* + It would be nice to do a reverse check here that an identifier has not + previously been assigned a record; however, unload + rematerialization + prevents us from having a great way of doing so when CustomRecordClasses + don't necessarily give us access to a `isDestroyed` for dematerialized + instance. + */ + + RecordCache.set(record, identifier); +} + +export const StoreMap = new WeakCache(DEBUG ? 'store' : ''); + +export function storeFor(record: RecordInstance): Store | undefined { + const store = StoreMap.get(record); + + assert( + `A record in a disconnected state cannot utilize the store. This typically means the record has been destroyed, most commonly by unloading it.`, + store + ); + return store; +} + +type Caches = { + record: WeakMap; + recordData: WeakMap; + reference: DebugWeakCache; +}; + +export class InstanceCache { + declare store: Store; + declare _storeWrapper: RecordDataStoreWrapper; + declare peekList: Dict>; + declare __recordDataFor: (resource: RecordIdentifier) => RecordData; + + #instances: Caches = { + record: new WeakMap(), + recordData: new WeakMap(), + reference: new WeakCache(DEBUG ? 'reference' : ''), + }; + + recordIsLoaded(identifier: StableRecordIdentifier, filterDeleted: boolean = false) { + const recordData = this.peek({ identifier, bucket: 'recordData' }); + if (!recordData) { + return false; + } + const isNew = recordData.isNew?.() || false; + const isEmpty = recordData.isEmpty?.() || false; + + // if we are new we must consider ourselves loaded + if (isNew) { + return true; + } + // even if we have a past request, if we are now empty we are not loaded + // typically this is true after an unloadRecord call + + // if we are not empty, not new && we have a fulfilled request then we are loaded + // we should consider allowing for something to be loaded that is simply "not empty". + // which is how RecordState currently handles this case; however, RecordState is buggy + // in that it does not account for unloading. + // return !isEmpty; + + const req = this.store.getRequestStateService(); + const fulfilled = req.getLastRequestForRecord(identifier); + const isLoading = + fulfilled !== null && req.getPendingRequestsForRecord(identifier).some((req) => req.type === 'query'); + + if (isEmpty || (filterDeleted && recordData.isDeletionCommitted?.()) || isLoading) { + return false; + } + + return true; + } + + constructor(store: Store) { + this.store = store; + this.peekList = Object.create(null) as Dict>; + + this._storeWrapper = new RecordDataStoreWrapper(this.store); + this.__recordDataFor = (resource: RecordIdentifier) => { + const identifier = this.store.identifierCache.getOrCreateRecordIdentifier(resource); + return this.getRecordData(identifier); + }; + + this.#instances.reference._generator = (identifier) => { + return new RecordReference(this.store, identifier); + }; + + store.identifierCache.__configureMerge( + (identifier: StableRecordIdentifier, matchedIdentifier: StableRecordIdentifier, resourceData) => { + let intendedIdentifier = identifier; + if (identifier.id !== matchedIdentifier.id) { + intendedIdentifier = + 'id' in resourceData && identifier.id === resourceData.id ? identifier : matchedIdentifier; + } else if (identifier.type !== matchedIdentifier.type) { + intendedIdentifier = + 'type' in resourceData && identifier.type === resourceData.type ? identifier : matchedIdentifier; + } + let altIdentifier = identifier === intendedIdentifier ? matchedIdentifier : identifier; + + // check for duplicate entities + let imHasRecord = this.#instances.record.has(intendedIdentifier); + let otherHasRecord = this.#instances.record.has(altIdentifier); + let imRecordData = this.#instances.recordData.get(intendedIdentifier) || null; + let otherRecordData = this.#instances.recordData.get(altIdentifier) || null; + + // we cannot merge entities when both have records + // (this may not be strictly true, we could probably swap the recordData the record points at) + if (imHasRecord && otherHasRecord) { + // TODO we probably don't need to throw these errors anymore + // we can probably just "swap" what data source the abandoned + // record points at so long as + // it itself is not retained by the store in any way. + if ('id' in resourceData) { + throw new Error( + `Failed to update the 'id' for the RecordIdentifier '${identifier.type}:${String(identifier.id)} (${ + identifier.lid + })' to '${String(resourceData.id)}', because that id is already in use by '${ + matchedIdentifier.type + }:${String(matchedIdentifier.id)} (${matchedIdentifier.lid})'` + ); + } + // TODO @runspired determine when this is even possible + assert( + `Failed to update the RecordIdentifier '${identifier.type}:${String(identifier.id)} (${ + identifier.lid + })' to merge with the detected duplicate identifier '${matchedIdentifier.type}:${String( + matchedIdentifier.id + )} (${String(matchedIdentifier.lid)})'` + ); + } + + // remove "other" from cache + if (otherHasRecord) { + // TODO probably need to release other things + this.peekList[altIdentifier.type]?.delete(altIdentifier); + } + + if (imRecordData === null && otherRecordData === null) { + // nothing more to do + return intendedIdentifier; + + // only the other has a RecordData + // OR only the other has a Record + } else if ( + (imRecordData === null && otherRecordData !== null) || + (imRecordData && !imHasRecord && otherRecordData && otherHasRecord) + ) { + if (imRecordData) { + // TODO check if we are retained in any async relationships + // TODO probably need to release other things + this.peekList[intendedIdentifier.type]?.delete(intendedIdentifier); + // im.destroy(); + } + imRecordData = otherRecordData!; + // TODO do we need to notify the id change? + // TODO swap recordIdentifierFor result? + this.peekList[intendedIdentifier.type] = this.peekList[intendedIdentifier.type] || new Set(); + this.peekList[intendedIdentifier.type]!.add(intendedIdentifier); + + // just use im + } else { + // otherIm.destroy(); + } + + /* + TODO @runspired consider adding this to make polymorphism even nicer + if (HAS_RECORD_DATA_PACKAGE) { + if (identifier.type !== matchedIdentifier.type) { + const graphFor = importSync('@ember-data/record-data/-private').graphFor; + graphFor(this).registerPolymorphicType(identifier.type, matchedIdentifier.type); + } + } + */ + + return intendedIdentifier; + } + ); + } + peek({ identifier, bucket }: { identifier: StableRecordIdentifier; bucket: 'record' }): RecordInstance | undefined; + peek({ identifier, bucket }: { identifier: StableRecordIdentifier; bucket: 'recordData' }): RecordData | undefined; + peek({ + identifier, + bucket, + }: { + identifier: StableRecordIdentifier; + bucket: 'record' | 'recordData'; + }): RecordData | RecordInstance | undefined { + return this.#instances[bucket]?.get(identifier); + } + + getRecord(identifier: StableRecordIdentifier, properties?: CreateRecordProperties): RecordInstance { + let record = this.peek({ identifier, bucket: 'record' }); + + if (!record) { + const recordData = this.getRecordData(identifier); + const createOptions = recordData._initRecordCreateOptions( + normalizeProperties(this.store, identifier, properties) + ); + record = this.store.instantiateRecord( + identifier, + createOptions, + this.__recordDataFor, + this.store._notificationManager + ); + setRecordIdentifier(record, identifier); + setRecordDataFor(record, recordData); + StoreMap.set(record, this.store); + this.#instances.record.set(identifier, record); + + if (LOG_INSTANCE_CACHE) { + // eslint-disable-next-line no-console + console.log(`InstanceCache: created Record for ${String(identifier)}`, properties); + } + } + + return record; + } + + getRecordData(identifier: StableRecordIdentifier) { + let recordData = this.peek({ identifier, bucket: 'recordData' }); + + if (!recordData) { + recordData = this.store.createRecordDataFor(identifier.type, identifier.id, identifier.lid, this._storeWrapper); + setRecordDataFor(identifier, recordData); + // TODO this is invalid for v2 recordData but required + // for v1 recordData. Remember to remove this once the + // RecordData manager handles converting recordData to identifier + setRecordIdentifier(recordData, identifier); + + this.#instances.recordData.set(identifier, recordData); + this.peekList[identifier.type] = this.peekList[identifier.type] || new Set(); + this.peekList[identifier.type]!.add(identifier); + if (LOG_INSTANCE_CACHE) { + // eslint-disable-next-line no-console + console.log(`InstanceCache: created RecordData for ${String(identifier)}`); + } + } + + return recordData; + } + + getReference(identifier: StableRecordIdentifier) { + return this.#instances.reference.lookup(identifier); + } + + createSnapshot(identifier: StableRecordIdentifier, options: FindOptions = {}): Snapshot { + return new Snapshot(options, identifier, this.store); + } + + disconnect(identifier: StableRecordIdentifier) { + const record = this.#instances.record.get(identifier); + assert( + 'Cannot destroy record while it is still materialized', + !record || record.isDestroyed || record.isDestroying + ); + + if (HAS_RECORD_DATA_PACKAGE) { + let graph = _peekGraph(this.store); + if (graph) { + graph.remove(identifier); + } + } + + this.store.identifierCache.forgetRecordIdentifier(identifier); + if (LOG_INSTANCE_CACHE) { + // eslint-disable-next-line no-console + console.log(`InstanceCache: disconnected ${String(identifier)}`); + } + } + + unloadRecord(identifier: StableRecordIdentifier) { + if (DEBUG) { + const requests = this.store.getRequestStateService().getPendingRequestsForRecord(identifier); + if ( + requests.some((req) => { + return req.type === 'mutation'; + }) + ) { + assert(`You can only unload a record which is not inFlight. '${String(identifier)}'`); + } + } + if (LOG_INSTANCE_CACHE) { + // eslint-disable-next-line no-console + console.groupCollapsed(`InstanceCache: unloading record for ${String(identifier)}`); + } + + // TODO is this join still necessary? + this.store._backburner.join(() => { + const record = this.peek({ identifier, bucket: 'record' }); + const recordData = this.peek({ identifier, bucket: 'recordData' }); + this.peekList[identifier.type]?.delete(identifier); + + if (record) { + this.store.teardownRecord(record); + this.#instances.record.delete(identifier); + StoreMap.delete(record); + RecordCache.delete(record); + removeRecordDataFor(record); + + if (LOG_INSTANCE_CACHE) { + // eslint-disable-next-line no-console + console.log(`InstanceCache: destroyed record for ${String(identifier)}`); + } + } + + if (recordData) { + recordData.unloadRecord(); + this.#instances.recordData.delete(identifier); + removeRecordDataFor(identifier); + RecordCache.delete(recordData); + } else { + this.disconnect(identifier); + } + + this.store._fetchManager.clearEntries(identifier); + this.store.recordArrayManager.recordDidChange(identifier); + if (LOG_INSTANCE_CACHE) { + // eslint-disable-next-line no-console + console.log(`InstanceCache: unloaded RecordData for ${String(identifier)}`); + // eslint-disable-next-line no-console + console.groupEnd(); + } + }); + } + + clear(type?: string) { + if (type === undefined) { + let keys = Object.keys(this.peekList); + keys.forEach((key) => this.clear(key)); + } else { + let identifiers = this.peekList[type]; + if (identifiers) { + identifiers.forEach((identifier) => { + // TODO we rely on not removing the main cache + // and only removing the peekList cache apparently. + // we should figure out this duality and codify whatever + // signal it is actually trying to give us. + // this.cache.delete(identifier); + this.peekList[identifier.type]!.delete(identifier); + this.unloadRecord(identifier); + // TODO we don't remove the identifier, should we? + }); + } + } + } + + // TODO this should move into the network layer + _fetchDataIfNeededForIdentifier( + identifier: StableRecordIdentifier, + options: FindOptions = {} + ): Promise { + // pre-loading will change the isEmpty value + const isEmpty = _isEmpty(this, identifier); + const isLoading = _isLoading(this, identifier); + + if (options.preload) { + this.store._backburner.join(() => { + preloadData(this.store, identifier, options.preload!); + }); + } + + let promise: Promise; + if (isEmpty) { + assertIdentifierHasId(identifier); + + promise = this.store._fetchManager.scheduleFetch(identifier, options); + } else if (isLoading) { + promise = this.store._fetchManager.getPendingFetch(identifier, options)!; + assert(`Expected to find a pending request for a record in the loading state, but found none`, promise); + } else { + promise = resolve(identifier); + } + + return promise; + } + + // TODO this should move into something coordinating operations + setRecordId(modelName: string, id: string, lid: string) { + const type = normalizeModelName(modelName); + const resource = constructResource(type, null, coerceId(lid)); + const identifier = this.store.identifierCache.peekRecordIdentifier(resource); + + if (identifier) { + let oldId = identifier.id; + + // ID absolutely can't be missing if the oldID is empty (missing Id in response for a new record) + assert( + `'${type}' was saved to the server, but the response does not have an id and your record does not either.`, + !(id === null && oldId === null) + ); + + // ID absolutely can't be different than oldID if oldID is not null + // TODO this assertion and restriction may not strictly be needed in the identifiers world + assert( + `Cannot update the id for '${type}:${lid}' from '${String(oldId)}' to '${id}'.`, + !(oldId !== null && id !== oldId) + ); + + // ID can be null if oldID is not null (altered ID in response for a record) + // however, this is more than likely a developer error. + if (oldId !== null && id === null) { + warn( + `Your ${type} record was saved to the server, but the response does not have an id.`, + !(oldId !== null && id === null) + ); + return; + } + + if (LOG_INSTANCE_CACHE) { + // eslint-disable-next-line no-console + console.log(`InstanceCache: updating id to '${id}' for record ${String(identifier)}`); + } + + let existingIdentifier = this.store.identifierCache.peekRecordIdentifier({ type, id }); + assert( + `'${type}' was saved to the server, but the response returned the new id '${id}', which has already been used with another record.'`, + !existingIdentifier || existingIdentifier === identifier + ); + + if (identifier.id === null) { + // TODO potentially this needs to handle merged result + this.store.identifierCache.updateRecordIdentifier(identifier, { type, id }); + } + + // TODO update recordData if needed ? + // TODO handle consequences of identifier merge for notifications + this.store._notificationManager.notify(identifier, 'identity'); + } + } + + // TODO this should move into something coordinating operations + loadData(data: ExistingResourceObject): StableExistingRecordIdentifier { + let modelName = data.type; + assert( + `You must include an 'id' for ${modelName} in an object passed to 'push'`, + data.id !== null && data.id !== undefined && data.id !== '' + ); + assert( + `You tried to push data with a type '${modelName}' but no model could be found with that name.`, + this.store.getSchemaDefinitionService().doesTypeExist(modelName) + ); + + const resource = constructResource(normalizeModelName(data.type), ensureStringId(data.id), coerceId(data.lid)); + let identifier = this.store.identifierCache.peekRecordIdentifier(resource); + let isUpdate = false; + + // store.push will be from empty + // findRecord will be from root.loading + // this cannot be loading state if we do not already have an identifier + // all else will be updates + if (identifier) { + const isLoading = _isLoading(this, identifier) || !this.recordIsLoaded(identifier); + isUpdate = !_isEmpty(this, identifier) && !isLoading; + + // exclude store.push (root.empty) case + if (isUpdate || isLoading) { + identifier = this.store.identifierCache.updateRecordIdentifier(identifier, data); + } + } else { + identifier = this.store.identifierCache.getOrCreateRecordIdentifier(data); + } + + const recordData = this.getRecordData(identifier); + if (recordData.isNew?.()) { + this.store._notificationManager.notify(identifier, 'identity'); + } + + const hasRecord = this.#instances.record.has(identifier); + recordData.pushData(data, hasRecord); + + if (!isUpdate) { + this.store.recordArrayManager.recordDidChange(identifier); + } + + return identifier as StableExistingRecordIdentifier; + } +} + +function normalizeProperties( + store: Store, + identifier: StableRecordIdentifier, + properties?: { [key: string]: unknown } +): { [key: string]: unknown } | undefined { + // assert here + if (properties !== undefined) { + if ('id' in properties) { + assert(`expected id to be a string or null`, properties.id !== undefined); + } + assert( + `You passed '${typeof properties}' as properties for record creation instead of an object.`, + typeof properties === 'object' && properties !== null + ); + + const { type } = identifier; + + // convert relationship Records to RecordDatas before passing to RecordData + let defs = store.getSchemaDefinitionService().relationshipsDefinitionFor({ type }); + + if (defs !== null) { + let keys = Object.keys(properties); + let relationshipValue; + + for (let i = 0; i < keys.length; i++) { + let prop = keys[i]; + let def = defs[prop]; + + if (def !== undefined) { + if (def.kind === 'hasMany') { + if (DEBUG) { + assertRecordsPassedToHasMany(properties[prop] as RecordInstance[]); + } + relationshipValue = extractRecordDatasFromRecords(properties[prop] as RecordInstance[]); + } else { + relationshipValue = extractRecordDataFromRecord(properties[prop] as RecordInstance); + } + + properties[prop] = relationshipValue; + } + } + } + } + return properties; +} + +function _recordDataIsFullDeleted(recordData: RecordData): boolean { + if (recordData.isDeletionCommitted?.() || (recordData.isNew?.() && recordData.isDeleted?.())) { + return true; + } else { + return false; + } +} + +export function recordDataIsFullyDeleted(cache: InstanceCache, identifier: StableRecordIdentifier): boolean { + let recordData = cache.peek({ identifier, bucket: 'recordData' }); + return !recordData || _recordDataIsFullDeleted(recordData); +} + +function assertRecordsPassedToHasMany(records: RecordInstance[]) { + assert(`You must pass an array of records to set a hasMany relationship`, Array.isArray(records)); + assert( + `All elements of a hasMany relationship must be instances of Model, you passed ${records + .map((r) => `${typeof r}`) + .join(', ')}`, + (function () { + return records.every((record) => { + try { + recordIdentifierFor(record); + return true; + } catch { + return false; + } + }); + })() + ); +} + +function extractRecordDatasFromRecords(records: RecordInstance[]): RecordData[] { + return records.map(extractRecordDataFromRecord) as RecordData[]; +} +type PromiseProxyRecord = { then(): void; content: RecordInstance | null | undefined }; + +function extractRecordDataFromRecord(recordOrPromiseRecord: PromiseProxyRecord | RecordInstance | null) { + if (!recordOrPromiseRecord) { + return null; + } + + if (isPromiseRecord(recordOrPromiseRecord)) { + let content = recordOrPromiseRecord.content; + assert( + 'You passed in a promise that did not originate from an EmberData relationship. You can only pass promises that come from a belongsTo or hasMany relationship to the get call.', + content !== undefined + ); + return content ? recordDataFor(content) : null; + } + + return recordDataFor(recordOrPromiseRecord); +} + +function isPromiseRecord(record: PromiseProxyRecord | RecordInstance): record is PromiseProxyRecord { + return !!record.then; +} + +/* + When a find request is triggered on the store, the user can optionally pass in + attributes and relationships to be preloaded. These are meant to behave as if they + came back from the server, except the user obtained them out of band and is informing + the store of their existence. The most common use case is for supporting client side + nested URLs, such as `/posts/1/comments/2` so the user can do + `store.findRecord('comment', 2, { preload: { post: 1 } })` without having to fetch the post. + + Preloaded data can be attributes and relationships passed in either as IDs or as actual + models. + */ +type PreloadRelationshipValue = RecordInstance | string; +function preloadData(store: Store, identifier: StableRecordIdentifier, preload: Dict) { + let jsonPayload: JsonApiResource = {}; + //TODO(Igor) consider the polymorphic case + const schemas = store.getSchemaDefinitionService(); + const relationships = schemas.relationshipsDefinitionFor(identifier); + Object.keys(preload).forEach((key) => { + let preloadValue = preload[key]; + + let relationshipMeta = relationships[key]; + if (relationshipMeta) { + if (!jsonPayload.relationships) { + jsonPayload.relationships = {}; + } + jsonPayload.relationships[key] = preloadRelationship( + relationshipMeta, + preloadValue as PreloadRelationshipValue | null | Array + ); + } else { + if (!jsonPayload.attributes) { + jsonPayload.attributes = {}; + } + jsonPayload.attributes[key] = preloadValue; + } + }); + store._instanceCache.getRecordData(identifier).pushData(jsonPayload); +} + +function preloadRelationship( + schema: RelationshipSchema, + preloadValue: PreloadRelationshipValue | null | Array +): JsonApiRelationship { + const relatedType = schema.type; + + if (schema.kind === 'hasMany') { + assert('You need to pass in an array to set a hasMany property on a record', Array.isArray(preloadValue)); + return { data: preloadValue.map((value) => _convertPreloadRelationshipToJSON(value, relatedType)) }; + } + + assert('You should not pass in an array to set a belongsTo property on a record', !Array.isArray(preloadValue)); + return { data: preloadValue ? _convertPreloadRelationshipToJSON(preloadValue, relatedType) : null }; +} + +/* + findRecord('user', '1', { preload: { friends: ['1'] }}); + findRecord('user', '1', { preload: { friends: [record] }}); +*/ +function _convertPreloadRelationshipToJSON(value: RecordInstance | string, type: string): ResourceIdentifierObject { + if (typeof value === 'string' || typeof value === 'number') { + return { type, id: value }; + } + // TODO if not a record instance assert it's an identifier + // and allow identifiers to be used + return recordIdentifierFor(value); +} + +function _isEmpty(cache: InstanceCache, identifier: StableRecordIdentifier): boolean { + const recordData = cache.peek({ identifier: identifier, bucket: 'recordData' }); + if (!recordData) { + return true; + } + const isNew = recordData.isNew?.() || false; + const isDeleted = recordData.isDeleted?.() || false; + const isEmpty = recordData.isEmpty?.() || false; + + return (!isNew || isDeleted) && isEmpty; +} + +function _isLoading(cache: InstanceCache, identifier: StableRecordIdentifier): boolean { + const req = cache.store.getRequestStateService(); + // const fulfilled = req.getLastRequestForRecord(identifier); + const isLoaded = cache.recordIsLoaded(identifier); + + return ( + !isLoaded && + // fulfilled === null && + req.getPendingRequestsForRecord(identifier).some((req) => req.type === 'query') + ); +} diff --git a/packages/store/addon/-private/record-data-for.ts b/packages/store/addon/-private/caches/record-data-for.ts similarity index 94% rename from packages/store/addon/-private/record-data-for.ts rename to packages/store/addon/-private/caches/record-data-for.ts index 3a93d5495d6..1916c5a2cb5 100644 --- a/packages/store/addon/-private/record-data-for.ts +++ b/packages/store/addon/-private/caches/record-data-for.ts @@ -5,7 +5,7 @@ import type { StableRecordIdentifier } from '@ember-data/types/q/identifier'; import type { RecordData } from '@ember-data/types/q/record-data'; import type { RecordInstance } from '@ember-data/types/q/record-instance'; -import WeakCache from './weak-cache'; +import WeakCache from '../utils/weak-cache'; /* * Returns the RecordData instance associated with a given @@ -24,7 +24,7 @@ export function setRecordDataFor(identifier: StableRecordIdentifier | RecordInst RecordDataForIdentifierCache.set(identifier, recordData); } -export function removeRecordDataFor(identifier: StableRecordIdentifier): void { +export function removeRecordDataFor(identifier: StableRecordIdentifier | RecordInstance): void { RecordDataForIdentifierCache.delete(identifier); } diff --git a/packages/store/addon/-private/identity-map.ts b/packages/store/addon/-private/identity-map.ts deleted file mode 100644 index d14472c0d7a..00000000000 --- a/packages/store/addon/-private/identity-map.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type { ConfidentDict } from '@ember-data/types/q/utils'; - -import InternalModelMap from './internal-model-map'; - -/** - @module @ember-data/store -*/ - -/** - `IdentityMap` is a custom storage map for records by modelName - used by `Store`. - - @class IdentityMap - @internal - */ -export default class IdentityMap { - private _map: ConfidentDict = Object.create(null); - - /** - Retrieves the `InternalModelMap` for a given modelName, - creating one if one did not already exist. This is - similar to `getWithDefault` or `get` on a `MapWithDefault` - - @method retrieve - @internal - @param modelName a previously normalized modelName - @return {InternalModelMap} the InternalModelMap for the given modelName - */ - retrieve(modelName: string): InternalModelMap { - let map = this._map[modelName]; - - if (map === undefined) { - map = this._map[modelName] = new InternalModelMap(modelName); - } - - return map; - } - - /** - Clears the contents of all known `RecordMaps`, but does - not remove the InternalModelMap instances. - - @internal - */ - clear(): void { - let map = this._map; - let keys = Object.keys(map); - - for (let i = 0; i < keys.length; i++) { - let key = keys[i]; - map[key].clear(); - } - } -} diff --git a/packages/store/addon/-private/index.ts b/packages/store/addon/-private/index.ts index c012b97c5e0..096ec00dca1 100644 --- a/packages/store/addon/-private/index.ts +++ b/packages/store/addon/-private/index.ts @@ -6,19 +6,19 @@ import { assert, deprecate } from '@ember/debug'; import { DEPRECATE_HELPERS } from '@ember-data/private-build-infra/deprecations'; -import _normalize from './normalize-model-name'; +import _normalize from './utils/normalize-model-name'; -export { default as Store, storeFor } from './core-store'; +export { default as Store, storeFor } from './store-service'; -export { recordIdentifierFor } from './internal-model-factory'; +export { recordIdentifierFor } from './caches/instance-cache'; -export { default as Snapshot } from './snapshot'; +export { default as Snapshot } from './network/snapshot'; export { setIdentifierGenerationMethod, setIdentifierUpdateMethod, setIdentifierForgetMethod, setIdentifierResetMethod, -} from './identifier-cache'; +} from './caches/identifier-cache'; export function normalizeModelName(modelName: string) { if (DEPRECATE_HELPERS) { @@ -37,23 +37,20 @@ export function normalizeModelName(modelName: string) { assert(`normalizeModelName support has been removed`); } -export { default as coerceId } from './coerce-id'; +export { default as coerceId } from './utils/coerce-id'; -// `ember-data-model-fragments` relies on `InternalModel` -export { default as InternalModel } from './model/internal-model'; - -export { PromiseArray, PromiseObject, deprecatedPromiseObject } from './promise-proxies'; +export { PromiseArray, PromiseObject, deprecatedPromiseObject } from './proxies/promise-proxies'; export { default as RecordArray } from './record-arrays/record-array'; export { default as AdapterPopulatedRecordArray } from './record-arrays/adapter-populated-record-array'; -export { default as RecordArrayManager } from './record-array-manager'; +export { default as RecordArrayManager } from './managers/record-array-manager'; // // Used by tests -export { default as SnapshotRecordArray } from './snapshot-record-array'; +export { default as SnapshotRecordArray } from './network/snapshot-record-array'; // New -export { default as recordDataFor, removeRecordDataFor } from './record-data-for'; -export { default as RecordDataStoreWrapper } from './record-data-store-wrapper'; +export { default as recordDataFor, removeRecordDataFor } from './caches/record-data-for'; +export { default as RecordDataStoreWrapper } from './managers/record-data-store-wrapper'; -export { default as WeakCache } from './weak-cache'; +export { default as WeakCache } from './utils/weak-cache'; diff --git a/packages/store/addon/-private/instance-cache.ts b/packages/store/addon/-private/instance-cache.ts deleted file mode 100644 index 9de6d9e8d88..00000000000 --- a/packages/store/addon/-private/instance-cache.ts +++ /dev/null @@ -1,387 +0,0 @@ -import { assert } from '@ember/debug'; -import { DEBUG } from '@glimmer/env'; - -import { resolve } from 'rsvp'; - -import type { ExistingResourceObject, ResourceIdentifierObject } from '@ember-data/types/q/ember-data-json-api'; -import type { - RecordIdentifier, - StableExistingRecordIdentifier, - StableRecordIdentifier, -} from '@ember-data/types/q/identifier'; -import type { RecordData } from '@ember-data/types/q/record-data'; -import type { RecordInstance } from '@ember-data/types/q/record-instance'; -import type { FindOptions } from '@ember-data/types/q/store'; - -import coerceId, { ensureStringId } from './coerce-id'; -import type { CreateRecordProperties } from './core-store'; -import type Store from './core-store'; -import { assertIdentifierHasId } from './core-store'; -import { internalModelFactoryFor, setRecordIdentifier } from './internal-model-factory'; -import InternalModel from './model/internal-model'; -import RecordReference from './model/record-reference'; -import normalizeModelName from './normalize-model-name'; -import recordDataFor, { setRecordDataFor } from './record-data-for'; -import RecordDataStoreWrapper from './record-data-store-wrapper'; -import Snapshot from './snapshot'; -import constructResource from './utils/construct-resource'; -import WeakCache from './weak-cache'; - -const RECORD_REFERENCES = new WeakCache(DEBUG ? 'reference' : ''); -export const StoreMap = new WeakCache(DEBUG ? 'store' : ''); - -export function storeFor(record: RecordInstance): Store | undefined { - const store = StoreMap.get(record); - - assert( - `A record in a disconnected state cannot utilize the store. This typically means the record has been destroyed, most commonly by unloading it.`, - store - ); - return store; -} - -type Caches = { - record: WeakMap; - recordData: WeakMap; -}; -export class InstanceCache { - declare store: Store; - declare _storeWrapper: RecordDataStoreWrapper; - - #instances: Caches = { - record: new WeakMap(), - recordData: new WeakMap(), - }; - - constructor(store: Store) { - this.store = store; - - this._storeWrapper = new RecordDataStoreWrapper(this.store); - this.__recordDataFor = this.__recordDataFor.bind(this); - - RECORD_REFERENCES._generator = (identifier) => { - return new RecordReference(this.store, identifier); - }; - } - peek({ identifier, bucket }: { identifier: StableRecordIdentifier; bucket: 'record' }): RecordInstance | undefined; - peek({ identifier, bucket }: { identifier: StableRecordIdentifier; bucket: 'recordData' }): RecordData | undefined; - peek({ - identifier, - bucket, - }: { - identifier: StableRecordIdentifier; - bucket: 'record' | 'recordData'; - }): RecordData | RecordInstance | undefined { - return this.#instances[bucket]?.get(identifier); - } - set({ - identifier, - bucket, - value, - }: { - identifier: StableRecordIdentifier; - bucket: 'record'; - value: RecordInstance; - }): void { - this.#instances[bucket].set(identifier, value); - } - - getRecord(identifier: StableRecordIdentifier, properties?: CreateRecordProperties): RecordInstance { - let record = this.peek({ identifier, bucket: 'record' }); - - if (!record) { - // TODO store this state somewhere better - const internalModel = this.getInternalModel(identifier); - - if (internalModel._isDematerializing) { - // TODO this should be an assertion, this likely means - // we have a bug to find wherein our own store is calling this - // with an identifier that should have already been disconnected. - // the destroy + fetch again case is likely either preserving the - // identifier for re-use or failing to unload it. - return null as unknown as RecordInstance; - } - - // TODO store this state somewhere better - internalModel.hasRecord = true; - - if (properties && 'id' in properties) { - assert(`expected id to be a string or null`, properties.id !== undefined); - internalModel.setId(properties.id); - } - - record = this._instantiateRecord(this.getRecordData(identifier), identifier, properties); - this.set({ identifier, bucket: 'record', value: record }); - } - - return record; - } - - getReference(identifier: StableRecordIdentifier) { - return RECORD_REFERENCES.lookup(identifier); - } - - _fetchDataIfNeededForIdentifier( - identifier: StableRecordIdentifier, - options: FindOptions = {} - ): Promise { - const internalModel = this.getInternalModel(identifier); - - // pre-loading will change the isEmpty value - // TODO stpre this state somewhere other than InternalModel - const { isEmpty, isLoading } = internalModel; - - if (options.preload) { - this.store._backburner.join(() => { - internalModel.preloadData(options.preload); - }); - } - - let promise: Promise; - if (isEmpty) { - assertIdentifierHasId(identifier); - - promise = this.store._fetchManager.scheduleFetch(identifier, options); - } else if (isLoading) { - promise = this.store._fetchManager.getPendingFetch(identifier, options)!; - assert(`Expected to find a pending request for a record in the loading state, but found none`, promise); - } else { - promise = resolve(identifier); - } - - return promise; - } - - _instantiateRecord( - recordData: RecordData, - identifier: StableRecordIdentifier, - properties?: { [key: string]: unknown } - ) { - // assert here - if (properties !== undefined) { - assert( - `You passed '${typeof properties}' as properties for record creation instead of an object.`, - typeof properties === 'object' && properties !== null - ); - - const { type } = identifier; - - // convert relationship Records to RecordDatas before passing to RecordData - let defs = this.store.getSchemaDefinitionService().relationshipsDefinitionFor({ type }); - - if (defs !== null) { - let keys = Object.keys(properties); - let relationshipValue; - - for (let i = 0; i < keys.length; i++) { - let prop = keys[i]; - let def = defs[prop]; - - if (def !== undefined) { - if (def.kind === 'hasMany') { - if (DEBUG) { - assertRecordsPassedToHasMany(properties[prop] as RecordInstance[]); - } - relationshipValue = extractRecordDatasFromRecords(properties[prop] as RecordInstance[]); - } else { - relationshipValue = extractRecordDataFromRecord(properties[prop] as RecordInstance); - } - - properties[prop] = relationshipValue; - } - } - } - } - - // TODO guard against initRecordOptions no being there - let createOptions = recordData._initRecordCreateOptions(properties); - //TODO Igor pass a wrapper instead of RD - let record = this.store.instantiateRecord( - identifier, - createOptions, - // eslint-disable-next-line @typescript-eslint/unbound-method - this.__recordDataFor, - this.store._notificationManager - ); - setRecordIdentifier(record, identifier); - setRecordDataFor(record, recordData); - StoreMap.set(record, this.store); - return record; - } - - _teardownRecord(record: RecordInstance) { - StoreMap.delete(record); - // TODO remove identifier:record cache link - this.store.teardownRecord(record); - } - - removeRecord(identifier: StableRecordIdentifier): boolean { - let record = this.peek({ identifier, bucket: 'record' }); - - if (record) { - this.#instances.record.delete(identifier); - this._teardownRecord(record); - } - - return !!record; - } - - // TODO move RecordData Cache into InstanceCache - getRecordData(identifier: StableRecordIdentifier) { - let recordData = this.peek({ identifier, bucket: 'recordData' }); - - if (!recordData) { - recordData = this._createRecordData(identifier); - this.#instances.recordData.set(identifier, recordData); - this.getInternalModel(identifier).hasRecordData = true; - } - - return recordData; - } - - // TODO move InternalModel cache into InstanceCache - getInternalModel(identifier: StableRecordIdentifier) { - return this._internalModelForResource(identifier); - } - - createSnapshot(identifier: StableRecordIdentifier, options: FindOptions = {}): Snapshot { - return new Snapshot(options, identifier, this.store); - } - - __recordDataFor(resource: RecordIdentifier) { - const identifier = this.store.identifierCache.getOrCreateRecordIdentifier(resource); - return this.recordDataFor(identifier, false); - } - - // TODO move this to InstanceCache - _createRecordData(identifier: StableRecordIdentifier): RecordData { - const recordData = this.store.createRecordDataFor( - identifier.type, - identifier.id, - identifier.lid, - this._storeWrapper - ); - setRecordDataFor(identifier, recordData); - // TODO this is invalid for v2 recordData but required - // for v1 recordData. Remember to remove this once the - // RecordData manager handles converting recordData to identifier - setRecordIdentifier(recordData, identifier); - return recordData; - } - - // TODO string candidate for early elimination - _internalModelForResource(resource: ResourceIdentifierObject): InternalModel { - return internalModelFactoryFor(this.store).getByResource(resource); - } - - setRecordId(modelName: string, newId: string, clientId: string) { - internalModelFactoryFor(this.store).setRecordId(modelName, newId, clientId); - } - - _load(data: ExistingResourceObject): StableExistingRecordIdentifier { - // TODO type should be pulled from the identifier for debug - let modelName = data.type; - assert( - `You must include an 'id' for ${modelName} in an object passed to 'push'`, - data.id !== null && data.id !== undefined && data.id !== '' - ); - assert( - `You tried to push data with a type '${modelName}' but no model could be found with that name.`, - this.store.getSchemaDefinitionService().doesTypeExist(modelName) - ); - - // TODO this should determine identifier via the cache before making assumptions - const resource = constructResource(normalizeModelName(data.type), ensureStringId(data.id), coerceId(data.lid)); - const maybeIdentifier = this.store.identifierCache.peekRecordIdentifier(resource); - - let internalModel = internalModelFactoryFor(this.store).lookup(resource, data); - - // store.push will be from empty - // findRecord will be from root.loading - // this cannot be loading state if we do not already have an identifier - // all else will be updates - const isLoading = internalModel.isLoading || (!internalModel.isLoaded && maybeIdentifier); - const isUpdate = internalModel.isEmpty === false && !isLoading; - - // exclude store.push (root.empty) case - let identifier = internalModel.identifier; - if (isUpdate || isLoading) { - let updatedIdentifier = this.store.identifierCache.updateRecordIdentifier(identifier, data); - - if (updatedIdentifier !== identifier) { - // we encountered a merge of identifiers in which - // two identifiers (and likely two internalModels) - // existed for the same resource. Now that we have - // determined the correct identifier to use, make sure - // that we also use the correct internalModel. - identifier = updatedIdentifier; - internalModel = internalModelFactoryFor(this.store).lookup(identifier); - } - } - - internalModel.setupData(data); - - if (!isUpdate) { - this.store.recordArrayManager.recordDidChange(identifier); - } - - return identifier as StableExistingRecordIdentifier; - } - - recordDataFor(identifier: StableRecordIdentifier | { type: string }, isCreate: boolean): RecordData { - let recordData: RecordData; - if (isCreate === true) { - // TODO remove once InternalModel is no longer essential to internal state - // and just build a new identifier directly - let internalModel = internalModelFactoryFor(this.store).build({ type: identifier.type, id: null }); - let stableIdentifier = internalModel.identifier; - recordData = this.getRecordData(stableIdentifier); - recordData.clientDidCreate(); - this.store.recordArrayManager.recordDidChange(stableIdentifier); - } else { - // TODO remove once InternalModel is no longer essential to internal state - internalModelFactoryFor(this.store).lookup(identifier as StableRecordIdentifier); - recordData = this.getRecordData(identifier as StableRecordIdentifier); - } - - return recordData; - } -} - -function assertRecordsPassedToHasMany(records: RecordInstance[]) { - assert(`You must pass an array of records to set a hasMany relationship`, Array.isArray(records)); - assert( - `All elements of a hasMany relationship must be instances of Model, you passed ${records - .map((r) => `${typeof r}`) - .join(', ')}`, - (function () { - return records.every((record) => Object.prototype.hasOwnProperty.call(record, '_internalModel') === true); - })() - ); -} - -function extractRecordDatasFromRecords(records: RecordInstance[]): RecordData[] { - return records.map(extractRecordDataFromRecord) as RecordData[]; -} -type PromiseProxyRecord = { then(): void; get(str: 'content'): RecordInstance | null | undefined }; - -function extractRecordDataFromRecord(recordOrPromiseRecord: PromiseProxyRecord | RecordInstance | null) { - if (!recordOrPromiseRecord) { - return null; - } - - if (isPromiseRecord(recordOrPromiseRecord)) { - let content = recordOrPromiseRecord.get && recordOrPromiseRecord.get('content'); - assert( - 'You passed in a promise that did not originate from an EmberData relationship. You can only pass promises that come from a belongsTo or hasMany relationship to the get call.', - content !== undefined - ); - return content ? recordDataFor(content) : null; - } - - return recordDataFor(recordOrPromiseRecord); -} - -function isPromiseRecord(record: PromiseProxyRecord | RecordInstance): record is PromiseProxyRecord { - return !!record.then; -} diff --git a/packages/store/addon/-private/internal-model-factory.ts b/packages/store/addon/-private/internal-model-factory.ts deleted file mode 100644 index ade102b82e3..00000000000 --- a/packages/store/addon/-private/internal-model-factory.ts +++ /dev/null @@ -1,359 +0,0 @@ -import { assert, warn } from '@ember/debug'; -import { DEBUG } from '@glimmer/env'; - -import type { - ExistingResourceObject, - NewResourceIdentifierObject, - ResourceIdentifierObject, -} from '@ember-data/types/q/ember-data-json-api'; -import type { StableRecordIdentifier } from '@ember-data/types/q/identifier'; -import type { RecordData } from '@ember-data/types/q/record-data'; -import type { RecordInstance } from '@ember-data/types/q/record-instance'; - -import type Store from './core-store'; -import type { IdentifierCache } from './identifier-cache'; -import IdentityMap from './identity-map'; -import type InternalModelMap from './internal-model-map'; -import InternalModel from './model/internal-model'; -import constructResource from './utils/construct-resource'; -import WeakCache from './weak-cache'; - -/** - @module @ember-data/store -*/ -const FactoryCache = new WeakCache(DEBUG ? 'internal-model-factory' : ''); -FactoryCache._generator = (store: Store) => { - return new InternalModelFactory(store); -}; -type NewResourceInfo = { type: string; id: string | null }; - -const RecordCache = new WeakCache(DEBUG ? 'identifier' : ''); -if (DEBUG) { - RecordCache._expectMsg = (key: RecordInstance | RecordData) => - `${String(key)} is not a record instantiated by @ember-data/store`; -} - -export function peekRecordIdentifier(record: RecordInstance | RecordData): StableRecordIdentifier | undefined { - return RecordCache.get(record); -} - -/** - Retrieves the unique referentially-stable [RecordIdentifier](/ember-data/release/classes/StableRecordIdentifier) - assigned to the given record instance. - - ```js - import { recordIdentifierFor } from "@ember-data/store"; - - // ... gain access to a record, for instance with peekRecord or findRecord - const record = store.peekRecord("user", "1"); - - // get the identifier for the record (see docs for StableRecordIdentifier) - const identifier = recordIdentifierFor(record); - - // access the identifier's properties. - const { id, type, lid } = identifier; - ``` - - @method recordIdentifierFor - @public - @static - @for @ember-data/store - @param {Object} record a record instance previously obstained from the store. - @returns {StableRecordIdentifier} - */ -export function recordIdentifierFor(record: RecordInstance | RecordData): StableRecordIdentifier { - return RecordCache.getWithError(record); -} - -export function setRecordIdentifier(record: RecordInstance | RecordData, identifier: StableRecordIdentifier): void { - if (DEBUG && RecordCache.has(record) && RecordCache.get(record) !== identifier) { - throw new Error(`${record} was already assigned an identifier`); - } - - /* - It would be nice to do a reverse check here that an identifier has not - previously been assigned a record; however, unload + rematerialization - prevents us from having a great way of doing so when CustomRecordClasses - don't necessarily give us access to a `isDestroyed` for dematerialized - instance. - */ - - RecordCache.set(record, identifier); -} - -export function internalModelFactoryFor(store: Store): InternalModelFactory { - return FactoryCache.lookup(store); -} - -/** - * The InternalModelFactory handles the lifecyle of - * instantiating, caching, and destroying InternalModel - * instances. - * - * @class InternalModelFactory - * @internal - */ -export default class InternalModelFactory { - declare _identityMap: IdentityMap; - declare identifierCache: IdentifierCache; - declare store: Store; - - constructor(store: Store) { - this.store = store; - this.identifierCache = store.identifierCache; - this.identifierCache.__configureMerge((identifier, matchedIdentifier, resourceData) => { - let intendedIdentifier = identifier; - if (identifier.id !== matchedIdentifier.id) { - intendedIdentifier = 'id' in resourceData && identifier.id === resourceData.id ? identifier : matchedIdentifier; - } else if (identifier.type !== matchedIdentifier.type) { - intendedIdentifier = - 'type' in resourceData && identifier.type === resourceData.type ? identifier : matchedIdentifier; - } - let altIdentifier = identifier === intendedIdentifier ? matchedIdentifier : identifier; - - // check for duplicate InternalModel's - const map = this.modelMapFor(identifier.type); - let im = map.get(intendedIdentifier.lid); - let otherIm = map.get(altIdentifier.lid); - - // we cannot merge internalModels when both have records - // (this may not be strictly true, we could probably swap the internalModel the record points at) - if (im && otherIm && im.hasRecord && otherIm.hasRecord) { - // TODO we probably don't need to throw these errors anymore - // once InternalModel is fully removed, as we can just "swap" - // what data source the abandoned record points at so long as - // it itself is not retained by the store in any way. - if ('id' in resourceData) { - throw new Error( - `Failed to update the 'id' for the RecordIdentifier '${identifier.type}:${identifier.id} (${identifier.lid})' to '${resourceData.id}', because that id is already in use by '${matchedIdentifier.type}:${matchedIdentifier.id} (${matchedIdentifier.lid})'` - ); - } - // TODO @runspired determine when this is even possible - assert( - `Failed to update the RecordIdentifier '${identifier.type}:${identifier.id} (${identifier.lid})' to merge with the detected duplicate identifier '${matchedIdentifier.type}:${matchedIdentifier.id} (${matchedIdentifier.lid})'` - ); - } - - // remove otherIm from cache - if (otherIm) { - map.remove(otherIm, altIdentifier.lid); - } - - if (im === null && otherIm === null) { - // nothing more to do - return intendedIdentifier; - - // only the other has an InternalModel - // OR only the other has a Record - } else if ((im === null && otherIm !== null) || (im && !im.hasRecord && otherIm && otherIm.hasRecord)) { - if (im) { - // TODO check if we are retained in any async relationships - map.remove(im, intendedIdentifier.lid); - // im.destroy(); - } - im = otherIm; - // TODO do we need to notify the id change? - im._id = intendedIdentifier.id; - im.identifier = intendedIdentifier; - map.add(im, intendedIdentifier.lid); - - // just use im - } else { - // otherIm.destroy(); - } - - /* - TODO @runspired consider adding this to make polymorphism even nicer - if (HAS_RECORD_DATA_PACKAGE) { - if (identifier.type !== matchedIdentifier.type) { - const graphFor = importSync('@ember-data/record-data/-private').graphFor; - graphFor(this).registerPolymorphicType(identifier.type, matchedIdentifier.type); - } - } - */ - - return intendedIdentifier; - }); - this._identityMap = new IdentityMap(); - } - - /** - * Retrieve the InternalModel for a given { type, id, lid }. - * - * If an InternalModel does not exist, it instantiates one. - * - * If an InternalModel does exist bus has a scheduled destroy, - * the scheduled destroy will be cancelled. - * - * @method lookup - * @private - */ - lookup(resource: ResourceIdentifierObject, data?: ExistingResourceObject): InternalModel { - if (data !== undefined) { - // if we've been given data associated with this lookup - // we must first give secondary-caches for LIDs the - // opportunity to populate based on it - this.identifierCache.getOrCreateRecordIdentifier(data); - } - - const identifier = this.identifierCache.getOrCreateRecordIdentifier(resource); - const internalModel = this.peek(identifier); - - if (internalModel) { - // unloadRecord is async, if one attempts to unload + then sync push, - // we must ensure the unload is canceled before continuing - // The createRecord path will take _existingInternalModelForId() - // which will call `destroySync` instead for this unload + then - // sync createRecord scenario. Once we have true client-side - // delete signaling, we should never call destroySync - if (internalModel.hasScheduledDestroy()) { - internalModel.cancelDestroy(); - } - - return internalModel; - } - - return this._build(identifier, false); - } - - /** - * Peek the InternalModel for a given { type, id, lid }. - * - * If an InternalModel does not exist, return `null`. - * - * @method peek - * @private - */ - peek(identifier: StableRecordIdentifier): InternalModel | null { - return this.modelMapFor(identifier.type).get(identifier.lid); - } - - getByResource(resource: ResourceIdentifierObject): InternalModel { - const normalizedResource = constructResource(resource); - - return this.lookup(normalizedResource); - } - - setRecordId(type: string, id: string, lid: string) { - const resource: NewResourceIdentifierObject = { type, id: null, lid }; - const identifier = this.identifierCache.getOrCreateRecordIdentifier(resource); - const internalModel = this.peek(identifier); - - if (internalModel === null) { - throw new Error(`Cannot set the id ${id} on the record ${type}:${lid} as there is no such record in the cache.`); - } - - let oldId = internalModel.id; - let modelName = internalModel.modelName; - - // ID absolutely can't be missing if the oldID is empty (missing Id in response for a new record) - assert( - `'${modelName}' was saved to the server, but the response does not have an id and your record does not either.`, - !(id === null && oldId === null) - ); - - // ID absolutely can't be different than oldID if oldID is not null - // TODO this assertion and restriction may not strictly be needed in the identifiers world - assert( - `Cannot update the id for '${modelName}:${lid}' from '${oldId}' to '${id}'.`, - !(oldId !== null && id !== oldId) - ); - - // ID can be null if oldID is not null (altered ID in response for a record) - // however, this is more than likely a developer error. - if (oldId !== null && id === null) { - warn( - `Your ${modelName} record was saved to the server, but the response does not have an id.`, - !(oldId !== null && id === null) - ); - return; - } - - let existingInternalModel = this.peekById(modelName, id); - - assert( - `'${modelName}' was saved to the server, but the response returned the new id '${id}', which has already been used with another record.'`, - !existingInternalModel || existingInternalModel === internalModel - ); - - if (identifier.id === null) { - // TODO potentially this needs to handle merged result - this.identifierCache.updateRecordIdentifier(identifier, { type, id }); - } - - internalModel.setId(id, true); - } - - peekById(type: string, id: string): InternalModel | null { - const identifier = this.identifierCache.peekRecordIdentifier({ type, id }); - let internalModel = identifier ? this.modelMapFor(type).get(identifier.lid) : null; - - if (internalModel && internalModel.hasScheduledDestroy()) { - // unloadRecord is async, if one attempts to unload + then sync create, - // we must ensure the unload is complete before starting the create - // The push path will take this.lookup() - // which will call `cancelDestroy` instead for this unload + then - // sync push scenario. Once we have true client-side - // delete signaling, we should never call destroySync - internalModel.destroySync(); - internalModel = null; - } - return internalModel; - } - - build(newResourceInfo: NewResourceInfo): InternalModel { - return this._build(newResourceInfo, true); - } - - _build(resource: StableRecordIdentifier, isCreate: false): InternalModel; - _build(resource: NewResourceInfo, isCreate: true): InternalModel; - _build(resource: StableRecordIdentifier | NewResourceInfo, isCreate: boolean = false): InternalModel { - if (isCreate === true && resource.id) { - let existingInternalModel = this.peekById(resource.type, resource.id); - - assert( - `The id ${resource.id} has already been used with another '${resource.type}' record.`, - !existingInternalModel - ); - } - - const { identifierCache } = this; - let identifier: StableRecordIdentifier; - - if (isCreate === true) { - identifier = identifierCache.createIdentifierForNewRecord(resource); - } else { - identifier = resource as StableRecordIdentifier; - } - - // lookupFactory should really return an object that creates - // instances with the injections applied - let internalModel = new InternalModel(this.store, identifier); - - this.modelMapFor(resource.type).add(internalModel, identifier.lid); - - return internalModel; - } - - remove(internalModel: InternalModel): void { - let recordMap = this.modelMapFor(internalModel.modelName); - let clientId = internalModel.identifier.lid; - - recordMap.remove(internalModel, clientId); - - const { identifier } = internalModel; - this.identifierCache.forgetRecordIdentifier(identifier); - } - - modelMapFor(type: string): InternalModelMap { - return this._identityMap.retrieve(type); - } - - clear(type?: string) { - if (type === undefined) { - this._identityMap.clear(); - } else { - this.modelMapFor(type).clear(); - } - } -} diff --git a/packages/store/addon/-private/internal-model-map.ts b/packages/store/addon/-private/internal-model-map.ts deleted file mode 100644 index 74a98d32eba..00000000000 --- a/packages/store/addon/-private/internal-model-map.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { assert } from '@ember/debug'; - -import type { StableRecordIdentifier } from '@ember-data/types/q/identifier'; -import type { ConfidentDict } from '@ember-data/types/q/utils'; - -import InternalModel from './model/internal-model'; - -/** - @module @ember-data/store -*/ - -/** - `InternalModelMap` is a custom storage map for internalModels of a given modelName - used by `IdentityMap`. - - It was extracted from an implicit pojo based "internalModel map" and preserves - that interface while we work towards a more official API. - - @class InternalModelMap - @internal - */ -export default class InternalModelMap { - _idToModel: ConfidentDict = Object.create(null); - _models: InternalModel[] = []; - modelName: string; - - constructor(modelName: string) { - this.modelName = modelName; - } - - get(id: string): InternalModel | null { - return this._idToModel[id] || null; - } - - has(id: string): boolean { - return !!this._idToModel[id]; - } - - get length(): number { - return this._models.length; - } - - get recordIdentifiers(): StableRecordIdentifier[] { - return this._models.map((m) => m.identifier); - } - - set(id: string, internalModel: InternalModel): void { - assert(`You cannot index an internalModel by an empty id'`, typeof id === 'string' && id.length > 0); - assert( - `You cannot set an index for an internalModel to something other than an internalModel`, - internalModel instanceof InternalModel - ); - assert( - `You cannot set an index for an internalModel that is not in the InternalModelMap`, - this.contains(internalModel) - ); - assert( - `You cannot update the id index of an InternalModel once set. Attempted to update ${id}.`, - !this.has(id) || this.get(id) === internalModel - ); - - this._idToModel[id] = internalModel; - } - - add(internalModel: InternalModel, id: string | null): void { - assert( - `You cannot re-add an already present InternalModel to the InternalModelMap.`, - !this.contains(internalModel) - ); - - if (id) { - assert( - `Duplicate InternalModel for ${this.modelName}:${id} detected.`, - !this.has(id) || this.get(id) === internalModel - ); - - this._idToModel[id] = internalModel; - } - - this._models.push(internalModel); - } - - remove(internalModel: InternalModel, id: string): void { - delete this._idToModel[id]; - - let loc = this._models.indexOf(internalModel); - - if (loc !== -1) { - this._models.splice(loc, 1); - } - } - - contains(internalModel: InternalModel): boolean { - return this._models.indexOf(internalModel) !== -1; - } - - /** - An array of all models of this modelName - @property models - @internal - @type Array - */ - get models(): InternalModel[] { - return this._models; - } - - /** - Destroy all models in the map - - @internal - */ - clear(): void { - let internalModels = this._models; - this._models = []; - - for (let i = 0; i < internalModels.length; i++) { - let internalModel = internalModels[i]; - internalModel.unloadRecord(); - } - } -} diff --git a/packages/store/addon/-private/model/record-reference.ts b/packages/store/addon/-private/legacy-model-support/record-reference.ts similarity index 96% rename from packages/store/addon/-private/model/record-reference.ts rename to packages/store/addon/-private/legacy-model-support/record-reference.ts index 722c6e41580..322be177351 100644 --- a/packages/store/addon/-private/model/record-reference.ts +++ b/packages/store/addon/-private/legacy-model-support/record-reference.ts @@ -10,9 +10,9 @@ import type { SingleResourceDocument } from '@ember-data/types/q/ember-data-json import type { StableRecordIdentifier } from '@ember-data/types/q/identifier'; import type { RecordInstance } from '@ember-data/types/q/record-instance'; -import type Store from '../core-store'; -import type { NotificationType } from '../record-notification-manager'; -import { unsubscribe } from '../record-notification-manager'; +import type { NotificationType } from '../managers/record-notification-manager'; +import { unsubscribe } from '../managers/record-notification-manager'; +import type Store from '../store-service'; /** @module @ember-data/store diff --git a/packages/store/addon/-private/schema-definition-service.ts b/packages/store/addon/-private/legacy-model-support/schema-definition-service.ts similarity index 94% rename from packages/store/addon/-private/schema-definition-service.ts rename to packages/store/addon/-private/legacy-model-support/schema-definition-service.ts index 41add669a96..0120a6f8f98 100644 --- a/packages/store/addon/-private/schema-definition-service.ts +++ b/packages/store/addon/-private/legacy-model-support/schema-definition-service.ts @@ -1,6 +1,5 @@ import { getOwner } from '@ember/application'; import { deprecate } from '@ember/debug'; -import { get } from '@ember/object'; import { importSync } from '@embroider/macros'; @@ -10,8 +9,8 @@ import { DEPRECATE_STRING_ARG_SCHEMAS } from '@ember-data/private-build-infra/de import type { RecordIdentifier } from '@ember-data/types/q/identifier'; import type { AttributesSchema, RelationshipsSchema } from '@ember-data/types/q/record-data-schemas'; -import type Store from './core-store'; -import normalizeModelName from './normalize-model-name'; +import type Store from '../store-service'; +import normalizeModelName from '../utils/normalize-model-name'; type ModelForMixin = (store: Store, normalizedModelName: string) => Model | null; @@ -55,7 +54,7 @@ export class DSModelSchemaDefinitionService { if (attributes === undefined) { let modelClass = this.store.modelFor(modelName); - let attributeMap = get(modelClass, 'attributes'); + let attributeMap = modelClass.attributes; attributes = Object.create(null); attributeMap.forEach((meta, name) => (attributes[name] = meta)); @@ -88,7 +87,7 @@ export class DSModelSchemaDefinitionService { if (relationships === undefined) { let modelClass = this.store.modelFor(modelName); - relationships = get(modelClass, 'relationshipsObject') || null; + relationships = modelClass.relationshipsObject || null; this._relationshipsDefCache[modelName] = relationships; } diff --git a/packages/store/addon/-private/model/shim-model-class.ts b/packages/store/addon/-private/legacy-model-support/shim-model-class.ts similarity index 97% rename from packages/store/addon/-private/model/shim-model-class.ts rename to packages/store/addon/-private/legacy-model-support/shim-model-class.ts index 475871c107f..c70500d10d6 100644 --- a/packages/store/addon/-private/model/shim-model-class.ts +++ b/packages/store/addon/-private/legacy-model-support/shim-model-class.ts @@ -4,8 +4,8 @@ import type { ModelSchema } from '@ember-data/types/q/ds-model'; import type { AttributeSchema, RelationshipSchema } from '@ember-data/types/q/record-data-schemas'; import type { Dict } from '@ember-data/types/q/utils'; -import type Store from '../core-store'; -import WeakCache from '../weak-cache'; +import type Store from '../store-service'; +import WeakCache from '../utils/weak-cache'; const AvailableShims = new WeakCache>(DEBUG ? 'schema-shims' : ''); AvailableShims._generator = () => { diff --git a/packages/store/addon/-private/record-array-manager.ts b/packages/store/addon/-private/managers/record-array-manager.ts similarity index 86% rename from packages/store/addon/-private/record-array-manager.ts rename to packages/store/addon/-private/managers/record-array-manager.ts index 44f01088ecc..314d5c1da7f 100644 --- a/packages/store/addon/-private/record-array-manager.ts +++ b/packages/store/addon/-private/managers/record-array-manager.ts @@ -4,20 +4,17 @@ import { A } from '@ember/array'; import { assert } from '@ember/debug'; -import { set } from '@ember/object'; import { _backburner as emberBackburner } from '@ember/runloop'; import { DEBUG } from '@glimmer/env'; -// import isStableIdentifier from '../identifiers/is-stable-identifier'; import type { CollectionResourceDocument, Meta } from '@ember-data/types/q/ember-data-json-api'; import type { StableRecordIdentifier } from '@ember-data/types/q/identifier'; import type { Dict } from '@ember-data/types/q/utils'; -import type Store from './core-store'; -import { internalModelFactoryFor } from './internal-model-factory'; -import AdapterPopulatedRecordArray from './record-arrays/adapter-populated-record-array'; -import RecordArray from './record-arrays/record-array'; -import WeakCache from './weak-cache'; +import AdapterPopulatedRecordArray from '../record-arrays/adapter-populated-record-array'; +import RecordArray from '../record-arrays/record-array'; +import type Store from '../store-service'; +import WeakCache from '../utils/weak-cache'; const RecordArraysCache = new WeakCache>(DEBUG ? 'record-arrays' : ''); RecordArraysCache._generator = () => new Set(); @@ -27,27 +24,6 @@ export function recordArraysForIdentifier(identifier: StableRecordIdentifier): S const pendingForIdentifier: Set = new Set([]); -function getIdentifier(identifier: StableRecordIdentifier): StableRecordIdentifier { - // during dematerialization we will get an identifier that - // has already been removed from the identifiers cache - // so it will not behave as if stable. This is a bug we should fix. - // if (!isStableIdentifier(identifierOrInternalModel)) { - // console.log({ unstable: i }); - // } - - return identifier; -} - -function shouldIncludeInRecordArrays(store: Store, identifier: StableRecordIdentifier): boolean { - const cache = internalModelFactoryFor(store); - const internalModel = cache.peek(identifier); - - if (internalModel === null) { - return false; - } - return !internalModel.isHiddenFromRecordArrays(); -} - /** @class RecordArrayManager @internal @@ -84,6 +60,7 @@ class RecordArrayManager { return; } let identifiersToRemove: StableRecordIdentifier[] = []; + let cache = this.store._instanceCache; for (let j = 0; j < identifiers.length; j++) { let i = identifiers[j]; @@ -91,8 +68,7 @@ class RecordArrayManager { // recordArrayManager pendingForIdentifier.delete(i); // build up a set of models to ensure we have purged correctly; - let isIncluded = shouldIncludeInRecordArrays(this.store, i); - if (!isIncluded) { + if (!cache.recordIsLoaded(i, true)) { identifiersToRemove.push(i); } } @@ -130,8 +106,8 @@ class RecordArrayManager { return; } let hasNoPotentialDeletions = pending.length === 0; - let map = internalModelFactoryFor(this.store).modelMapFor(modelName); - let hasNoInsertionsOrRemovals = map.length === array.length; + let listSize = this.store._instanceCache.peekList[modelName]?.size; + let hasNoInsertionsOrRemovals = listSize === array.length; /* Ideally the recordArrayManager has knowledge of the changes to be applied to @@ -165,7 +141,7 @@ class RecordArrayManager { _didUpdateAll(modelName: string): void { let recordArray = this._liveRecordArrays[modelName]; if (recordArray) { - set(recordArray, 'isUpdating', false); + recordArray.isUpdating = false; // TODO potentially we should sync here, currently // this occurs as a side-effect of individual records updating // this._syncLiveRecordArray(recordArray, modelName); @@ -204,13 +180,14 @@ class RecordArrayManager { } _visibleIdentifiersByType(modelName: string) { - let all = internalModelFactoryFor(this.store).modelMapFor(modelName).recordIdentifiers; + const cache = this.store._instanceCache; + const list = cache.peekList[modelName]; + let all = list ? [...list.values()] : []; let visible: StableRecordIdentifier[] = []; for (let i = 0; i < all.length; i++) { let identifier = all[i]; - let shouldInclude = shouldIncludeInRecordArrays(this.store, identifier); - if (shouldInclude) { + if (cache.recordIsLoaded(identifier, true)) { visible.push(identifier); } } @@ -335,7 +312,6 @@ class RecordArrayManager { _associateWithRecordArray(identifiers: StableRecordIdentifier[], array: RecordArray): void { 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); } @@ -350,7 +326,6 @@ class RecordArrayManager { return; } let modelName = identifier.type; - identifier = getIdentifier(identifier); if (pendingForIdentifier.has(identifier)) { return; @@ -397,20 +372,18 @@ function removeFromArray(array: RecordArray[], item: RecordArray): boolean { function updateLiveRecordArray(store: Store, recordArray: RecordArray, identifiers: StableRecordIdentifier[]): void { let identifiersToAdd: StableRecordIdentifier[] = []; let identifiersToRemove: StableRecordIdentifier[] = []; + const cache = store._instanceCache; for (let i = 0; i < identifiers.length; i++) { let identifier = identifiers[i]; - let shouldInclude = shouldIncludeInRecordArrays(store, identifier); let recordArrays = recordArraysForIdentifier(identifier); - if (shouldInclude) { + if (cache.recordIsLoaded(identifier, true)) { if (!recordArrays.has(recordArray)) { identifiersToAdd.push(identifier); recordArrays.add(recordArray); } - } - - if (!shouldInclude) { + } else { identifiersToRemove.push(identifier); recordArrays.delete(recordArray); } @@ -431,7 +404,6 @@ function removeFromAdapterPopulatedRecordArrays(store: Store, identifiers: Stabl } function removeFromAll(store: Store, identifier: StableRecordIdentifier): void { - identifier = getIdentifier(identifier); const recordArrays = recordArraysForIdentifier(identifier); recordArrays.forEach(function (recordArray) { diff --git a/packages/store/addon/-private/record-data-store-wrapper.ts b/packages/store/addon/-private/managers/record-data-store-wrapper.ts similarity index 75% rename from packages/store/addon/-private/record-data-store-wrapper.ts rename to packages/store/addon/-private/managers/record-data-store-wrapper.ts index 9f0be766b84..e761039e598 100644 --- a/packages/store/addon/-private/record-data-store-wrapper.ts +++ b/packages/store/addon/-private/managers/record-data-store-wrapper.ts @@ -1,7 +1,4 @@ -import { importSync } from '@embroider/macros'; - import type { RelationshipDefinition } from '@ember-data/model/-private/relationship-meta'; -import { HAS_RECORD_DATA_PACKAGE } from '@ember-data/private-build-infra'; import type { StableRecordIdentifier } from '@ember-data/types/q/identifier'; import type { RecordData } from '@ember-data/types/q/record-data'; import type { @@ -11,10 +8,9 @@ import type { } from '@ember-data/types/q/record-data-schemas'; import type { RecordDataStoreWrapper as StoreWrapper } from '@ember-data/types/q/record-data-store-wrapper'; -import type Store from './core-store'; -import type { IdentifierCache } from './identifier-cache'; -import { internalModelFactoryFor } from './internal-model-factory'; -import constructResource from './utils/construct-resource'; +import type { IdentifierCache } from '../caches/identifier-cache'; +import type Store from '../store-service'; +import constructResource from '../utils/construct-resource'; /** @module @ember-data/store @@ -24,17 +20,6 @@ function metaIsRelationshipDefinition(meta: RelationshipSchema): meta is Relatio return typeof (meta as RelationshipDefinition)._inverseKey === 'function'; } -let peekGraph; -if (HAS_RECORD_DATA_PACKAGE) { - let _peekGraph; - peekGraph = (wrapper) => { - _peekGraph = - _peekGraph || - (importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')).peekGraph; - return _peekGraph(wrapper); - }; -} - export default class RecordDataStoreWrapper implements StoreWrapper { declare _willNotify: boolean; declare _pendingNotifies: Map>; @@ -86,19 +71,11 @@ export default class RecordDataStoreWrapper implements StoreWrapper { let pending = this._pendingNotifies; this._pendingNotifies = new Map(); this._willNotify = false; - const factory = internalModelFactoryFor(this._store); pending.forEach((map, identifier) => { - const internalModel = factory.peek(identifier); - if (internalModel) { - map.forEach((kind, key) => { - if (kind === 'belongsTo') { - internalModel.notifyBelongsToChange(key); - } else { - internalModel.notifyHasManyChange(key); - } - }); - } + map.forEach((kind, key) => { + this._store._notificationManager.notify(identifier, 'relationships', key); + }); }); } @@ -152,11 +129,8 @@ export default class RecordDataStoreWrapper implements StoreWrapper { notifyPropertyChange(type: string, id: string | null, lid: string | null | undefined, key?: string): void { const resource = constructResource(type, id, lid); const identifier = this.identifierCache.getOrCreateRecordIdentifier(resource); - let internalModel = internalModelFactoryFor(this._store).peek(identifier); - if (internalModel) { - internalModel.notifyAttributes(key ? [key] : []); - } + this._store._notificationManager.notify(identifier, 'attributes', key); } notifyHasManyChange(type: string, id: string | null, lid: string, key: string): void; @@ -181,10 +155,11 @@ export default class RecordDataStoreWrapper implements StoreWrapper { notifyStateChange(type: string, id: string | null, lid: string | null, key?: string): void { const resource = constructResource(type, id, lid); const identifier = this.identifierCache.getOrCreateRecordIdentifier(resource); - let internalModel = internalModelFactoryFor(this._store).peek(identifier); - if (internalModel) { - internalModel.notifyStateChange(key); + this._store._notificationManager.notify(identifier, 'state'); + + if (!key || key === 'isDeletionCommitted') { + this._store.recordArrayManager.recordDidChange(identifier); } } @@ -192,17 +167,25 @@ export default class RecordDataStoreWrapper implements StoreWrapper { recordDataFor(type: string, id: string | null, lid: string): RecordData; recordDataFor(type: string): RecordData; recordDataFor(type: string, id?: string | null, lid?: string | null): RecordData { - let identifier: StableRecordIdentifier | { type: string }; - let isCreate: boolean = false; + // TODO @deprecate create capability. This is problematic because there's + // no outside association between this RecordData and an Identifier. It's + // likely a mistake but we said in an RFC we'd allow this. We should RFC + // enforcing someone to use the record-data and identifier-cache APIs to + // create a new identifier and then call clientDidCreate on the RecordData + // instead. + const identifier = + !id && !lid + ? this.identifierCache.createIdentifierForNewRecord({ type: type }) + : this.identifierCache.getOrCreateRecordIdentifier(constructResource(type, id, lid)); + + const recordData = this._store._instanceCache.getRecordData(identifier); + if (!id && !lid) { - isCreate = true; - identifier = { type }; - } else { - const resource = constructResource(type, id, lid); - identifier = this.identifierCache.getOrCreateRecordIdentifier(resource); + recordData.clientDidCreate(); + this._store.recordArrayManager.recordDidChange(identifier); } - return this._store._instanceCache.recordDataFor(identifier, isCreate); + return recordData; } setRecordId(type: string, id: string, lid: string) { @@ -213,9 +196,9 @@ export default class RecordDataStoreWrapper implements StoreWrapper { isRecordInUse(type: string, id: string, lid?: string | null): boolean; isRecordInUse(type: string, id: string | null, lid?: string | null): boolean { const resource = constructResource(type, id, lid); - const identifier = this.identifierCache.getOrCreateRecordIdentifier(resource); + const identifier = this.identifierCache.peekRecordIdentifier(resource); - const record = this._store._instanceCache.peek({ identifier, bucket: 'record' }); + const record = identifier && this._store._instanceCache.peek({ identifier, bucket: 'record' }); return record ? !(record.isDestroyed || record.isDestroying) : false; } @@ -224,16 +207,11 @@ export default class RecordDataStoreWrapper implements StoreWrapper { disconnectRecord(type: string, id: string, lid?: string | null): void; disconnectRecord(type: string, id: string | null, lid?: string | null): void { const resource = constructResource(type, id, lid); - const identifier = this.identifierCache.getOrCreateRecordIdentifier(resource); - if (HAS_RECORD_DATA_PACKAGE) { - let graph = peekGraph(this); - if (graph) { - graph.remove(identifier); - } - } - let internalModel = internalModelFactoryFor(this._store).peek(identifier); - if (internalModel) { - internalModel.destroyFromRecordData(); + const identifier = this.identifierCache.peekRecordIdentifier(resource); + + if (identifier) { + this._store._instanceCache.disconnect(identifier); + this._pendingNotifies.delete(identifier); } } } diff --git a/packages/store/addon/-private/managers/record-notification-manager.ts b/packages/store/addon/-private/managers/record-notification-manager.ts new file mode 100644 index 00000000000..c33759af3f2 --- /dev/null +++ b/packages/store/addon/-private/managers/record-notification-manager.ts @@ -0,0 +1,96 @@ +import { assert } from '@ember/debug'; +import { DEBUG } from '@glimmer/env'; + +import { LOG_NOTIFICATIONS } from '@ember-data/private-build-infra/debugging'; +import type { StableRecordIdentifier } from '@ember-data/types/q/identifier'; + +import { isStableIdentifier } from '../caches/identifier-cache'; +import type Store from '../store-service'; +import WeakCache from '../utils/weak-cache'; + +type UnsubscribeToken = object; + +const Cache = new WeakCache>( + DEBUG ? 'subscribers' : '' +); +Cache._generator = () => new Map(); +const Tokens = new WeakCache(DEBUG ? 'identifier' : ''); + +export type NotificationType = + | 'attributes' + | 'relationships' + | 'identity' + | 'errors' + | 'meta' + | 'unload' + | 'state' + | 'property'; // 'property' is an internal EmberData only transition period concept. + +export interface NotificationCallback { + ( + identifier: StableRecordIdentifier, + notificationType: 'attributes' | 'relationships' | 'property', + key?: string + ): void; + (identifier: StableRecordIdentifier, notificationType: 'errors' | 'meta' | 'identity' | 'unload' | 'state'): void; + (identifier: StableRecordIdentifier, notificationType: NotificationType, key?: string): void; +} + +export function unsubscribe(token: UnsubscribeToken) { + let identifier = Tokens.get(token); + if (!identifier) { + throw new Error('Passed unknown unsubscribe token to unsubscribe'); + } + Tokens.delete(token); + const map = Cache.get(identifier); + map?.delete(token); +} +/* + Currently only support a single callback per identifier +*/ +export default class NotificationManager { + constructor(private store: Store) {} + + subscribe(identifier: StableRecordIdentifier, callback: NotificationCallback): UnsubscribeToken { + assert(`Expected to receive a stable Identifier to subscribe to`, isStableIdentifier(identifier)); + let map = Cache.lookup(identifier); + let unsubToken = {}; + map.set(unsubToken, callback); + Tokens.set(unsubToken, identifier); + return unsubToken; + } + + unsubscribe(token: UnsubscribeToken) { + unsubscribe(token); + } + + notify(identifier: StableRecordIdentifier, value: 'attributes' | 'relationships' | 'property', key?: string): boolean; + notify(identifier: StableRecordIdentifier, value: 'errors' | 'meta' | 'identity' | 'unload' | 'state'): boolean; + notify(identifier: StableRecordIdentifier, value: NotificationType, key?: string): boolean { + if (!isStableIdentifier(identifier)) { + if (LOG_NOTIFICATIONS) { + // eslint-disable-next-line no-console + console.log( + `Notifying: Expected to receive a stable Identifier to notify '${value}' '${key}' with, but ${String( + identifier + )} is not in the cache`, + identifier + ); + } + return false; + } + + if (LOG_NOTIFICATIONS) { + // eslint-disable-next-line no-console + console.log(`Notifying: ${String(identifier)}\t${value}\t${key}`); + } + let callbackMap = Cache.get(identifier); + if (!callbackMap || !callbackMap.size) { + return false; + } + callbackMap.forEach((cb) => { + cb(identifier, value, key); + }); + return true; + } +} diff --git a/packages/store/addon/-private/model/internal-model.ts b/packages/store/addon/-private/model/internal-model.ts deleted file mode 100644 index 9ea227c42b1..00000000000 --- a/packages/store/addon/-private/model/internal-model.ts +++ /dev/null @@ -1,600 +0,0 @@ -import { assert } from '@ember/debug'; -import { _backburner as emberBackburner, cancel, run } from '@ember/runloop'; -import { DEBUG } from '@glimmer/env'; - -import { HAS_MODEL_PACKAGE } from '@ember-data/private-build-infra'; -import type { DSModel } from '@ember-data/types/q/ds-model'; -import type { StableRecordIdentifier } from '@ember-data/types/q/identifier'; -import type { MinimumSerializerInterface } from '@ember-data/types/q/minimum-serializer-interface'; -import type { ChangedAttributesHash, RecordData } from '@ember-data/types/q/record-data'; -import type { JsonApiResource, JsonApiValidationError } from '@ember-data/types/q/record-data-json-api'; -import type { RecordInstance } from '@ember-data/types/q/record-instance'; - -import type Store from '../core-store'; -import { internalModelFactoryFor } from '../internal-model-factory'; -import type ShimModelClass from './shim-model-class'; - -/** - @module @ember-data/store -*/ - -function isDSModel(record: RecordInstance | null): record is DSModel { - return ( - HAS_MODEL_PACKAGE && - !!record && - 'constructor' in record && - 'isModel' in record.constructor && - record.constructor.isModel === true - ); -} - -type AdapterErrors = Error & { errors?: unknown[]; isAdapterError?: true; code?: string }; -type SerializerWithParseErrors = MinimumSerializerInterface & { - extractErrors?(store: Store, modelClass: ShimModelClass, error: AdapterErrors, recordId: string | null): any; -}; - -export default class InternalModel { - declare _id: string | null; - declare modelName: string; - declare clientId: string; - declare hasRecordData: boolean; - declare _isDestroyed: boolean; - declare isError: boolean; - declare _pendingRecordArrayManagerFlush: boolean; - declare _isDematerializing: boolean; - declare _doNotDestroy: boolean; - declare isDestroying: boolean; - declare _isUpdatingId: boolean; - declare _deletedRecordWasNew: boolean; - - // Not typed yet - declare _scheduledDestroy: any; - declare _modelClass: any; - declare __recordArrays: any; - declare error: any; - declare store: Store; - declare identifier: StableRecordIdentifier; - declare hasRecord: boolean; - - constructor(store: Store, identifier: StableRecordIdentifier) { - this.store = store; - this.identifier = identifier; - this._id = identifier.id; - this._isUpdatingId = false; - this.modelName = identifier.type; - this.clientId = identifier.lid; - this.hasRecord = false; - - this.hasRecordData = false; - - this._isDestroyed = false; - this._doNotDestroy = false; - this.isError = false; - this._pendingRecordArrayManagerFlush = false; // used by the recordArrayManager - - // During dematerialization we don't want to rematerialize the record. The - // reason this might happen is that dematerialization removes records from - // record arrays, and Ember arrays will always `objectAt(0)` and - // `objectAt(len - 1)` to test whether or not `firstObject` or `lastObject` - // have changed. - this._isDematerializing = false; - this._scheduledDestroy = null; - - this.error = null; - - // caches for lazy getters - this._modelClass = null; - this.__recordArrays = null; - - this.error = null; - } - - get id(): string | null { - return this.identifier.id; - } - set id(value: string | null) { - if (value !== this._id) { - let newIdentifier = { type: this.identifier.type, lid: this.identifier.lid, id: value }; - // TODO potentially this needs to handle merged result - this.store.identifierCache.updateRecordIdentifier(this.identifier, newIdentifier); - if (this.hasRecord) { - // TODO this should likely *mostly* be the a different bucket - this.store._notificationManager.notify(this.identifier, 'property', 'id'); - } - } - } - - get modelClass() { - if (this.store.modelFor) { - return this._modelClass || (this._modelClass = this.store.modelFor(this.modelName)); - } - } - - get _recordData(): RecordData { - return this.store._instanceCache.getRecordData(this.identifier); - } - - isHiddenFromRecordArrays() { - // During dematerialization we don't want to rematerialize the record. - // recordWasDeleted can cause other records to rematerialize because it - // removes the internal model from the array and Ember arrays will always - // `objectAt(0)` and `objectAt(len -1)` to check whether `firstObject` or - // `lastObject` have changed. When this happens we don't want those - // models to rematerialize their records. - - // eager checks to avoid instantiating record data if we are empty or loading - if (this.isEmpty) { - return true; - } - - if (this.isLoading) { - return false; - } - - let isRecordFullyDeleted = this._isRecordFullyDeleted(); - return this._isDematerializing || this.hasScheduledDestroy() || this.isDestroyed || isRecordFullyDeleted; - } - - _isRecordFullyDeleted(): boolean { - if (this._recordData.isDeletionCommitted && this._recordData.isDeletionCommitted()) { - return true; - } else if ( - this._recordData.isNew && - this._recordData.isDeleted && - this._recordData.isNew() && - this._recordData.isDeleted() - ) { - return true; - } else { - return false; - } - } - - isDeleted(): boolean { - if (this._recordData.isDeleted) { - return this._recordData.isDeleted(); - } else { - return false; - } - } - - isNew(): boolean { - if (this.hasRecordData && this._recordData.isNew) { - return this._recordData.isNew(); - } else { - return false; - } - } - - get isEmpty(): boolean { - return !this.hasRecordData || ((!this.isNew() || this.isDeleted()) && this._recordData.isEmpty?.()) || false; - } - - get isLoading() { - const req = this.store.getRequestStateService(); - const { identifier } = this; - // const fulfilled = req.getLastRequestForRecord(identifier); - - return ( - !this.isLoaded && - // fulfilled === null && - req.getPendingRequestsForRecord(identifier).some((req) => req.type === 'query') - ); - } - - get isLoaded() { - // if we are new we must consider ourselves loaded - if (this.isNew()) { - return true; - } - // even if we have a past request, if we are now empty we are not loaded - // typically this is true after an unloadRecord call - - // if we are not empty, not new && we have a fulfilled request then we are loaded - // we should consider allowing for something to be loaded that is simply "not empty". - // which is how RecordState currently handles this case; however, RecordState is buggy - // in that it does not account for unloading. - return !this.isEmpty; - } - - dematerializeRecord() { - this._isDematerializing = true; - - // TODO IGOR add a test that fails when this is missing, something that involves canceling a destroy - // and the destroy not happening, and then later on trying to destroy - this._doNotDestroy = false; - // this has to occur before the internal model is removed - // for legacy compat. - const { identifier } = this; - this.store._instanceCache.removeRecord(identifier); - - // move to an empty never-loaded state - // ensure any record notifications happen prior to us - // unseting the record but after we've triggered - // destroy - this.store._backburner.join(() => { - this._recordData.unloadRecord(); - }); - - this.hasRecord = false; // this must occur after relationship removal - this.error = null; - this.store.recordArrayManager.recordDidChange(this.identifier); - } - - deleteRecord() { - run(() => { - const backburner = this.store._backburner; - backburner.run(() => { - if (this._recordData.setIsDeleted) { - this._recordData.setIsDeleted(true); - } - - if (this.isNew()) { - // destroyRecord follows up deleteRecord with save(). This prevents an unecessary save for a new record - this._deletedRecordWasNew = true; - this.unloadRecord(); - } - }); - }); - } - - /* - Unload the record for this internal model. This will cause the record to be - destroyed and freed up for garbage collection. It will also do a check - for cleaning up internal models. - - This check is performed by first computing the set of related internal - models. If all records in this set are unloaded, then the entire set is - destroyed. Otherwise, nothing in the set is destroyed. - - This means that this internal model will be freed up for garbage collection - once all models that refer to it via some relationship are also unloaded. - */ - unloadRecord() { - if (this.isDestroyed) { - return; - } - if (DEBUG) { - const requests = this.store.getRequestStateService().getPendingRequestsForRecord(this.identifier); - if ( - requests.some((req) => { - return req.type === 'mutation'; - }) - ) { - assert('You can only unload a record which is not inFlight. `' + this + '`'); - } - } - this.dematerializeRecord(); - if (this._scheduledDestroy === null) { - this._scheduledDestroy = emberBackburner.schedule('destroy', this, '_checkForOrphanedInternalModels'); - } - } - - hasScheduledDestroy() { - return !!this._scheduledDestroy; - } - - cancelDestroy() { - assert( - `You cannot cancel the destruction of an InternalModel once it has already been destroyed`, - !this.isDestroyed - ); - - this._doNotDestroy = true; - this._isDematerializing = false; - cancel(this._scheduledDestroy); - this._scheduledDestroy = null; - } - - // typically, we prefer to async destroy this lets us batch cleanup work. - // Unfortunately, some scenarios where that is not possible. Such as: - // - // ```js - // const record = store.findRecord(‘record’, 1); - // record.unloadRecord(); - // store.createRecord(‘record’, 1); - // ``` - // - // In those scenarios, we make that model's cleanup work, sync. - // - destroySync() { - if (this._isDematerializing) { - this.cancelDestroy(); - } - this._checkForOrphanedInternalModels(); - if (this.isDestroyed || this.isDestroying) { - return; - } - - // just in-case we are not one of the orphaned, we should still - // still destroy ourselves - this.destroy(); - } - - _checkForOrphanedInternalModels() { - this._isDematerializing = false; - this._scheduledDestroy = null; - if (this.isDestroyed) { - return; - } - } - - destroyFromRecordData() { - if (this._doNotDestroy) { - this._doNotDestroy = false; - return; - } - this.destroy(); - } - - destroy() { - let record = this.store._instanceCache.peek({ identifier: this.identifier, bucket: 'record' }); - assert( - 'Cannot destroy an internalModel while its record is materialized', - !record || record.isDestroyed || record.isDestroying - ); - this.isDestroying = true; - - internalModelFactoryFor(this.store).remove(this); - this._isDestroyed = true; - } - - setupData(data) { - if (this.isNew()) { - this.store._notificationManager.notify(this.identifier, 'identity'); - } - this._recordData.pushData(data, this.hasRecord); - } - - notifyAttributes(keys: string[]): void { - if (this.hasRecord) { - let manager = this.store._notificationManager; - let { identifier } = this; - - if (!keys || !keys.length) { - manager.notify(identifier, 'attributes'); - } else { - for (let i = 0; i < keys.length; i++) { - manager.notify(identifier, 'attributes', keys[i]); - } - } - } - } - - get isDestroyed(): boolean { - return this._isDestroyed; - } - - hasChangedAttributes(): boolean { - if (!this.hasRecordData) { - // no need to calculate changed attributes when calling `findRecord` - return false; - } - return this._recordData.hasChangedAttributes(); - } - - changedAttributes(): ChangedAttributesHash { - if (!this.hasRecordData) { - // no need to calculate changed attributes when calling `findRecord` - return {}; - } - return this._recordData.changedAttributes(); - } - - adapterWillCommit(): void { - this._recordData.willCommit(); - let record = this.store._instanceCache.peek({ identifier: this.identifier, bucket: 'record' }); - if (record && isDSModel(record)) { - record.errors.clear(); - } - } - - notifyHasManyChange(key: string) { - if (this.hasRecord) { - this.store._notificationManager.notify(this.identifier, 'relationships', key); - } - } - - notifyBelongsToChange(key: string) { - if (this.hasRecord) { - this.store._notificationManager.notify(this.identifier, 'relationships', key); - } - } - - notifyStateChange(key?: string) { - if (this.hasRecord) { - this.store._notificationManager.notify(this.identifier, 'state'); - } - if (!key || key === 'isDeletionCommitted') { - this.store.recordArrayManager.recordDidChange(this.identifier); - } - } - - rollbackAttributes() { - this.store._backburner.join(() => { - let dirtyKeys = this._recordData.rollbackAttributes(); - - let record = this.store._instanceCache.peek({ identifier: this.identifier, bucket: 'record' }); - if (record && isDSModel(record)) { - record.errors.clear(); - } - - if (this.hasRecord && dirtyKeys && dirtyKeys.length > 0) { - this.notifyAttributes(dirtyKeys); - } - }); - } - - removeFromInverseRelationships() { - if (this.hasRecordData) { - this.store._backburner.join(() => { - this._recordData.removeFromInverseRelationships(); - }); - } - } - - /* - When a find request is triggered on the store, the user can optionally pass in - attributes and relationships to be preloaded. These are meant to behave as if they - came back from the server, except the user obtained them out of band and is informing - the store of their existence. The most common use case is for supporting client side - nested URLs, such as `/posts/1/comments/2` so the user can do - `store.findRecord('comment', 2, { preload: { post: 1 } })` without having to fetch the post. - - Preloaded data can be attributes and relationships passed in either as IDs or as actual - models. - */ - preloadData(preload) { - let jsonPayload: JsonApiResource = {}; - //TODO(Igor) consider the polymorphic case - Object.keys(preload).forEach((key) => { - let preloadValue = preload[key]; - let relationshipMeta = this.modelClass.metaForProperty(key); - if (relationshipMeta.isRelationship) { - if (!jsonPayload.relationships) { - jsonPayload.relationships = {}; - } - jsonPayload.relationships[key] = this._preloadRelationship(key, preloadValue); - } else { - if (!jsonPayload.attributes) { - jsonPayload.attributes = {}; - } - jsonPayload.attributes[key] = preloadValue; - } - }); - this._recordData.pushData(jsonPayload); - } - - _preloadRelationship(key, preloadValue) { - let relationshipMeta = this.modelClass.metaForProperty(key); - let modelClass = relationshipMeta.type; - let data; - if (relationshipMeta.kind === 'hasMany') { - assert('You need to pass in an array to set a hasMany property on a record', Array.isArray(preloadValue)); - data = preloadValue.map((value) => this._convertPreloadRelationshipToJSON(value, modelClass)); - } else { - data = this._convertPreloadRelationshipToJSON(preloadValue, modelClass); - } - return { data }; - } - - _convertPreloadRelationshipToJSON(value, modelClass) { - if (typeof value === 'string' || typeof value === 'number') { - return { type: modelClass, id: value }; - } - let internalModel; - if (value._internalModel) { - internalModel = value._internalModel; - } else { - internalModel = value; - } - // TODO IGOR DAVID assert if no id is present - return { type: internalModel.modelName, id: internalModel.id }; - } - - /* - * calling `InstanceCache.setRecordId` is necessary to update - * the cache index for this record if we have changed. - * - * However, since the store is not aware of whether the update - * is from us (via user set) or from a push of new data - * it will also call us so that we can notify and update state. - * - * When it does so it calls with `fromCache` so that we can - * short-circuit instead of cycling back. - * - * This differs from the short-circuit in the `_isUpdatingId` - * case in that the the cache can originate the call to setId, - * so on first entry we will still need to do our own update. - */ - setId(id: string | null, fromCache: boolean = false) { - if (this._isUpdatingId === true) { - return; - } - this._isUpdatingId = true; - let didChange = id !== this._id; - this._id = id; - - if (didChange && id !== null) { - if (!fromCache) { - this.store._instanceCache.setRecordId(this.modelName, id, this.clientId); - } - // internal set of ID to get it to RecordData from DS.Model - // if we are within create we may not have a recordData yet. - if (this.hasRecordData && this._recordData.__setId) { - this._recordData.__setId(id); - } - } - - if (didChange && this.hasRecord) { - this.store._notificationManager.notify(this.identifier, 'identity'); - } - this._isUpdatingId = false; - } - - // FOR USE DURING COMMIT PROCESS - adapterDidInvalidate(error: Error & { errors?: JsonApiValidationError[]; isAdapterError?: true; code?: string }) { - if (error && error.isAdapterError === true && error.code === 'InvalidError') { - let serializer = this.store.serializerFor(this.modelName) as SerializerWithParseErrors; - - // TODO @deprecate extractErrors being called - // TODO remove extractErrors from the default serializers. - if (serializer && typeof serializer.extractErrors === 'function') { - let errorsHash = serializer.extractErrors(this.store, this.store.modelFor(this.modelName), error, this.id); - error.errors = errorsHashToArray(errorsHash); - } - } - - if (error.errors) { - assert( - `Expected the RecordData implementation for ${this.identifier} to have a getErrors(identifier) method for retreiving errors.`, - typeof this._recordData.getErrors === 'function' - ); - - let jsonApiErrors: JsonApiValidationError[] = error.errors; - if (jsonApiErrors.length === 0) { - jsonApiErrors = [{ title: 'Invalid Error', detail: '', source: { pointer: '/data' } }]; - } - this._recordData.commitWasRejected(this.identifier, jsonApiErrors); - } else { - this._recordData.commitWasRejected(this.identifier); - } - } - - toString() { - return `<${this.modelName}:${this.id}>`; - } -} - -function makeArray(value) { - return Array.isArray(value) ? value : [value]; -} - -const PRIMARY_ATTRIBUTE_KEY = 'base'; - -function errorsHashToArray(errors): JsonApiValidationError[] { - const out: JsonApiValidationError[] = []; - - if (errors) { - Object.keys(errors).forEach((key) => { - let messages = makeArray(errors[key]); - for (let i = 0; i < messages.length; i++) { - let title = 'Invalid Attribute'; - let pointer = `/data/attributes/${key}`; - if (key === PRIMARY_ATTRIBUTE_KEY) { - title = 'Invalid Document'; - pointer = `/data`; - } - out.push({ - title: title, - detail: messages[i], - source: { - pointer: pointer, - }, - }); - } - }); - } - - return out; -} diff --git a/packages/store/addon/-private/fetch-manager.ts b/packages/store/addon/-private/network/fetch-manager.ts similarity index 95% rename from packages/store/addon/-private/fetch-manager.ts rename to packages/store/addon/-private/network/fetch-manager.ts index e2695ac00ff..db236d8073d 100644 --- a/packages/store/addon/-private/fetch-manager.ts +++ b/packages/store/addon/-private/network/fetch-manager.ts @@ -19,14 +19,14 @@ import type { MinimumSerializerInterface } from '@ember-data/types/q/minimum-ser import type { FindOptions } from '@ember-data/types/q/store'; import type { Dict } from '@ember-data/types/q/utils'; -import coerceId from './coerce-id'; -import { _bind, _guard, _objectIsAlive, guardDestroyedStore } from './common'; -import type Store from './core-store'; -import ShimModelClass from './model/shim-model-class'; +import ShimModelClass from '../legacy-model-support/shim-model-class'; +import type Store from '../store-service'; +import coerceId from '../utils/coerce-id'; +import { _bind, _guard, _objectIsAlive, guardDestroyedStore } from '../utils/common'; +import { normalizeResponseHelper } from '../utils/serializer-response'; +import WeakCache from '../utils/weak-cache'; import RequestCache from './request-cache'; -import { normalizeResponseHelper } from './serializer-response'; import Snapshot from './snapshot'; -import WeakCache from './weak-cache'; function payloadIsNotBlank(adapterPayload): boolean { if (Array.isArray(adapterPayload)) { @@ -84,6 +84,10 @@ export default class FetchManager { this.isDestroyed = false; } + clearEntries(identifier: StableRecordIdentifier) { + delete this.requestCache._done[identifier.lid]; + } + /** This method is called by `record.save`, and gets passed a resolver for the promise that `record.save` returns. @@ -126,10 +130,10 @@ export default class FetchManager { let adapter = this._store.adapterFor(identifier.type); let operation = options[SaveOp]; - let internalModel = snapshot._internalModel; let modelName = snapshot.modelName; let store = this._store; let modelClass = store.modelFor(modelName); + const record = store._instanceCache.getRecord(identifier); assert(`You tried to update a record but you have no adapter (for ${modelName})`, adapter); assert( @@ -139,16 +143,16 @@ export default class FetchManager { let promise = resolve().then(() => adapter[operation](store, modelClass, snapshot)); let serializer: SerializerWithParseErrors | null = store.serializerFor(modelName); - let label = `DS: Extract and notify about ${operation} completion of ${internalModel}`; + let label = `DS: Extract and notify about ${operation} completion of ${identifier}`; assert( `Your adapter's '${operation}' method must return a value, but it returned 'undefined'`, promise !== undefined ); - promise = _guard(guardDestroyedStore(promise, store, label), _bind(_objectIsAlive, internalModel)).then( + promise = _guard(guardDestroyedStore(promise, store, label), _bind(_objectIsAlive, record)).then( (adapterPayload) => { - if (!_objectIsAlive(internalModel)) { + if (!_objectIsAlive(record)) { if (DEPRECATE_RSVP_PROMISE) { deprecate( `A Promise while saving ${modelName} did not resolve by the time your model was destroyed. This will error in a future release.`, @@ -239,11 +243,8 @@ export default class FetchManager { } let resolverPromise = resolver.promise; - - // TODO replace with some form of record state cache const store = this._store; - const internalModel = store._instanceCache.getInternalModel(identifier); - const isLoading = !internalModel.isLoaded; // we don't use isLoading directly because we are the request + const isLoading = !store._instanceCache.recordIsLoaded(identifier); // we don't use isLoading directly because we are the request const promise = resolverPromise.then( (payload) => { @@ -262,8 +263,9 @@ export default class FetchManager { return identifier; }, (error) => { - if (internalModel.isEmpty || isLoading) { - internalModel.unloadRecord(); + const recordData = store._instanceCache.peek({ identifier, bucket: 'recordData' }); + if (!recordData || recordData.isEmpty?.() || isLoading) { + store._instanceCache.unloadRecord(identifier); } throw error; } diff --git a/packages/store/addon/-private/finders.js b/packages/store/addon/-private/network/finders.js similarity index 89% rename from packages/store/addon/-private/finders.js rename to packages/store/addon/-private/network/finders.js index 1a4943b66ca..ef5f6fd8807 100644 --- a/packages/store/addon/-private/finders.js +++ b/packages/store/addon/-private/network/finders.js @@ -2,8 +2,8 @@ import { assert } from '@ember/debug'; import { Promise } from 'rsvp'; -import { guardDestroyedStore } from './common'; -import { normalizeResponseHelper } from './serializer-response'; +import { guardDestroyedStore } from '../utils/common'; +import { normalizeResponseHelper } from '../utils/serializer-response'; /** @module @ember-data/store @@ -48,6 +48,7 @@ export function _findAll(adapter, store, modelName, options) { export function _query(adapter, store, modelName, query, recordArray, options) { let modelClass = store.modelFor(modelName); // adapter.query needs the class + // TODO @deprecate RecordArrays being passed to Adapters recordArray = recordArray || store.recordArrayManager.createAdapterPopulatedRecordArray(modelName, query); let promise = Promise.resolve().then(() => adapter.query(store, modelClass, query, recordArray, options)); @@ -64,16 +65,7 @@ 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(identifiers) ); - if (recordArray) { - recordArray._setIdentifiers(identifiers, payload); - } else { - recordArray = store.recordArrayManager.createAdapterPopulatedRecordArray( - modelName, - query, - identifiers, - payload - ); - } + recordArray._setIdentifiers(identifiers, payload); return recordArray; }, diff --git a/packages/store/addon/-private/request-cache.ts b/packages/store/addon/-private/network/request-cache.ts similarity index 100% rename from packages/store/addon/-private/request-cache.ts rename to packages/store/addon/-private/network/request-cache.ts diff --git a/packages/store/addon/-private/snapshot-record-array.ts b/packages/store/addon/-private/network/snapshot-record-array.ts similarity index 96% rename from packages/store/addon/-private/snapshot-record-array.ts rename to packages/store/addon/-private/network/snapshot-record-array.ts index 2f48b463f83..db0285973e8 100644 --- a/packages/store/addon/-private/snapshot-record-array.ts +++ b/packages/store/addon/-private/network/snapshot-record-array.ts @@ -9,7 +9,7 @@ import type { ModelSchema } from '@ember-data/types/q/ds-model'; import type { FindOptions } from '@ember-data/types/q/store'; import type { Dict } from '@ember-data/types/q/utils'; -import type RecordArray from './record-arrays/record-array'; +import type RecordArray from '../record-arrays/record-array'; import type Snapshot from './snapshot'; /** SnapshotRecordArray is not directly instantiable. @@ -77,7 +77,7 @@ export default class SnapshotRecordArray { @public @type {Number} */ - this.length = recordArray.get('length'); + this.length = recordArray.length as unknown as number; // deal with computedProperty shennanigans /** Meta objects for the record array. @@ -220,7 +220,7 @@ if (DEPRECATE_SNAPSHOT_MODEL_CLASS_ACCESS) { since: { available: '4.5.0', enabled: '4.5.0' }, } ); - return this._recordArray.get('type'); + return this._recordArray.type; }, }); } diff --git a/packages/store/addon/-private/snapshot.ts b/packages/store/addon/-private/network/snapshot.ts similarity index 91% rename from packages/store/addon/-private/snapshot.ts rename to packages/store/addon/-private/network/snapshot.ts index 67813e09b86..4f9133f2b44 100644 --- a/packages/store/addon/-private/snapshot.ts +++ b/packages/store/addon/-private/network/snapshot.ts @@ -2,7 +2,6 @@ @module @ember-data/store */ import { assert, deprecate } from '@ember/debug'; -import { get } from '@ember/object'; import { importSync } from '@embroider/macros'; @@ -10,11 +9,7 @@ import { HAS_RECORD_DATA_PACKAGE } from '@ember-data/private-build-infra'; import { DEPRECATE_SNAPSHOT_MODEL_CLASS_ACCESS } from '@ember-data/private-build-infra/deprecations'; import type BelongsToRelationship from '@ember-data/record-data/addon/-private/relationships/state/belongs-to'; import type ManyRelationship from '@ember-data/record-data/addon/-private/relationships/state/has-many'; -import type { DSModel, DSModelSchema, ModelSchema } from '@ember-data/types/q/ds-model'; -import type { - ExistingResourceIdentifierObject, - NewResourceIdentifierObject, -} from '@ember-data/types/q/ember-data-json-api'; +import type { DSModelSchema, ModelSchema } from '@ember-data/types/q/ds-model'; import type { StableRecordIdentifier } from '@ember-data/types/q/identifier'; import type { OptionsHash } from '@ember-data/types/q/minimum-serializer-interface'; import type { ChangedAttributesHash } from '@ember-data/types/q/record-data'; @@ -23,8 +18,7 @@ import type { RecordInstance } from '@ember-data/types/q/record-instance'; import type { FindOptions } from '@ember-data/types/q/store'; import type { Dict } from '@ember-data/types/q/utils'; -import type Store from './core-store'; -import type InternalModel from './model/internal-model'; +import type Store from '../store-service'; type RecordId = string | null; @@ -46,7 +40,6 @@ export default class Snapshot implements Snapshot { private _belongsToIds: Dict = Object.create(null); private _hasManyRelationships: Dict = Object.create(null); private _hasManyIds: Dict = Object.create(null); - declare _internalModel: InternalModel; declare _changedAttributes: ChangedAttributesHash; declare identifier: StableRecordIdentifier; @@ -64,7 +57,7 @@ export default class Snapshot implements Snapshot { * @param _store */ constructor(options: FindOptions, identifier: StableRecordIdentifier, private _store: Store) { - let internalModel = (this._internalModel = _store._instanceCache._internalModelForResource(identifier)); + const hasRecord = !!_store._instanceCache.peek({ identifier, bucket: 'record' }); this.modelName = identifier.type; /** @@ -83,7 +76,7 @@ export default class Snapshot implements Snapshot { in time" in which a snapshot is created, we greedily grab the values. */ - if (internalModel.hasRecord) { + if (hasRecord) { this._attributes; } @@ -129,7 +122,7 @@ export default class Snapshot implements Snapshot { @public */ this.modelName = identifier.type; - if (internalModel.hasRecord) { + if (hasRecord) { this._changedAttributes = this._store._instanceCache.getRecordData(identifier).changedAttributes(); } } @@ -160,10 +153,12 @@ export default class Snapshot implements Snapshot { let attributes = (this.__attributes = Object.create(null)); let attrs = Object.keys(this._store.getSchemaDefinitionService().attributesDefinitionFor(this.identifier)); let recordData = this._store._instanceCache.getRecordData(this.identifier); + const modelClass = this._store.modelFor(this.identifier.type); + const isDSModel = schemaIsDSModel(modelClass); attrs.forEach((keyName) => { - if (schemaIsDSModel(this._internalModel.modelClass)) { + if (isDSModel) { // if the schema is for a DSModel then the instance is too - attributes[keyName] = get(record as DSModel, keyName); + attributes[keyName] = record[keyName]; } else { attributes[keyName] = recordData.getAttr(keyName); } @@ -182,7 +177,8 @@ export default class Snapshot implements Snapshot { */ get isNew(): boolean { - return this._internalModel.isNew(); + const recordData = this._store._instanceCache.peek({ identifier: this.identifier, bucket: 'recordData' }); + return recordData?.isNew?.() || false; } /** @@ -297,9 +293,8 @@ export default class Snapshot implements Snapshot { */ belongsTo(keyName: string, options?: { id?: boolean }): Snapshot | RecordId | undefined { let returnModeIsId = !!(options && options.id); - let inverseInternalModel: InternalModel | null; let result: Snapshot | RecordId | undefined; - let store = this._internalModel.store; + let store = this._store; if (returnModeIsId === true && keyName in this._belongsToIds) { return this._belongsToIds[keyName]; @@ -319,7 +314,7 @@ export default class Snapshot implements Snapshot { // TODO @runspired it seems this code branch would not work with CUSTOM_MODEL_CLASSes // this check is not a regression in behavior because relationships don't currently - // function without access to intimate API contracts between RecordData and InternalModel. + // function without access to intimate API contracts between RecordData and Model. // This is a requirement we should fix as soon as the relationship layer does not require // this intimate API usage. if (!HAS_RECORD_DATA_PACKAGE) { @@ -344,14 +339,14 @@ export default class Snapshot implements Snapshot { let value = relationship.getData(); let data = value && value.data; - inverseInternalModel = data ? store._instanceCache._internalModelForResource(data) : null; + let inverseIdentifier = data ? store.identifierCache.getOrCreateRecordIdentifier(data) : null; if (value && value.data !== undefined) { - if (inverseInternalModel && !inverseInternalModel.isDeleted()) { + if (inverseIdentifier && !store._instanceCache.getRecordData(inverseIdentifier).isDeleted?.()) { if (returnModeIsId) { - result = inverseInternalModel.id; + result = inverseIdentifier.id; } else { - result = store._instanceCache.createSnapshot(inverseInternalModel.identifier); + result = store._instanceCache.createSnapshot(inverseIdentifier); } } else { result = null; @@ -411,7 +406,7 @@ export default class Snapshot implements Snapshot { return cachedSnapshots; } - let store = this._internalModel.store; + let store = this._store; let relationshipMeta = store.getSchemaDefinitionService().relationshipsDefinitionFor({ type: this.modelName })[ keyName ]; @@ -422,7 +417,7 @@ export default class Snapshot implements Snapshot { // TODO @runspired it seems this code branch would not work with CUSTOM_MODEL_CLASSes // this check is not a regression in behavior because relationships don't currently - // function without access to intimate API contracts between RecordData and InternalModel. + // function without access to intimate API contracts between RecordData and Model. // This is a requirement we should fix as soon as the relationship layer does not require // this intimate API usage. if (!HAS_RECORD_DATA_PACKAGE) { @@ -448,14 +443,12 @@ export default class Snapshot implements Snapshot { if (value.data) { results = []; value.data.forEach((member) => { - let internalModel = store._instanceCache._internalModelForResource(member); - if (!internalModel.isDeleted()) { + let inverseIdentifier = store.identifierCache.getOrCreateRecordIdentifier(member); + if (!store._instanceCache.getRecordData(inverseIdentifier).isDeleted?.()) { if (returnModeIsIds) { - (results as RecordId[]).push( - (member as ExistingResourceIdentifierObject | NewResourceIdentifierObject).id || null - ); + (results as RecordId[]).push(inverseIdentifier.id); } else { - (results as Snapshot[]).push(store._instanceCache.createSnapshot(internalModel.identifier)); + (results as Snapshot[]).push(store._instanceCache.createSnapshot(inverseIdentifier)); } } }); @@ -566,7 +559,7 @@ if (DEPRECATE_SNAPSHOT_MODEL_CLASS_ACCESS) { since: { available: '4.5.0', enabled: '4.5.0' }, } ); - return this._internalModel.modelClass; + return this._store.modelFor(this.identifier.type); }, }); } diff --git a/packages/store/addon/-private/promise-proxies.ts b/packages/store/addon/-private/proxies/promise-proxies.ts similarity index 96% rename from packages/store/addon/-private/promise-proxies.ts rename to packages/store/addon/-private/proxies/promise-proxies.ts index edc5ebd49d7..9896835b125 100644 --- a/packages/store/addon/-private/promise-proxies.ts +++ b/packages/store/addon/-private/proxies/promise-proxies.ts @@ -31,10 +31,10 @@ import { PromiseArrayProxy, PromiseObjectProxy } from './promise-proxy-base'; promise: $.getJSON('/some/remote/data.json') }); - promiseArray.get('length'); // 0 + promiseArray.length; // 0 promiseArray.then(function() { - promiseArray.get('length'); // 100 + promiseArray.length; // 100 }); ``` @@ -77,10 +77,10 @@ export class PromiseArray> extends PromiseArrayPr promise: $.getJSON('/some/remote/data.json') }); - promiseObject.get('name'); // null + promiseObject.name; // null promiseObject.then(function() { - promiseObject.get('name'); // 'Tomster' + promiseObject.name; // 'Tomster' }); ``` diff --git a/packages/store/addon/-private/promise-proxy-base.d.ts b/packages/store/addon/-private/proxies/promise-proxy-base.d.ts similarity index 100% rename from packages/store/addon/-private/promise-proxy-base.d.ts rename to packages/store/addon/-private/proxies/promise-proxy-base.d.ts diff --git a/packages/store/addon/-private/promise-proxy-base.js b/packages/store/addon/-private/proxies/promise-proxy-base.js similarity index 100% rename from packages/store/addon/-private/promise-proxy-base.js rename to packages/store/addon/-private/proxies/promise-proxy-base.js diff --git a/packages/store/addon/-private/record-arrays/adapter-populated-record-array.ts b/packages/store/addon/-private/record-arrays/adapter-populated-record-array.ts index 39fd15b0bca..6145279da53 100644 --- a/packages/store/addon/-private/record-arrays/adapter-populated-record-array.ts +++ b/packages/store/addon/-private/record-arrays/adapter-populated-record-array.ts @@ -7,11 +7,11 @@ import type { RecordInstance } from '@ember-data/types/q/record-instance'; import type { FindOptions } from '@ember-data/types/q/store'; import type { Dict } from '@ember-data/types/q/utils'; -import type Store from '../core-store'; -import type { PromiseArray } from '../promise-proxies'; -import { promiseArray } from '../promise-proxies'; -import type RecordArrayManager from '../record-array-manager'; -import SnapshotRecordArray from '../snapshot-record-array'; +import type RecordArrayManager from '../managers/record-array-manager'; +import SnapshotRecordArray from '../network/snapshot-record-array'; +import type { PromiseArray } from '../proxies/promise-proxies'; +import { promiseArray } from '../proxies/promise-proxies'; +import type Store from '../store-service'; import RecordArray from './record-array'; export interface AdapterPopulatedRecordArrayCreateArgs { @@ -48,20 +48,18 @@ export interface AdapterPopulatedRecordArrayCreateArgs { // GET /users?isAdmin=true store.query('user', { isAdmin: true }).then(function(admins) { - admins.then(function() { - console.log(admins.get("length")); // 42 - }); + admins.get("length"); // 42 // somewhere later in the app code, when new admins have been created // in the meantime // // GET /users?isAdmin=true admins.update().then(function() { - admins.get('isUpdating'); // false - console.log(admins.get("length")); // 123 + admins.isUpdating; // false + admins.get("length"); // 123 }); - admins.get('isUpdating'); // true + admins.isUpdating; // true } ``` diff --git a/packages/store/addon/-private/record-arrays/record-array.ts b/packages/store/addon/-private/record-arrays/record-array.ts index b477d07e686..ce7250a85c9 100644 --- a/packages/store/addon/-private/record-arrays/record-array.ts +++ b/packages/store/addon/-private/record-arrays/record-array.ts @@ -4,7 +4,7 @@ import type NativeArray from '@ember/array/-private/native-array'; import ArrayProxy from '@ember/array/proxy'; import { assert, deprecate } from '@ember/debug'; -import { get, set } from '@ember/object'; +import { set } from '@ember/object'; import { tracked } from '@glimmer/tracking'; import { Promise } from 'rsvp'; @@ -16,10 +16,10 @@ import type { StableRecordIdentifier } from '@ember-data/types/q/identifier'; import type { RecordInstance } from '@ember-data/types/q/record-instance'; import type { FindOptions } from '@ember-data/types/q/store'; -import type Store from '../core-store'; -import type { PromiseArray } from '../promise-proxies'; -import { promiseArray } from '../promise-proxies'; -import SnapshotRecordArray from '../snapshot-record-array'; +import SnapshotRecordArray from '../network/snapshot-record-array'; +import type { PromiseArray } from '../proxies/promise-proxies'; +import { promiseArray } from '../proxies/promise-proxies'; +import type Store from '../store-service'; function recordForIdentifier(store: Store, identifier: StableRecordIdentifier): RecordInstance { return store._instanceCache.getRecord(identifier); @@ -67,7 +67,7 @@ export default class RecordArray extends ArrayProxy); set(this, 'length', 0); super.willDestroy(); diff --git a/packages/store/addon/-private/record-notification-manager.ts b/packages/store/addon/-private/record-notification-manager.ts deleted file mode 100644 index e555d2247f1..00000000000 --- a/packages/store/addon/-private/record-notification-manager.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { DEBUG } from '@glimmer/env'; - -import type { RecordIdentifier, StableRecordIdentifier } from '@ember-data/types/q/identifier'; - -import type Store from './core-store'; -import WeakCache from './weak-cache'; - -type UnsubscribeToken = Object; - -const Cache = new WeakCache>( - DEBUG ? 'subscribers' : '' -); -Cache._generator = () => new Map(); -const Tokens = new WeakCache(DEBUG ? 'identifier' : ''); - -export type NotificationType = - | 'attributes' - | 'relationships' - | 'identity' - | 'errors' - | 'meta' - | 'unload' - | 'state' - | 'property'; // 'property' is an internal EmberData only transition period concept. - -export interface NotificationCallback { - (identifier: RecordIdentifier, notificationType: 'attributes' | 'relationships' | 'property', key?: string): void; - (identifier: RecordIdentifier, notificationType: 'errors' | 'meta' | 'identity' | 'unload' | 'state'): void; - (identifier: StableRecordIdentifier, notificationType: NotificationType, key?: string): void; -} - -export function unsubscribe(token: UnsubscribeToken) { - let identifier = Tokens.get(token); - if (!identifier) { - throw new Error('Passed unknown unsubscribe token to unsubscribe'); - } - Tokens.delete(token); - const map = Cache.get(identifier); - map?.delete(token); -} -/* - Currently only support a single callback per identifier -*/ -export default class NotificationManager { - constructor(private store: Store) {} - - subscribe(identifier: RecordIdentifier, callback: NotificationCallback): UnsubscribeToken { - let stableIdentifier = this.store.identifierCache.getOrCreateRecordIdentifier(identifier); - let map = Cache.lookup(stableIdentifier); - let unsubToken = {}; - map.set(unsubToken, callback); - Tokens.set(unsubToken, stableIdentifier); - return unsubToken; - } - - unsubscribe(token: UnsubscribeToken) { - unsubscribe(token); - } - - notify(identifier: RecordIdentifier, value: 'attributes' | 'relationships' | 'property', key?: string): boolean; - notify(identifier: RecordIdentifier, value: 'errors' | 'meta' | 'identity' | 'unload' | 'state'): boolean; - notify(identifier: RecordIdentifier, value: NotificationType, key?: string): boolean { - let stableIdentifier = this.store.identifierCache.getOrCreateRecordIdentifier(identifier); - let callbackMap = Cache.get(stableIdentifier); - if (!callbackMap || !callbackMap.size) { - return false; - } - callbackMap.forEach((cb) => { - cb(stableIdentifier, value, key); - }); - return true; - } -} diff --git a/packages/store/addon/-private/core-store.ts b/packages/store/addon/-private/store-service.ts similarity index 90% rename from packages/store/addon/-private/core-store.ts rename to packages/store/addon/-private/store-service.ts index 9efb8d2f35b..6948bddd6bf 100644 --- a/packages/store/addon/-private/core-store.ts +++ b/packages/store/addon/-private/store-service.ts @@ -3,7 +3,7 @@ */ import { getOwner, setOwner } from '@ember/application'; import { assert, deprecate } from '@ember/debug'; -import { _backburner as emberBackburner } from '@ember/runloop'; +import { _backburner as emberBackburner, run } from '@ember/runloop'; import type { Backburner } from '@ember/runloop/-private/backburner'; import Service from '@ember/service'; import { registerWaiter, unregisterWaiter } from '@ember/test'; @@ -17,7 +17,6 @@ import { HAS_MODEL_PACKAGE, HAS_RECORD_DATA_PACKAGE } from '@ember-data/private- import { DEPRECATE_HAS_RECORD, DEPRECATE_JSON_API_FALLBACK, - DEPRECATE_RECORD_WAS_INVALID, DEPRECATE_STORE_FIND, } from '@ember-data/private-build-infra/deprecations'; import type { RecordData as RecordDataClass } from '@ember-data/record-data/-private'; @@ -33,38 +32,41 @@ import type { StableExistingRecordIdentifier, StableRecordIdentifier } from '@em import type { MinimumAdapterInterface } from '@ember-data/types/q/minimum-adapter-interface'; import type { MinimumSerializerInterface } from '@ember-data/types/q/minimum-serializer-interface'; import type { RecordData } from '@ember-data/types/q/record-data'; -import type { RecordDataRecordWrapper } from '@ember-data/types/q/record-data-record-wrapper'; +import { JsonApiValidationError } from '@ember-data/types/q/record-data-json-api'; +import type { RecordDataWrapper } from '@ember-data/types/q/record-data-record-wrapper'; import type { RecordInstance } from '@ember-data/types/q/record-instance'; import type { SchemaDefinitionService } from '@ember-data/types/q/schema-definition-service'; import type { FindOptions } from '@ember-data/types/q/store'; import type { Dict } from '@ember-data/types/q/utils'; import edBackburner from './backburner'; -import coerceId, { ensureStringId } from './coerce-id'; -import FetchManager, { SaveOp } from './fetch-manager'; -import { _findAll, _query, _queryRecord } from './finders'; -import { IdentifierCache } from './identifier-cache'; -import { InstanceCache, storeFor, StoreMap } from './instance-cache'; +import { IdentifierCache } from './caches/identifier-cache'; import { - internalModelFactoryFor, + InstanceCache, peekRecordIdentifier, + recordDataIsFullyDeleted, recordIdentifierFor, setRecordIdentifier, -} from './internal-model-factory'; -import RecordReference from './model/record-reference'; -import type ShimModelClass from './model/shim-model-class'; -import { getShimClass } from './model/shim-model-class'; -import normalizeModelName from './normalize-model-name'; -import { PromiseArray, promiseArray, PromiseObject, promiseObject } from './promise-proxies'; -import RecordArrayManager from './record-array-manager'; + storeFor, + StoreMap, +} from './caches/instance-cache'; +import { setRecordDataFor } from './caches/record-data-for'; +import RecordReference from './legacy-model-support/record-reference'; +import { DSModelSchemaDefinitionService, getModelFactory } from './legacy-model-support/schema-definition-service'; +import type ShimModelClass from './legacy-model-support/shim-model-class'; +import { getShimClass } from './legacy-model-support/shim-model-class'; +import RecordArrayManager from './managers/record-array-manager'; +import RecordDataStoreWrapper from './managers/record-data-store-wrapper'; +import NotificationManager from './managers/record-notification-manager'; +import FetchManager, { SaveOp } from './network/fetch-manager'; +import { _findAll, _query, _queryRecord } from './network/finders'; +import type RequestCache from './network/request-cache'; +import { PromiseArray, promiseArray, PromiseObject, promiseObject } from './proxies/promise-proxies'; import AdapterPopulatedRecordArray from './record-arrays/adapter-populated-record-array'; import RecordArray from './record-arrays/record-array'; -import { setRecordDataFor } from './record-data-for'; -import RecordDataStoreWrapper from './record-data-store-wrapper'; -import NotificationManager from './record-notification-manager'; -import type RequestCache from './request-cache'; -import { DSModelSchemaDefinitionService, getModelFactory } from './schema-definition-service'; +import coerceId, { ensureStringId } from './utils/coerce-id'; import constructResource from './utils/construct-resource'; +import normalizeModelName from './utils/normalize-model-name'; import promiseRecord from './utils/promise-record'; export { storeFor }; @@ -191,6 +193,7 @@ class Store extends Service { declare _trackAsyncRequestStart: (str: string) => void; declare _trackAsyncRequestEnd: (token: AsyncTrackingToken) => void; declare __asyncWaiter: () => boolean; + declare DISABLE_WAITER?: boolean; /** @method init @@ -268,7 +271,7 @@ class Store extends Service { this.__asyncWaiter = () => { let tracked = this._trackedAsyncRequests; - return tracked.length === 0; + return this.DISABLE_WAITER || tracked.length === 0; }; registerWaiter(this.__asyncWaiter); @@ -282,23 +285,22 @@ class Store extends Service { instantiateRecord( identifier: StableRecordIdentifier, createRecordArgs: { [key: string]: unknown }, - recordDataFor: (identifier: StableRecordIdentifier) => RecordDataRecordWrapper, + recordDataFor: (identifier: StableRecordIdentifier) => RecordDataWrapper, notificationManager: NotificationManager ): DSModel | RecordInstance { if (HAS_MODEL_PACKAGE) { let modelName = identifier.type; let store = this; - let internalModel = this._instanceCache._internalModelForResource(identifier); + let recordData = this._instanceCache.getRecordData(identifier); let createOptions: any = { - _internalModel: internalModel, // TODO deprecate allowing unknown args setting _createProps: createRecordArgs, // TODO @deprecate consider deprecating accessing record properties during init which the below is necessary for _secretInit: (record: RecordInstance): void => { setRecordIdentifier(record, identifier); StoreMap.set(record, store); - setRecordDataFor(record, internalModel._recordData); + setRecordDataFor(record, recordData); }, container: null, // necessary hack for setOwner? }; @@ -306,7 +308,6 @@ class Store extends Service { // ensure that `getOwner(this)` works inside a model instance setOwner(createOptions, getOwner(this)); delete createOptions.container; - // TODO this needs to not use the private property here to get modelFactoryCache so as to not break interop return getModelFactory(this, this._modelFactoryCache, modelName).create(createOptions); } assert(`You must implement the store's instantiateRecord hook for your custom model class.`); @@ -326,6 +327,8 @@ class Store extends Service { getSchemaDefinitionService(): SchemaDefinitionService { if (HAS_MODEL_PACKAGE && !this._schemaDefinitionService) { + // it is potentially a mistake for the RFC to have not enabled chaining these services, though highlander rule is nice. + // what ember-m3 did via private API to allow both worlds to interop would be much much harder using this. this._schemaDefinitionService = new DSModelSchemaDefinitionService(this); } assert( @@ -367,10 +370,6 @@ class Store extends Service { ); if (HAS_MODEL_PACKAGE) { let normalizedModelName = normalizeModelName(modelName); - // TODO this is safe only because - // apps would be horribly broken if the schema service were using DS_MODEL but not using DS_MODEL's schema service. - // it is potentially a mistake for the RFC to have not enabled chaining these services, though highlander rule is nice. - // what ember-m3 did via private API to allow both worlds to interop would be much much harder using this. let maybeFactory = getModelFactory(this, this._modelFactoryCache, normalizedModelName); // for factorFor factory/class split @@ -461,12 +460,20 @@ class Store extends Service { // Coerce ID to a string properties.id = coerceId(properties.id); + const resource = { type: normalizedModelName, id: properties.id }; - const factory = internalModelFactoryFor(this); - const internalModel = factory.build({ type: normalizedModelName, id: properties.id }); - const { identifier } = internalModel; + if (resource.id) { + const identifier = this.identifierCache.peekRecordIdentifier(resource as ResourceIdentifierObject); - this._instanceCache.getRecordData(identifier).clientDidCreate(); + assert( + `The id ${properties.id} has already been used with another '${normalizedModelName}' record.`, + !identifier + ); + } + + const identifier = this.identifierCache.createIdentifierForNewRecord(resource); + const recordData = this._instanceCache.getRecordData(identifier); + recordData.clientDidCreate(); this.recordArrayManager.recordDidChange(identifier); return this._instanceCache.getRecord(identifier, properties); @@ -495,13 +502,27 @@ class Store extends Service { if (DEBUG) { assertDestroyingStore(this, 'deleteRecord'); } + // TODO eliminate this interleaving + // it is unlikely we need both an outer join and the inner run + // of our own queue this._backburner.join(() => { - let identifier = peekRecordIdentifier(record); + const identifier = peekRecordIdentifier(record); if (identifier) { - let internalModel = internalModelFactoryFor(this).peek(identifier); - if (internalModel) { - internalModel.deleteRecord(); - } + run(() => { + const backburner = this._backburner; + backburner.run(() => { + const recordData = this._instanceCache.peek({ identifier, bucket: 'recordData' }); + + if (recordData) { + if (recordData?.setIsDeleted) { + recordData.setIsDeleted(true); + } + if (recordData.isNew?.()) { + this._instanceCache.unloadRecord(identifier); + } + } + }); + }); } }); } @@ -528,10 +549,7 @@ class Store extends Service { } let identifier = peekRecordIdentifier(record); if (identifier) { - let internalModel = internalModelFactoryFor(this).peek(identifier); - if (internalModel) { - internalModel.unloadRecord(); - } + this._instanceCache.unloadRecord(identifier); } } @@ -745,7 +763,7 @@ class Store extends Service { // } // ] store.findRecord('post', 1, { reload: true }).then(function(post) { - post.get('revision'); // 2 + post.revision; // 2 }); ``` @@ -783,7 +801,7 @@ class Store extends Service { }); let blogPost = store.findRecord('post', 1).then(function(post) { - post.get('revision'); // 1 + post.revision; // 1 }); // later, once adapter#findRecord resolved with @@ -795,7 +813,7 @@ class Store extends Service { // } // ] - blogPost.get('revision'); // 2 + blogPost.revision; // 2 ``` If you would like to force or prevent background reloading, you can set a @@ -983,13 +1001,12 @@ class Store extends Service { resource = constructResource(type, normalizedId); } - const internalModel = internalModelFactoryFor(this).lookup(resource); - const { identifier } = internalModel; + const identifier = this.identifierCache.getOrCreateRecordIdentifier(resource); let promise; options = options || {}; // if not loaded start loading - if (!internalModel.isLoaded) { + if (!this._instanceCache.recordIsLoaded(identifier)) { promise = this._instanceCache._fetchDataIfNeededForIdentifier(identifier, options); // Refetch if the reload option is passed @@ -1144,10 +1161,10 @@ class Store extends Service { peekRecord(identifier: ResourceIdentifierObject | string, id?: string | number): RecordInstance | null { if (arguments.length === 1 && isMaybeIdentifier(identifier)) { const stableIdentifier = this.identifierCache.peekRecordIdentifier(identifier); - const internalModel = stableIdentifier && internalModelFactoryFor(this).peek(stableIdentifier); + const isLoaded = stableIdentifier && this._instanceCache.recordIsLoaded(stableIdentifier); // TODO come up with a better mechanism for determining if we have data and could peek. // this is basically an "are we not empty" query. - return internalModel && internalModel.isLoaded ? this._instanceCache.getRecord(stableIdentifier) : null; + return isLoaded ? this._instanceCache.getRecord(stableIdentifier) : null; } if (DEBUG) { @@ -1164,9 +1181,9 @@ class Store extends Service { const normalizedId = ensureStringId(id); const resource = { type, id: normalizedId }; const stableIdentifier = this.identifierCache.peekRecordIdentifier(resource); - const internalModel = stableIdentifier && internalModelFactoryFor(this).peek(stableIdentifier); + const isLoaded = stableIdentifier && this._instanceCache.recordIsLoaded(stableIdentifier); - return internalModel && internalModel.isLoaded ? this._instanceCache.getRecord(stableIdentifier) : null; + return isLoaded ? this._instanceCache.getRecord(stableIdentifier) : null; } /** @@ -1211,9 +1228,7 @@ class Store extends Service { const resource = { type, id: trueId }; const identifier = this.identifierCache.peekRecordIdentifier(resource); - const internalModel = identifier && internalModelFactoryFor(this).peek(identifier); - - return !!internalModel && internalModel.isLoaded; + return Boolean(identifier && this._instanceCache.recordIsLoaded(identifier)); } assert(`store.hasRecordForId has been removed`); } @@ -1336,8 +1351,8 @@ class Store extends Service { ```javascript store.queryRecord('user', {}).then(function(user) { - let username = user.get('username'); - console.log(`Currently logged in as ${username}`); + let username = user.username; + // do thing }); ``` @@ -1374,9 +1389,9 @@ class Store extends Service { ```javascript store.query('user', { username: 'unique' }).then(function(users) { - return users.get('firstObject'); + return users.firstObject; }).then(function(user) { - let id = user.get('id'); + let id = user.id; }); ``` @@ -1394,7 +1409,7 @@ class Store extends Service { ```javascript store.queryRecord('user', { username: 'unique' }).then(function(user) { - console.log(user); // null + // user is null }); ``` @@ -1752,50 +1767,14 @@ class Store extends Service { !modelName || typeof modelName === 'string' ); - const factory = internalModelFactoryFor(this); - if (modelName === undefined) { - factory.clear(); + this._instanceCache.clear(); } else { let normalizedModelName = normalizeModelName(modelName); - factory.clear(normalizedModelName); + this._instanceCache.clear(normalizedModelName); } } - /** - This method is called once the promise returned by an - adapter's `createRecord`, `updateRecord` or `deleteRecord` - is rejected with a `InvalidError`. - - @method recordWasInvalid - @private - @deprecated - @param {InternalModel} internalModel - @param {Object} errors - */ - recordWasInvalid(internalModel, parsedErrors, error) { - if (DEPRECATE_RECORD_WAS_INVALID) { - deprecate( - `The private API recordWasInvalid will be removed in an upcoming release. Use record.errors add/remove instead if the intent was to move the record into an invalid state manually.`, - false, - { - id: 'ember-data:deprecate-record-was-invalid', - for: 'ember-data', - until: '5.0', - since: { enabled: '4.5', available: '4.5' }, - } - ); - if (DEBUG) { - assertDestroyingStore(this, 'recordWasInvalid'); - } - error = error || new Error(`unknown invalid error`); - error = typeof error === 'string' ? new Error(error) : error; - error._parsedErrors = parsedErrors; - internalModel.adapterDidInvalidate(error); - } - assert(`store.recordWasInvalid has been removed`); - } - /** Push some data for a given type into the store. @@ -1975,7 +1954,7 @@ class Store extends Service { @method _push @private @param {Object} jsonApiDoc - @return {InternalModel|Array} pushed InternalModel(s) + @return {StableRecordIdentifier|Array} identifiers for the primary records that had data loaded */ _push(jsonApiDoc): StableExistingRecordIdentifier | StableExistingRecordIdentifier[] | null { if (DEBUG) { @@ -1987,7 +1966,7 @@ class Store extends Service { if (included) { for (i = 0, length = included.length; i < length; i++) { - this._instanceCache._load(included[i]); + this._instanceCache.loadData(included[i]); } } @@ -1996,7 +1975,7 @@ class Store extends Service { let identifiers = new Array(length); for (i = 0; i < length; i++) { - identifiers[i] = this._instanceCache._load(jsonApiDoc.data[i]); + identifiers[i] = this._instanceCache.loadData(jsonApiDoc.data[i]); } return identifiers; } @@ -2012,7 +1991,7 @@ class Store extends Service { typeof jsonApiDoc.data === 'object' ); - return this._instanceCache._load(jsonApiDoc.data); + return this._instanceCache.loadData(jsonApiDoc.data); }); // this typecast is necessary because `backburner.join` is mistyped to return void @@ -2115,37 +2094,35 @@ class Store extends Service { saveRecord(record: RecordInstance, options: Dict = {}): Promise { assert(`Unable to initate save for a record in a disconnected state`, storeFor(record)); let identifier = recordIdentifierFor(record); - let internalModel = identifier && internalModelFactoryFor(this).peek(identifier)!; + let recordData = identifier && this._instanceCache.peek({ identifier, bucket: 'recordData' }); - if (!internalModel) { + if (!recordData) { // this commonly means we're disconnected // but just in case we reject here to prevent bad things. return reject(`Record Is Disconnected`); } // TODO we used to check if the record was destroyed here - // Casting can be removed once REQUEST_SERVICE ff is turned on - // because a `Record` is provided there will always be a matching internalModel - assert( `Cannot initiate a save request for an unloaded record: ${identifier}`, - !internalModel.isEmpty && !internalModel.isDestroyed + recordData && this._instanceCache.recordIsLoaded(identifier) ); - if (internalModel._isRecordFullyDeleted()) { + if (recordDataIsFullyDeleted(this._instanceCache, identifier)) { return resolve(record); } - internalModel.adapterWillCommit(); + recordData.willCommit(); + if (isDSModel(record)) { + record.errors.clear(); + } if (!options) { options = {}; } - let recordData = this._instanceCache.getRecordData(identifier); let operation: 'createRecord' | 'deleteRecord' | 'updateRecord' = 'updateRecord'; - // TODO handle missing isNew - if (recordData.isNew && recordData.isNew()) { + if (recordData.isNew?.()) { operation = 'createRecord'; - } else if (recordData.isDeleted && recordData.isDeleted()) { + } else if (recordData.isDeleted?.()) { operation = 'deleteRecord'; } @@ -2173,20 +2150,21 @@ class Store extends Service { let data = payload && payload.data; if (!data) { assert( - `Your ${internalModel.modelName} record was saved to the server, but the response does not have an id and no id has been set client side. Records must have ids. Please update the server response to provide an id in the response or generate the id on the client side either before saving the record or while normalizing the response.`, - internalModel.id + `Your ${identifier.type} record was saved to the server, but the response does not have an id and no id has been set client side. Records must have ids. Please update the server response to provide an id in the response or generate the id on the client side either before saving the record or while normalizing the response.`, + identifier.id ); } const cache = this.identifierCache; + let actualIdentifier = identifier; if (operation !== 'deleteRecord' && data) { - cache.updateRecordIdentifier(identifier, data); + actualIdentifier = cache.updateRecordIdentifier(identifier, data); } //We first make sure the primary data has been updated - //TODO try to move notification to the user to the end of the runloop - internalModel._recordData.didCommit(data); - this.recordArrayManager.recordDidChange(internalModel.identifier); + const recordData = this._instanceCache.getRecordData(actualIdentifier); + recordData.didCommit(data); + this.recordArrayManager.recordDidChange(actualIdentifier); if (payload && payload.included) { this._push({ data: null, included: payload.included }); @@ -2201,7 +2179,7 @@ class Store extends Service { } else if (typeof e === 'string') { err = new Error(e); } - internalModel.adapterDidInvalidate(err); + adapterDidInvalidate(this, identifier, err); throw err; } ); @@ -2215,13 +2193,13 @@ class Store extends Service { * @public * @param modelName * @param id - * @param clientId + * @param lid * @param storeWrapper */ createRecordDataFor( modelName: string, id: string | null, - clientId: string, + lid: string, storeWrapper: RecordDataStoreWrapper ): RecordData { if (HAS_RECORD_DATA_PACKAGE) { @@ -2233,13 +2211,13 @@ class Store extends Service { if (_RecordData === undefined) { _RecordData = ( importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private') - ).RecordData as RecordDataConstruct; + ).RecordData; } let identifier = this.identifierCache.getOrCreateRecordIdentifier({ type: modelName, id, - lid: clientId, + lid, }); return new _RecordData(identifier, storeWrapper); } @@ -2515,3 +2493,84 @@ export function assertIdentifierHasId( ): asserts identifier is StableExistingRecordIdentifier { assert(`Attempted to schedule a fetch for a record without an id.`, identifier.id !== null); } + +function isDSModel(record: RecordInstance | null): record is DSModel { + return ( + HAS_MODEL_PACKAGE && + !!record && + 'constructor' in record && + 'isModel' in record.constructor && + record.constructor.isModel === true + ); +} + +type AdapterErrors = Error & { errors?: unknown[]; isAdapterError?: true; code?: string }; +type SerializerWithParseErrors = MinimumSerializerInterface & { + extractErrors?(store: Store, modelClass: ShimModelClass, error: AdapterErrors, recordId: string | null): any; +}; + +function adapterDidInvalidate( + store: Store, + identifier: StableRecordIdentifier, + error: Error & { errors?: JsonApiValidationError[]; isAdapterError?: true; code?: string } +) { + if (error && error.isAdapterError === true && error.code === 'InvalidError') { + let serializer = store.serializerFor(identifier.type) as SerializerWithParseErrors; + + // TODO @deprecate extractErrors being called + // TODO remove extractErrors from the default serializers. + if (serializer && typeof serializer.extractErrors === 'function') { + let errorsHash = serializer.extractErrors(store, store.modelFor(identifier.type), error, identifier.id); + error.errors = errorsHashToArray(errorsHash); + } + } + const recordData = store._instanceCache.getRecordData(identifier); + + if (error.errors) { + assert( + `Expected the RecordData implementation for ${identifier} to have a getErrors(identifier) method for retreiving errors.`, + typeof recordData.getErrors === 'function' + ); + + let jsonApiErrors: JsonApiValidationError[] = error.errors; + if (jsonApiErrors.length === 0) { + jsonApiErrors = [{ title: 'Invalid Error', detail: '', source: { pointer: '/data' } }]; + } + recordData.commitWasRejected(identifier, jsonApiErrors); + } else { + recordData.commitWasRejected(identifier); + } +} + +function makeArray(value) { + return Array.isArray(value) ? value : [value]; +} + +const PRIMARY_ATTRIBUTE_KEY = 'base'; + +function errorsHashToArray(errors): JsonApiValidationError[] { + const out: JsonApiValidationError[] = []; + + if (errors) { + Object.keys(errors).forEach((key) => { + let messages = makeArray(errors[key]); + for (let i = 0; i < messages.length; i++) { + let title = 'Invalid Attribute'; + let pointer = `/data/attributes/${key}`; + if (key === PRIMARY_ATTRIBUTE_KEY) { + title = 'Invalid Document'; + pointer = `/data`; + } + out.push({ + title: title, + detail: messages[i], + source: { + pointer: pointer, + }, + }); + } + }); + } + + return out; +} diff --git a/packages/store/addon/-private/coerce-id.ts b/packages/store/addon/-private/utils/coerce-id.ts similarity index 98% rename from packages/store/addon/-private/coerce-id.ts rename to packages/store/addon/-private/utils/coerce-id.ts index fe421bc8d70..6aa9961536e 100644 --- a/packages/store/addon/-private/coerce-id.ts +++ b/packages/store/addon/-private/utils/coerce-id.ts @@ -35,7 +35,7 @@ export function ensureStringId(id: Coercable): string { throw new Error(`Expected id to be a string or number, received ${String(id)}`); } - return normalized!; + return normalized; } export default coerceId; diff --git a/packages/store/addon/-private/common.js b/packages/store/addon/-private/utils/common.js similarity index 92% rename from packages/store/addon/-private/common.js rename to packages/store/addon/-private/utils/common.js index 3162d912e04..1d63f75289f 100644 --- a/packages/store/addon/-private/common.js +++ b/packages/store/addon/-private/utils/common.js @@ -1,5 +1,4 @@ import { deprecate } from '@ember/debug'; -import { get } from '@ember/object'; import { DEBUG } from '@glimmer/env'; import { resolve } from 'rsvp'; @@ -27,7 +26,7 @@ export function _guard(promise, test) { } export function _objectIsAlive(object) { - return !(get(object, 'isDestroyed') || get(object, 'isDestroying')); + return !(object.isDestroyed || object.isDestroying); } export function guardDestroyedStore(promise, store, label) { diff --git a/packages/store/addon/-private/utils/construct-resource.ts b/packages/store/addon/-private/utils/construct-resource.ts index 079a24d1602..12710992cea 100644 --- a/packages/store/addon/-private/utils/construct-resource.ts +++ b/packages/store/addon/-private/utils/construct-resource.ts @@ -5,8 +5,8 @@ import type { ResourceIdentifierObject, } from '@ember-data/types/q/ember-data-json-api'; -import coerceId from '../coerce-id'; -import { isStableIdentifier } from '../identifier-cache'; +import { isStableIdentifier } from '../caches/identifier-cache'; +import coerceId from './coerce-id'; import isNonEmptyString from './is-non-empty-string'; function constructResource(type: ResourceIdentifierObject): ResourceIdentifierObject; diff --git a/packages/store/addon/-private/identifer-debug-consts.ts b/packages/store/addon/-private/utils/identifer-debug-consts.ts similarity index 100% rename from packages/store/addon/-private/identifer-debug-consts.ts rename to packages/store/addon/-private/utils/identifer-debug-consts.ts diff --git a/packages/store/addon/-private/normalize-model-name.ts b/packages/store/addon/-private/utils/normalize-model-name.ts similarity index 100% rename from packages/store/addon/-private/normalize-model-name.ts rename to packages/store/addon/-private/utils/normalize-model-name.ts diff --git a/packages/store/addon/-private/utils/promise-record.ts b/packages/store/addon/-private/utils/promise-record.ts index 03c182fd1ca..2df603774a9 100644 --- a/packages/store/addon/-private/utils/promise-record.ts +++ b/packages/store/addon/-private/utils/promise-record.ts @@ -1,9 +1,9 @@ import type { StableRecordIdentifier } from '@ember-data/types/q/identifier'; import type { RecordInstance } from '@ember-data/types/q/record-instance'; -import type Store from '../core-store'; -import type { PromiseObject } from '../promise-proxies'; -import { promiseObject } from '../promise-proxies'; +import type { PromiseObject } from '../proxies/promise-proxies'; +import { promiseObject } from '../proxies/promise-proxies'; +import type Store from '../store-service'; export default function promiseRecord( store: Store, diff --git a/packages/store/addon/-private/serializer-response.ts b/packages/store/addon/-private/utils/serializer-response.ts similarity index 95% rename from packages/store/addon/-private/serializer-response.ts rename to packages/store/addon/-private/utils/serializer-response.ts index c95b2015d93..2e68e242c27 100644 --- a/packages/store/addon/-private/serializer-response.ts +++ b/packages/store/addon/-private/utils/serializer-response.ts @@ -5,8 +5,8 @@ import type { JsonApiDocument } from '@ember-data/types/q/ember-data-json-api'; import type { AdapterPayload } from '@ember-data/types/q/minimum-adapter-interface'; import type { MinimumSerializerInterface, RequestType } from '@ember-data/types/q/minimum-serializer-interface'; -import type Store from './core-store'; -import type ShimModelClass from './model/shim-model-class'; +import type ShimModelClass from '../legacy-model-support/shim-model-class'; +import type Store from '../store-service'; /** This is a helper method that validates a JSON API top-level document diff --git a/packages/store/addon/-private/weak-cache.ts b/packages/store/addon/-private/utils/weak-cache.ts similarity index 100% rename from packages/store/addon/-private/weak-cache.ts rename to packages/store/addon/-private/utils/weak-cache.ts diff --git a/packages/unpublished-adapter-encapsulation-test-app/app/services/store.js b/packages/unpublished-adapter-encapsulation-test-app/app/services/store.js index c61976949e5..33ced4b2c4b 100644 --- a/packages/unpublished-adapter-encapsulation-test-app/app/services/store.js +++ b/packages/unpublished-adapter-encapsulation-test-app/app/services/store.js @@ -2,11 +2,11 @@ import { RecordData } from '@ember-data/record-data/-private'; import Store from '@ember-data/store'; export default class DefaultStore extends Store { - createRecordDataFor(modelName, id, clientId, storeWrapper) { + createRecordDataFor(modelName, id, lid, storeWrapper) { let identifier = this.identifierCache.getOrCreateRecordIdentifier({ type: modelName, id, - lid: clientId, + lid, }); return new RecordData(identifier, storeWrapper); } diff --git a/packages/unpublished-adapter-encapsulation-test-app/tests/integration/belongs-to-test.js b/packages/unpublished-adapter-encapsulation-test-app/tests/integration/belongs-to-test.js index 1d5633d6e5f..d4e4d9e4e07 100644 --- a/packages/unpublished-adapter-encapsulation-test-app/tests/integration/belongs-to-test.js +++ b/packages/unpublished-adapter-encapsulation-test-app/tests/integration/belongs-to-test.js @@ -173,7 +173,7 @@ module('integration/belongs-to - Belongs To Tests', function (hooks) { let comment = store.push(initialRecord); - let post = await comment.get('post'); + let post = await comment.post; assert.strictEqual(findRecordCalled, 0, 'findRecord is not called'); assert.strictEqual(findBelongsToCalled, 1, 'findBelongsTo is called once'); @@ -210,7 +210,7 @@ module('integration/belongs-to - Belongs To Tests', function (hooks) { let comment = store.push(initialRecord); await assert.expectAssertion(async function () { - await comment.get('post'); + await comment.post; }, /You tried to load a belongsTo relationship from a specified 'link' in the original payload but your adapter does not implement 'findBelongsTo'/); } ); @@ -288,7 +288,7 @@ module('integration/belongs-to - Belongs To Tests', function (hooks) { let comment = store.push(initialRecord); - let post = await comment.get('post'); + let post = await comment.post; assert.strictEqual(findRecordCalled, 1, 'findRecord is called once'); assert.strictEqual(findBelongsToCalled, 0, 'findBelongsTo is not called'); @@ -363,7 +363,7 @@ module('integration/belongs-to - Belongs To Tests', function (hooks) { let comment = store.push(initialRecord); - let post = await comment.get('post'); + let post = await comment.post; assert.strictEqual(findRecordCalled, 1, 'findRecord is called once'); assert.deepEqual(post.serialize(), expectedResult, 'findRecord returns expected result'); @@ -447,7 +447,7 @@ module('integration/belongs-to - Belongs To Tests', function (hooks) { let comment = store.push(initialRecord); - let post = await comment.get('post'); + let post = await comment.post; assert.strictEqual(findRecordCalled, 0, 'findRecord is not called'); assert.strictEqual(findBelongsToCalled, 1, 'findBelongsTo is called once'); @@ -522,7 +522,7 @@ module('integration/belongs-to - Belongs To Tests', function (hooks) { let comment = store.push(initialRecord); - let post = await comment.get('post'); + let post = await comment.post; assert.strictEqual(findRecordCalled, 1, 'findRecord is called once'); assert.deepEqual(post.serialize(), expectedResult, 'findRecord returns expected result'); diff --git a/packages/unpublished-adapter-encapsulation-test-app/tests/integration/has-many-test.js b/packages/unpublished-adapter-encapsulation-test-app/tests/integration/has-many-test.js index c42c8378870..de002677ba0 100644 --- a/packages/unpublished-adapter-encapsulation-test-app/tests/integration/has-many-test.js +++ b/packages/unpublished-adapter-encapsulation-test-app/tests/integration/has-many-test.js @@ -193,7 +193,7 @@ module('integration/has-many - Has Many Tests', function (hooks) { let post = store.push(initialRecord); - let comments = await post.get('comments'); + let comments = await post.comments; let serializedComments = { data: comments.toArray().map((comment) => comment.serialize().data), }; @@ -232,7 +232,7 @@ module('integration/has-many - Has Many Tests', function (hooks) { let post = store.push(initialRecord); await assert.expectAssertion(async function () { - await post.get('comments'); + await post.comments; }, /You tried to load a hasMany relationship from a specified 'link' in the original payload but your adapter does not implement 'findHasMany'/); }); @@ -302,7 +302,7 @@ module('integration/has-many - Has Many Tests', function (hooks) { owner.register('adapter:application', TestFindRecordAdapter); let post = store.push(initialRecord); - let comments = await post.get('comments'); + let comments = await post.comments; let serializedComments = { data: comments.toArray().map((comment) => comment.serialize().data), }; @@ -374,7 +374,7 @@ module('integration/has-many - Has Many Tests', function (hooks) { owner.register('adapter:application', TestFindRecordAdapter); let post = store.push(initialRecord); - let comments = await post.get('comments'); + let comments = await post.comments; let serializedComments = { data: comments.toArray().map((comment) => comment.serialize().data), }; @@ -457,7 +457,7 @@ module('integration/has-many - Has Many Tests', function (hooks) { owner.register('adapter:application', TestFindManyAdapter); let post = store.push(initialRecord); - let comments = await post.get('comments'); + let comments = await post.comments; let serializedComments = { data: comments.toArray().map((comment) => comment.serialize().data), }; @@ -536,7 +536,7 @@ module('integration/has-many - Has Many Tests', function (hooks) { owner.register('adapter:application', TestFindManyAdapter); let post = store.push(initialRecord); - let comments = await post.get('comments'); + let comments = await post.comments; let serializedComments = { data: comments.toArray().map((comment) => comment.serialize().data), }; @@ -615,7 +615,7 @@ module('integration/has-many - Has Many Tests', function (hooks) { let post = store.push(initialRecord); - let comments = await post.get('comments'); + let comments = await post.comments; let serializedComments = { data: comments.toArray().map((comment) => comment.serialize().data), }; @@ -697,7 +697,7 @@ module('integration/has-many - Has Many Tests', function (hooks) { owner.register('adapter:application', TestFindManyAdapter); let post = store.push(initialRecord); - let comments = await post.get('comments'); + let comments = await post.comments; let serializedComments = { data: comments.toArray().map((comment) => comment.serialize().data), }; @@ -768,7 +768,7 @@ module('integration/has-many - Has Many Tests', function (hooks) { owner.register('adapter:application', TestFindRecordAdapter); let post = store.push(initialRecord); - let comments = await post.get('comments'); + let comments = await post.comments; let serializedComments = { data: comments.toArray().map((comment) => comment.serialize().data), }; diff --git a/packages/unpublished-fastboot-test-app/app/models/person.js b/packages/unpublished-fastboot-test-app/app/models/person.js index d353542d03e..8255ebebbe3 100644 --- a/packages/unpublished-fastboot-test-app/app/models/person.js +++ b/packages/unpublished-fastboot-test-app/app/models/person.js @@ -11,7 +11,7 @@ export default class Person extends Model { parent; get parentId() { - return this.parent.get('id'); + return this.belongsTo('parent').id(); } toNode() { diff --git a/packages/unpublished-fastboot-test-app/app/templates/index.hbs b/packages/unpublished-fastboot-test-app/app/templates/index.hbs index 53706222ffa..e0c9b62fd3e 100644 --- a/packages/unpublished-fastboot-test-app/app/templates/index.hbs +++ b/packages/unpublished-fastboot-test-app/app/templates/index.hbs @@ -1,10 +1,5 @@ -{{#x-tree - model=this.model - checkable=true - expandDepth=-1 - as |node| -}} + {{node.toggle}} {{node.checkbox}} {{node.model.name}} -{{/x-tree}} \ No newline at end of file + \ No newline at end of file diff --git a/packages/unpublished-fastboot-test-app/config/ember-try.js b/packages/unpublished-fastboot-test-app/config/ember-try.js index 5905ff6359d..a6dd4c25584 100644 --- a/packages/unpublished-fastboot-test-app/config/ember-try.js +++ b/packages/unpublished-fastboot-test-app/config/ember-try.js @@ -8,7 +8,7 @@ module.exports = function () { name: 'fastboot-with-ember-fetch', npm: { devDependencies: { - 'ember-fetch': '*', + 'ember-fetch': '^8.1.1', }, }, }, diff --git a/packages/unpublished-model-encapsulation-test-app/ember-cli-build.js b/packages/unpublished-model-encapsulation-test-app/ember-cli-build.js index 26256558192..ac483a09b06 100644 --- a/packages/unpublished-model-encapsulation-test-app/ember-cli-build.js +++ b/packages/unpublished-model-encapsulation-test-app/ember-cli-build.js @@ -8,7 +8,9 @@ module.exports = function (defaults) { const isProd = process.env.EMBER_ENV === 'production'; // allows testing with env config for stripping all deprecations const compatWith = process.env.EMBER_DATA_FULL_COMPAT ? '99.0' : null; - const plugins = [...require('@ember-data/private-build-infra/src/debug-macros')(null, isProd, compatWith)]; + const plugins = [ + ...require('@ember-data/private-build-infra/src/debug-macros')(null, isProd, { compatWith, debug: {} }), + ]; let app = new EmberApp(defaults, { emberData: { diff --git a/packages/unpublished-serializer-encapsulation-test-app/app/services/store.js b/packages/unpublished-serializer-encapsulation-test-app/app/services/store.js index c61976949e5..33ced4b2c4b 100644 --- a/packages/unpublished-serializer-encapsulation-test-app/app/services/store.js +++ b/packages/unpublished-serializer-encapsulation-test-app/app/services/store.js @@ -2,11 +2,11 @@ import { RecordData } from '@ember-data/record-data/-private'; import Store from '@ember-data/store'; export default class DefaultStore extends Store { - createRecordDataFor(modelName, id, clientId, storeWrapper) { + createRecordDataFor(modelName, id, lid, storeWrapper) { let identifier = this.identifierCache.getOrCreateRecordIdentifier({ type: modelName, id, - lid: clientId, + lid, }); return new RecordData(identifier, storeWrapper); } diff --git a/packages/unpublished-serializer-encapsulation-test-app/tests/integration/relationships-test.js b/packages/unpublished-serializer-encapsulation-test-app/tests/integration/relationships-test.js index a6186c6079c..8892341919e 100644 --- a/packages/unpublished-serializer-encapsulation-test-app/tests/integration/relationships-test.js +++ b/packages/unpublished-serializer-encapsulation-test-app/tests/integration/relationships-test.js @@ -114,7 +114,7 @@ module('integration/relationships - running requests for async relatonships with }, }, }); - let comments = await post.get('comments'); + let comments = await post.comments; assert.strictEqual(normalizeResponseCalled, 1, 'normalizeResponse is called once'); assert.deepEqual(comments.mapBy('message'), ['Message 1', 'Message 2'], 'response is expected response'); @@ -177,7 +177,7 @@ module('integration/relationships - running requests for async relatonships with }, }, }); - let comments = await post.get('comments'); + let comments = await post.comments; assert.strictEqual(normalizeResponseCalled, 1, 'normalizeResponse is called once'); assert.deepEqual(comments.mapBy('message'), ['Message 1', 'Message 2'], 'response is expected response'); @@ -247,7 +247,7 @@ module('integration/relationships - running requests for async relatonships with }, }, }); - let post = await comment.get('post'); + let post = await comment.post; assert.strictEqual(normalizeResponseCalled, 1, 'normalizeResponse is called once'); assert.deepEqual(post.title, 'Chris', 'response is expected response'); diff --git a/packages/unpublished-test-infra/addon-test-support/async.js b/packages/unpublished-test-infra/addon-test-support/async.js deleted file mode 100644 index 811a4044f50..00000000000 --- a/packages/unpublished-test-infra/addon-test-support/async.js +++ /dev/null @@ -1,42 +0,0 @@ -import { run } from '@ember/runloop'; - -import { all, resolve } from 'rsvp'; - -// Should not use these going forward, use async/await instead. -export function wait(callback, timeout) { - let done = this.async(); - - let timer = setTimeout(() => { - this.ok(false, 'Timeout was reached'); - done(); - }, timeout || 200); - - return function () { - window.clearTimeout(timer); - - let args = arguments; - let result; - try { - result = run(() => callback.apply(this, args)); - } finally { - done(); - } - return result; - }; -} - -export function asyncEqual(a, b, message) { - return all([resolve(a), resolve(b)]).then( - this.wait((array) => { - let actual = array[0]; - let expected = array[1]; - let result = actual === expected; - - this.pushResult({ result, actual, expected, message }); - }) - ); -} - -export function invokeAsync(callback, timeout = 1) { - setTimeout(this.wait(callback, timeout + 100), timeout); -} diff --git a/packages/unpublished-test-infra/addon-test-support/legacy.js b/packages/unpublished-test-infra/addon-test-support/legacy.js deleted file mode 100644 index e472c442fbb..00000000000 --- a/packages/unpublished-test-infra/addon-test-support/legacy.js +++ /dev/null @@ -1,28 +0,0 @@ -import QUnit from 'qunit'; - -import { asyncEqual, invokeAsync, wait } from './async'; - -const { assert } = QUnit; - -export default function additionalLegacyAsserts() { - assert.wait = wait; - assert.asyncEqual = asyncEqual; - assert.invokeAsync = invokeAsync; - - assert.assertClean = function (promise) { - return promise.then( - this.wait((record) => { - this.equal(record.get('hasDirtyAttributes'), false, 'The record is now clean'); - return record; - }) - ); - }; - - assert.contains = function (array, item) { - this.ok(array.indexOf(item) !== -1, `array contains ${item}`); - }; - - assert.without = function (array, item) { - this.ok(array.indexOf(item) === -1, `array doesn't contain ${item}`); - }; -} diff --git a/packages/unpublished-test-infra/ember-cli-build.js b/packages/unpublished-test-infra/ember-cli-build.js index a210e7d8cae..8ed73a75377 100644 --- a/packages/unpublished-test-infra/ember-cli-build.js +++ b/packages/unpublished-test-infra/ember-cli-build.js @@ -15,7 +15,9 @@ module.exports = function (defaults) { babel: { // this ensures that the same build-time code stripping that is done // for library packages is also done for our tests and dummy app - plugins: [...require('@ember-data/private-build-infra/src/debug-macros')(null, isProd, compatWith)], + plugins: [ + ...require('@ember-data/private-build-infra/src/debug-macros')(null, isProd, { compatWith, debug: {} }), + ], }, 'ember-cli-babel': { throwUnlessParallelizable: true, diff --git a/tsconfig.json b/tsconfig.json index 1d210d578df..87414519978 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -42,24 +42,20 @@ "ember-data-types/q/fetch-manager.ts", "ember-data-types/q/ember-data-json-api.ts", "ember-data-types/q/ds-model.ts", - "packages/store/addon/-private/record-data-store-wrapper.ts", - "packages/store/addon/-private/internal-model-factory.ts", - "packages/store/addon/-private/snapshot.ts", - "packages/store/addon/-private/snapshot-record-array.ts", - "packages/store/addon/-private/schema-definition-service.ts", - "packages/store/addon/-private/request-cache.ts", - "packages/store/addon/-private/record-notification-manager.ts", - "packages/store/addon/-private/record-data-for.ts", - "packages/store/addon/-private/normalize-model-name.ts", - "packages/store/addon/-private/model/shim-model-class.ts", - "packages/store/addon/-private/model/internal-model.ts", - "packages/store/addon/-private/internal-model-map.ts", - "packages/store/addon/-private/identity-map.ts", - "packages/store/addon/-private/fetch-manager.ts", - "packages/store/addon/-private/core-store.ts", - "packages/store/addon/-private/coerce-id.ts", + "packages/store/addon/-private/managers/record-data-store-wrapper.ts", + "packages/store/addon/-private/network/snapshot.ts", + "packages/store/addon/-private/network/snapshot-record-array.ts", + "packages/store/addon/-private/legacy-model-support/schema-definition-service.ts", + "packages/store/addon/-private/network/request-cache.ts", + "packages/store/addon/-private/managers/record-notification-manager.ts", + "packages/store/addon/-private/caches/record-data-for.ts", + "packages/store/addon/-private/utils/normalize-model-name.ts", + "packages/store/addon/-private/legacy-model-support/shim-model-class.ts", + "packages/store/addon/-private/network/fetch-manager.ts", + "packages/store/addon/-private/store-service.ts", + "packages/store/addon/-private/utils/coerce-id.ts", "packages/store/addon/-private/index.ts", - "packages/store/addon/-private/identifier-cache.ts", + "packages/store/addon/-private/caches/identifier-cache.ts", "packages/serializer/tests/dummy/app/routes/application/route.ts", "packages/serializer/tests/dummy/app/router.ts", "packages/serializer/tests/dummy/app/resolver.ts",