@ember-data/model 4.8.0-alpha.0 → 4.8.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -130,13 +130,24 @@ function attr(type, options) {
130
130
  return computed({
131
131
  get(key) {
132
132
  if (DEBUG) {
133
- if (['_internalModel', 'currentState'].indexOf(key) !== -1) {
133
+ if (['currentState'].indexOf(key) !== -1) {
134
134
  throw new Error(
135
135
  `'${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()}`
136
136
  );
137
137
  }
138
138
  }
139
+ if (this.isDestroyed || this.isDestroying) {
140
+ return;
141
+ }
139
142
  let recordData = recordDataFor(this);
143
+ // TODO hasAttr is not spec'd
144
+ // essentially this is needed because
145
+ // there is a difference between "undefined" meaning never set
146
+ // and "undefined" meaning set to "undefined". In the "key present"
147
+ // case we want to return undefined. In the "key absent" case
148
+ // we want to return getDefaultValue. RecordDataV2 can fix this
149
+ // by providing the attributes blob such that we can make our
150
+ // own determination.
140
151
  if (recordData.hasAttr(key)) {
141
152
  return recordData.getAttr(key);
142
153
  } else {
@@ -145,7 +156,7 @@ function attr(type, options) {
145
156
  },
146
157
  set(key, value) {
147
158
  if (DEBUG) {
148
- if (['_internalModel', 'currentState'].indexOf(key) !== -1) {
159
+ if (['currentState'].indexOf(key) !== -1) {
149
160
  throw new Error(
150
161
  `'${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()}`
151
162
  );
@@ -98,7 +98,7 @@ import { computedMacroWithOptionalParams } from './util';
98
98
  a related resource is known to exist and it has not been loaded.
99
99
 
100
100
  ```
101
- let post = comment.get('post');
101
+ let post = comment.post;
102
102
 
103
103
  ```
104
104
 
@@ -143,10 +143,16 @@ function belongsTo(modelName, options) {
143
143
 
144
144
  return computed({
145
145
  get(key) {
146
+ // this is a legacy behavior we may not carry into a new model setup
147
+ // it's better to error on disconnected records so users find errors
148
+ // in their logic.
149
+ if (this.isDestroying || this.isDestroyed) {
150
+ return null;
151
+ }
146
152
  const support = LEGACY_SUPPORT.lookup(this);
147
153
 
148
154
  if (DEBUG) {
149
- if (['_internalModel', 'recordData', 'currentState'].indexOf(key) !== -1) {
155
+ if (['currentState'].indexOf(key) !== -1) {
150
156
  throw new Error(
151
157
  `'${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()}`
152
158
  );
@@ -177,7 +183,7 @@ function belongsTo(modelName, options) {
177
183
  set(key, value) {
178
184
  const support = LEGACY_SUPPORT.lookup(this);
179
185
  if (DEBUG) {
180
- if (['_internalModel', 'recordData', 'currentState'].indexOf(key) !== -1) {
186
+ if (['currentState'].indexOf(key) !== -1) {
181
187
  throw new Error(
182
188
  `'${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()}`
183
189
  );
@@ -123,7 +123,7 @@ export default class Errors extends ArrayProxyWithCustomOverrides<ValidationErro
123
123
  email: 'invalidEmail'
124
124
  });
125
125
  user.save().catch(function(){
126
- user.get('errors').errorsFor('email'); // returns:
126
+ user.errors.errorsFor('email'); // returns:
127
127
  // [{attribute: "email", message: "Doesn't look like a valid email."}]
128
128
  });
129
129
  ```
@@ -219,7 +219,7 @@ export default class Errors extends ArrayProxyWithCustomOverrides<ValidationErro
219
219
 
220
220
  Example
221
221
  ```javascript
222
- let errors = get(user, 'errors');
222
+ let errors = user.errors;
223
223
 
224
224
  // add multiple errors
225
225
  errors.add('password', [
@@ -293,7 +293,7 @@ export default class Errors extends ArrayProxyWithCustomOverrides<ValidationErro
293
293
  Example:
294
294
 
295
295
  ```javascript
296
- let errors = get('user', errors);
296
+ let errors = user.errors;
297
297
  errors.add('phone', ['error-1', 'error-2']);
298
298
 
299
299
  errors.errorsFor('phone');
@@ -344,7 +344,7 @@ export default class Errors extends ArrayProxyWithCustomOverrides<ValidationErro
344
344
  Example:
345
345
 
346
346
  ```javascript
347
- let errors = get('user', errors);
347
+ let errors = user.errors;
348
348
  errors.add('username', ['error-a']);
349
349
  errors.add('phone', ['error-1', 'error-2']);
350
350
 
@@ -369,7 +369,7 @@ export default class Errors extends ArrayProxyWithCustomOverrides<ValidationErro
369
369
  errors.errorsFor('phone');
370
370
  // => undefined
371
371
 
372
- errors.get('messages')
372
+ errors.messages
373
373
  // => []
374
374
  ```
375
375
  @method clear
@@ -406,7 +406,7 @@ export default class Errors extends ArrayProxyWithCustomOverrides<ValidationErro
406
406
  export default class UserEditController extends Controller {
407
407
  @action
408
408
  save(user) {
409
- if (user.get('errors').has('email')) {
409
+ if (user.errors.has('email')) {
410
410
  return alert('Please update your email before attempting to save.');
411
411
  }
412
412
  user.save();
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  @module @ember-data/model
3
3
  */
4
+ import { A } from '@ember/array';
4
5
  import { assert, inspect } from '@ember/debug';
5
6
  import { computed } from '@ember/object';
6
7
  import { DEBUG } from '@glimmer/env';
@@ -132,7 +133,7 @@ import { computedMacroWithOptionalParams } from './util';
132
133
  when any of the known related resources have not been loaded.
133
134
 
134
135
  ```
135
- post.get('comments').forEach((comment) => {
136
+ post.comments.forEach((comment) => {
136
137
 
137
138
  });
138
139
 
@@ -183,17 +184,20 @@ function hasMany(type, options) {
183
184
  return computed({
184
185
  get(key) {
185
186
  if (DEBUG) {
186
- if (['_internalModel', 'currentState'].indexOf(key) !== -1) {
187
+ if (['currentState'].indexOf(key) !== -1) {
187
188
  throw new Error(
188
189
  `'${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()}`
189
190
  );
190
191
  }
191
192
  }
193
+ if (this.isDestroying || this.isDestroyed) {
194
+ return A();
195
+ }
192
196
  return LEGACY_SUPPORT.lookup(this).getHasMany(key);
193
197
  },
194
198
  set(key, records) {
195
199
  if (DEBUG) {
196
- if (['_internalModel', 'currentState'].indexOf(key) !== -1) {
200
+ if (['currentState'].indexOf(key) !== -1) {
197
201
  throw new Error(
198
202
  `'${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()}`
199
203
  );
@@ -8,6 +8,7 @@ import { DEPRECATE_RSVP_PROMISE } from '@ember-data/private-build-infra/deprecat
8
8
  import { iterateData, normalizeResponseHelper } from './legacy-data-utils';
9
9
 
10
10
  export function _findHasMany(adapter, store, identifier, link, relationship, options) {
11
+ const record = store._instanceCache.getRecord(identifier);
11
12
  const snapshot = store._instanceCache.createSnapshot(identifier, options);
12
13
  let modelClass = store.modelFor(relationship.type);
13
14
  let useLink = !link || typeof link === 'string';
@@ -18,7 +19,7 @@ export function _findHasMany(adapter, store, identifier, link, relationship, opt
18
19
  promise = guardDestroyedStore(promise, store, label);
19
20
  promise = promise.then(
20
21
  (adapterPayload) => {
21
- if (!_objectIsAlive(store._instanceCache.getInternalModel(identifier))) {
22
+ if (!_objectIsAlive(record)) {
22
23
  if (DEPRECATE_RSVP_PROMISE) {
23
24
  deprecate(
24
25
  `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
57
58
  );
58
59
 
59
60
  if (DEPRECATE_RSVP_PROMISE) {
60
- promise = _guard(promise, _bind(_objectIsAlive, store._instanceCache.getInternalModel(identifier)));
61
+ promise = _guard(promise, _bind(_objectIsAlive, record));
61
62
  }
62
63
 
63
64
  return promise;
64
65
  }
65
66
 
66
67
  export function _findBelongsTo(store, identifier, link, relationship, options) {
68
+ const record = store._instanceCache.getRecord(identifier);
67
69
  let adapter = store.adapterFor(identifier.type);
68
70
 
69
71
  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) {
79
81
  let label = `DS: Handle Adapter#findBelongsTo of ${identifier.type} : ${relationship.type}`;
80
82
 
81
83
  promise = guardDestroyedStore(promise, store, label);
82
- promise = _guard(promise, _bind(_objectIsAlive, store._instanceCache.getInternalModel(identifier)));
84
+ promise = _guard(promise, _bind(_objectIsAlive, record));
83
85
 
84
86
  promise = promise.then(
85
87
  (adapterPayload) => {
86
- if (!_objectIsAlive(store._instanceCache.getInternalModel(identifier))) {
88
+ if (!_objectIsAlive(record)) {
87
89
  if (DEPRECATE_RSVP_PROMISE) {
88
90
  deprecate(
89
91
  `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) {
123
125
  );
124
126
 
125
127
  if (DEPRECATE_RSVP_PROMISE) {
126
- promise = _guard(promise, _bind(_objectIsAlive, store._instanceCache.getInternalModel(identifier)));
128
+ promise = _guard(promise, _bind(_objectIsAlive, record));
127
129
  }
128
130
 
129
131
  return promise;
@@ -229,8 +231,8 @@ function ensureRelationshipIsSetToParent(payload, parentIdentifier, store, paren
229
231
  }
230
232
  }
231
233
 
232
- function getInverse(store, parentInternalModel, parentRelationship, type) {
233
- return recordDataFindInverseRelationshipInfo(store, parentInternalModel, parentRelationship, type);
234
+ function getInverse(store, parentIdentifier, parentRelationship, type) {
235
+ return recordDataFindInverseRelationshipInfo(store, parentIdentifier, parentRelationship, type);
234
236
  }
235
237
 
236
238
  function recordDataFindInverseRelationshipInfo(store, parentIdentifier, parentRelationship, type) {
@@ -2,7 +2,7 @@ import { assert } from '@ember/debug';
2
2
  import { DEBUG } from '@glimmer/env';
3
3
 
4
4
  import type Store from '@ember-data/store';
5
- import type ShimModelClass from '@ember-data/store/-private/model/shim-model-class';
5
+ import type ShimModelClass from '@ember-data/store/-private/legacy-model-support/shim-model-class';
6
6
  import type { JsonApiDocument } from '@ember-data/types/q/ember-data-json-api';
7
7
  import type { StableExistingRecordIdentifier, StableRecordIdentifier } from '@ember-data/types/q/identifier';
8
8
  import type { AdapterPayload } from '@ember-data/types/q/minimum-adapter-interface';
@@ -13,11 +13,10 @@ import type {
13
13
  import type { UpgradedMeta } from '@ember-data/record-data/-private/graph/-edge-definition';
14
14
  import type { RelationshipState } from '@ember-data/record-data/-private/graph/-state';
15
15
  import type Store from '@ember-data/store';
16
- import type { InternalModel } from '@ember-data/store/-private';
17
16
  import { recordDataFor, recordIdentifierFor, storeFor } from '@ember-data/store/-private';
18
- import type { IdentifierCache } from '@ember-data/store/-private/identifier-cache';
17
+ import { IdentifierCache } from '@ember-data/store/-private/caches/identifier-cache';
19
18
  import type { DSModel } from '@ember-data/types/q/ds-model';
20
- import type { ResourceIdentifierObject } from '@ember-data/types/q/ember-data-json-api';
19
+ import { ResourceIdentifierObject } from '@ember-data/types/q/ember-data-json-api';
21
20
  import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
22
21
  import type { RecordData } from '@ember-data/types/q/record-data';
23
22
  import type { JsonApiRelationship } from '@ember-data/types/q/record-data-json-api';
@@ -141,7 +140,7 @@ export class LegacySupport {
141
140
  `You looked up the '${key}' relationship on a '${identifier.type}' with id ${
142
141
  identifier.id || 'null'
143
142
  } 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 })\`)`,
144
- toReturn === null || !store._instanceCache.getInternalModel(relatedIdentifier).isEmpty
143
+ toReturn === null || store._instanceCache.recordIsLoaded(relatedIdentifier, true)
145
144
  );
146
145
  return toReturn;
147
146
  }
@@ -447,7 +446,7 @@ export class LegacySupport {
447
446
  return resolve(null);
448
447
  }
449
448
 
450
- const internalModel = resource.data ? this.store._instanceCache._internalModelForResource(resource.data) : null;
449
+ const identifier = resource.data ? this.store.identifierCache.getOrCreateRecordIdentifier(resource.data) : null;
451
450
 
452
451
  let { isStale, hasDematerializedInverse, hasReceivedData, isEmpty, shouldForceReload } = resource._relationship
453
452
  .state as RelationshipState;
@@ -458,9 +457,9 @@ export class LegacySupport {
458
457
  resource.links.related &&
459
458
  (shouldForceReload || hasDematerializedInverse || isStale || (!allInverseRecordsAreLoaded && !isEmpty));
460
459
 
461
- if (internalModel) {
460
+ if (identifier) {
462
461
  // short circuit if we are already loading
463
- let pendingRequest = this.store._fetchManager.getPendingFetch(internalModel.identifier, options);
462
+ let pendingRequest = this.store._fetchManager.getPendingFetch(identifier, options);
464
463
  if (pendingRequest) {
465
464
  return pendingRequest;
466
465
  }
@@ -485,22 +484,21 @@ export class LegacySupport {
485
484
  return resolve(null);
486
485
  }
487
486
 
488
- if (!internalModel) {
489
- assert(`No InternalModel found for ${resource.lid}`, internalModel);
487
+ if (!identifier) {
488
+ assert(`No Information found for ${resource.lid}`, identifier);
490
489
  }
491
490
 
492
- return this.store._instanceCache._fetchDataIfNeededForIdentifier(internalModel.identifier, options);
491
+ return this.store._instanceCache._fetchDataIfNeededForIdentifier(identifier, options);
493
492
  }
494
493
 
495
494
  let resourceIsLocal = !localDataIsEmpty && resource.data.id === null;
496
495
 
497
- if (internalModel && resourceIsLocal) {
498
- return resolve(internalModel.identifier);
496
+ if (identifier && resourceIsLocal) {
497
+ return resolve(identifier);
499
498
  }
500
499
 
501
500
  // fetch by data
502
- if (internalModel && !localDataIsEmpty) {
503
- let identifier = internalModel.identifier;
501
+ if (identifier && !localDataIsEmpty) {
504
502
  assertIdentifierHasId(identifier);
505
503
 
506
504
  return this.store._fetchManager.scheduleFetch(identifier, options);
@@ -512,10 +510,6 @@ export class LegacySupport {
512
510
  }
513
511
 
514
512
  destroy() {
515
- assert(
516
- 'Cannot destroy an internalModel while its record is materialized',
517
- !this.record || this.record.isDestroyed || this.record.isDestroying
518
- );
519
513
  this.isDestroying = true;
520
514
 
521
515
  const cache = this._manyArrayCache;
@@ -625,7 +619,14 @@ function assertRecordsPassedToHasMany(records: RecordInstance[]) {
625
619
  .map((r) => `${typeof r}`)
626
620
  .join(', ')}`,
627
621
  (function () {
628
- return records.every((record) => Object.prototype.hasOwnProperty.call(record, '_internalModel') === true);
622
+ return records.every((record) => {
623
+ try {
624
+ recordIdentifierFor(record);
625
+ return true;
626
+ } catch {
627
+ return false;
628
+ }
629
+ });
629
630
  })()
630
631
  );
631
632
  }
@@ -634,7 +635,10 @@ function extractRecordDatasFromRecords(records: RecordInstance[]): RecordData[]
634
635
  return records.map(extractRecordDataFromRecord) as RecordData[];
635
636
  }
636
637
 
637
- type PromiseProxyRecord = { then(): void; get(str: 'content'): RecordInstance | null | undefined };
638
+ type PromiseProxyRecord = {
639
+ then(): void;
640
+ content: RecordInstance | null | undefined;
641
+ };
638
642
 
639
643
  function extractRecordDataFromRecord(recordOrPromiseRecord: PromiseProxyRecord | RecordInstance | null) {
640
644
  if (!recordOrPromiseRecord) {
@@ -642,7 +646,7 @@ function extractRecordDataFromRecord(recordOrPromiseRecord: PromiseProxyRecord |
642
646
  }
643
647
 
644
648
  if (isPromiseRecord(recordOrPromiseRecord)) {
645
- let content = recordOrPromiseRecord.get && recordOrPromiseRecord.get('content');
649
+ let content = recordOrPromiseRecord.content;
646
650
  assert(
647
651
  '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.',
648
652
  content !== undefined
@@ -659,24 +663,15 @@ function isPromiseRecord(record: PromiseProxyRecord | RecordInstance): record is
659
663
 
660
664
  function anyUnloaded(store: Store, relationship: ManyRelationship) {
661
665
  let state = relationship.currentState;
666
+ const cache = store._instanceCache;
662
667
  const unloaded = state.find((s) => {
663
- let im = store._instanceCache.getInternalModel(s);
664
- return im._isDematerializing || !im.isLoaded;
668
+ let isLoaded = cache.recordIsLoaded(s, true);
669
+ return !isLoaded;
665
670
  });
666
671
 
667
672
  return unloaded || false;
668
673
  }
669
674
 
670
- /**
671
- * Flag indicating whether all inverse records are available
672
- *
673
- * true if the inverse exists and is loaded (not empty)
674
- * true if there is no inverse
675
- * false if the inverse exists and is not loaded (empty)
676
- *
677
- * @internal
678
- * @return {boolean}
679
- */
680
675
  function areAllInverseRecordsLoaded(store: Store, resource: JsonApiRelationship): boolean {
681
676
  const cache = store.identifierCache;
682
677
 
@@ -684,7 +679,7 @@ function areAllInverseRecordsLoaded(store: Store, resource: JsonApiRelationship)
684
679
  // treat as collection
685
680
  // check for unloaded records
686
681
  let hasEmptyRecords = resource.data.reduce((hasEmptyModel, resourceIdentifier) => {
687
- return hasEmptyModel || internalModelForRelatedResource(store, cache, resourceIdentifier).isEmpty;
682
+ return hasEmptyModel || isEmpty(store, cache, resourceIdentifier);
688
683
  }, false);
689
684
 
690
685
  return !hasEmptyRecords;
@@ -693,17 +688,13 @@ function areAllInverseRecordsLoaded(store: Store, resource: JsonApiRelationship)
693
688
  if (!resource.data) {
694
689
  return true;
695
690
  } else {
696
- const internalModel = internalModelForRelatedResource(store, cache, resource.data);
697
- return !internalModel.isEmpty;
691
+ return !isEmpty(store, cache, resource.data);
698
692
  }
699
693
  }
700
694
  }
701
695
 
702
- function internalModelForRelatedResource(
703
- store: Store,
704
- cache: IdentifierCache,
705
- resource: ResourceIdentifierObject
706
- ): InternalModel {
696
+ function isEmpty(store: Store, cache: IdentifierCache, resource: ResourceIdentifierObject): boolean {
707
697
  const identifier = cache.getOrCreateRecordIdentifier(resource);
708
- return store._instanceCache._internalModelForResource(identifier);
698
+ const recordData = store._instanceCache.peek({ identifier, bucket: 'recordData' });
699
+ return !recordData || !!recordData.isEmpty?.();
709
700
  }
@@ -10,8 +10,8 @@ import { all } from 'rsvp';
10
10
 
11
11
  import type Store from '@ember-data/store';
12
12
  import { PromiseArray, recordDataFor } from '@ember-data/store/-private';
13
- import type { CreateRecordProperties } from '@ember-data/store/-private/core-store';
14
- import type ShimModelClass from '@ember-data/store/-private/model/shim-model-class';
13
+ import type ShimModelClass from '@ember-data/store/-private/legacy-model-support/shim-model-class';
14
+ import type { CreateRecordProperties } from '@ember-data/store/-private/store-service';
15
15
  import type { DSModelSchema } from '@ember-data/types/q/ds-model';
16
16
  import type { Links, PaginationLinks } from '@ember-data/types/q/ember-data-json-api';
17
17
  import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
@@ -306,16 +306,16 @@ export default class ManyArray extends MutableArrayWithObject<StableRecordIdenti
306
306
  this._isDirty = false;
307
307
  this._isUpdating = true;
308
308
  let jsonApi = this.recordData.getHasMany(this.key);
309
+ const cache = this.store._instanceCache;
310
+ const idCache = this.store.identifierCache;
309
311
 
310
312
  let identifiers: StableRecordIdentifier[] = [];
311
313
  if (jsonApi.data) {
312
314
  for (let i = 0; i < jsonApi.data.length; i++) {
313
- // TODO figure out where this state comes from
314
- let im = this.store._instanceCache._internalModelForResource(jsonApi.data[i]);
315
- let shouldRemove = im._isDematerializing || im.isEmpty || !im.isLoaded;
315
+ const identifier = idCache.getOrCreateRecordIdentifier(jsonApi.data[i]);
316
316
 
317
- if (!shouldRemove) {
318
- identifiers.push(im.identifier);
317
+ if (cache.recordIsLoaded(identifier, true)) {
318
+ identifiers.push(identifier);
319
319
  }
320
320
  }
321
321
  }
@@ -348,6 +348,12 @@ export default class ManyArray extends MutableArrayWithObject<StableRecordIdenti
348
348
  this._isUpdating = false;
349
349
  }
350
350
 
351
+ destroy() {
352
+ this._length = 0;
353
+ this.currentState = [];
354
+ return super.destroy();
355
+ }
356
+
351
357
  /**
352
358
  Reloads all of the records in the manyArray. If the manyArray
353
359
  holds a relationship that was originally fetched using a links url
@@ -29,10 +29,8 @@ export default function modelForMixin(store: Store, normalizedModelName: string)
29
29
  let mixin = MaybeMixin && MaybeMixin.class;
30
30
  if (mixin) {
31
31
  let ModelForMixin = Model.extend(mixin);
32
- ModelForMixin.reopenClass({
33
- __isMixin: true,
34
- __mixin: mixin,
35
- });
32
+ ModelForMixin.__isMixin = true;
33
+ ModelForMixin.__mixin = mixin;
36
34
  //Cache the class as a model
37
35
  owner.register('model:' + normalizedModelName, ModelForMixin);
38
36
  }