@ember-data/model 4.7.0-beta.0 → 4.7.1
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.
- package/addon/-private/attr.js +9 -24
- package/addon/-private/belongs-to.js +87 -25
- package/addon/-private/deprecated-promise-proxy.ts +54 -0
- package/addon/-private/errors.ts +6 -6
- package/addon/-private/has-many.js +84 -22
- package/addon/-private/legacy-data-fetch.js +35 -13
- package/addon/-private/legacy-data-utils.ts +1 -1
- package/addon/-private/legacy-relationships-support.ts +279 -218
- package/addon/-private/many-array.ts +216 -242
- package/addon/-private/model-for-mixin.ts +2 -4
- package/addon/-private/model.js +565 -172
- package/addon/-private/notify-changes.ts +2 -2
- package/addon/-private/promise-belongs-to.ts +1 -1
- package/addon/-private/promise-many-array.ts +204 -124
- package/addon/-private/promise-proxy-base.js +4 -0
- package/addon/-private/record-state.ts +63 -46
- package/addon/-private/references/belongs-to.ts +65 -38
- package/addon/-private/references/has-many.ts +86 -55
- package/addon/-private/relationship-meta.ts +8 -26
- package/index.js +3 -0
- package/package.json +13 -10
package/addon/-private/attr.js
CHANGED
|
@@ -11,19 +11,6 @@ import { computedMacroWithOptionalParams } from './util';
|
|
|
11
11
|
@module @ember-data/model
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
function getDefaultValue(record, options, key) {
|
|
15
|
-
if (typeof options.defaultValue === 'function') {
|
|
16
|
-
return options.defaultValue.apply(null, arguments);
|
|
17
|
-
} else {
|
|
18
|
-
let defaultValue = options.defaultValue;
|
|
19
|
-
assert(
|
|
20
|
-
`Non primitive defaultValues are not supported because they are shared between all instances. If you would like to use a complex object as a default value please provide a function that returns the complex object.`,
|
|
21
|
-
typeof defaultValue !== 'object' || defaultValue === null
|
|
22
|
-
);
|
|
23
|
-
return defaultValue;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
14
|
/**
|
|
28
15
|
`attr` defines an attribute on a [Model](/ember-data/release/classes/Model).
|
|
29
16
|
By default, attributes are passed through as-is, however you can specify an
|
|
@@ -123,29 +110,26 @@ function attr(type, options) {
|
|
|
123
110
|
let meta = {
|
|
124
111
|
type: type,
|
|
125
112
|
isAttribute: true,
|
|
126
|
-
kind: 'attribute',
|
|
127
113
|
options: options,
|
|
128
114
|
};
|
|
129
115
|
|
|
130
116
|
return computed({
|
|
131
117
|
get(key) {
|
|
132
118
|
if (DEBUG) {
|
|
133
|
-
if (['
|
|
119
|
+
if (['currentState'].indexOf(key) !== -1) {
|
|
134
120
|
throw new Error(
|
|
135
121
|
`'${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
122
|
);
|
|
137
123
|
}
|
|
138
124
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
return recordData.getAttr(key);
|
|
142
|
-
} else {
|
|
143
|
-
return getDefaultValue(this, options, key);
|
|
125
|
+
if (this.isDestroyed || this.isDestroying) {
|
|
126
|
+
return;
|
|
144
127
|
}
|
|
128
|
+
return recordDataFor(this).getAttr(recordIdentifierFor(this), key);
|
|
145
129
|
},
|
|
146
130
|
set(key, value) {
|
|
147
131
|
if (DEBUG) {
|
|
148
|
-
if (['
|
|
132
|
+
if (['currentState'].indexOf(key) !== -1) {
|
|
149
133
|
throw new Error(
|
|
150
134
|
`'${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
135
|
);
|
|
@@ -155,10 +139,11 @@ function attr(type, options) {
|
|
|
155
139
|
`Attempted to set '${key}' on the deleted record ${recordIdentifierFor(this)}`,
|
|
156
140
|
!this.currentState.isDeleted
|
|
157
141
|
);
|
|
158
|
-
const
|
|
159
|
-
|
|
142
|
+
const identifier = recordIdentifierFor(this);
|
|
143
|
+
const recordData = storeFor(this)._instanceCache.getRecordData(identifier);
|
|
144
|
+
let currentValue = recordData.getAttr(identifier, key);
|
|
160
145
|
if (currentValue !== value) {
|
|
161
|
-
recordData.
|
|
146
|
+
recordData.setAttr(identifier, key, value);
|
|
162
147
|
|
|
163
148
|
if (!this.isValid) {
|
|
164
149
|
const { errors } = this;
|
|
@@ -1,10 +1,24 @@
|
|
|
1
|
-
import { assert,
|
|
1
|
+
import { assert, deprecate, warn } from '@ember/debug';
|
|
2
2
|
import { computed } from '@ember/object';
|
|
3
|
+
import { dasherize } from '@ember/string';
|
|
3
4
|
import { DEBUG } from '@glimmer/env';
|
|
4
5
|
|
|
5
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
DEPRECATE_RELATIONSHIPS_WITHOUT_ASYNC,
|
|
8
|
+
DEPRECATE_RELATIONSHIPS_WITHOUT_INVERSE,
|
|
9
|
+
DEPRECATE_RELATIONSHIPS_WITHOUT_TYPE,
|
|
10
|
+
} from '@ember-data/private-build-infra/deprecations';
|
|
11
|
+
|
|
12
|
+
import { lookupLegacySupport } from './model';
|
|
6
13
|
import { computedMacroWithOptionalParams } from './util';
|
|
7
14
|
|
|
15
|
+
function normalizeType(type) {
|
|
16
|
+
if (DEPRECATE_RELATIONSHIPS_WITHOUT_TYPE && !type) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return dasherize(type);
|
|
21
|
+
}
|
|
8
22
|
/**
|
|
9
23
|
@module @ember-data/model
|
|
10
24
|
*/
|
|
@@ -98,7 +112,7 @@ import { computedMacroWithOptionalParams } from './util';
|
|
|
98
112
|
a related resource is known to exist and it has not been loaded.
|
|
99
113
|
|
|
100
114
|
```
|
|
101
|
-
let post = comment.
|
|
115
|
+
let post = comment.post;
|
|
102
116
|
|
|
103
117
|
```
|
|
104
118
|
|
|
@@ -111,29 +125,71 @@ import { computedMacroWithOptionalParams } from './util';
|
|
|
111
125
|
@return {Ember.computed} relationship
|
|
112
126
|
*/
|
|
113
127
|
function belongsTo(modelName, options) {
|
|
114
|
-
let opts
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
128
|
+
let opts = options;
|
|
129
|
+
let userEnteredModelName = modelName;
|
|
130
|
+
if (DEPRECATE_RELATIONSHIPS_WITHOUT_TYPE && (typeof modelName !== 'string' || !modelName.length)) {
|
|
131
|
+
deprecate('belongsTo() must specify the string type of the related resource as the first parameter', false, {
|
|
132
|
+
id: 'ember-data:deprecate-non-strict-relationships',
|
|
133
|
+
for: 'ember-data',
|
|
134
|
+
until: '5.0',
|
|
135
|
+
since: { enabled: '4.8', available: '4.8' },
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
if (typeof modelName === 'object') {
|
|
139
|
+
opts = modelName;
|
|
140
|
+
userEnteredModelName = undefined;
|
|
141
|
+
} else {
|
|
142
|
+
opts = options;
|
|
143
|
+
userEnteredModelName = modelName;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
assert(
|
|
147
|
+
'The first argument to belongsTo must be a string representing a model type key, not an instance of ' +
|
|
148
|
+
typeof userEnteredModelName +
|
|
149
|
+
". E.g., to define a relation to the Person model, use belongsTo('person')",
|
|
150
|
+
typeof userEnteredModelName === 'string' || typeof userEnteredModelName === 'undefined'
|
|
151
|
+
);
|
|
121
152
|
}
|
|
122
153
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
154
|
+
if (DEPRECATE_RELATIONSHIPS_WITHOUT_ASYNC && (!opts || typeof opts.async !== 'boolean')) {
|
|
155
|
+
opts = opts || {};
|
|
156
|
+
if (!('async' in opts)) {
|
|
157
|
+
opts.async = true;
|
|
158
|
+
}
|
|
159
|
+
deprecate('belongsTo(<type>, <options>) must specify options.async as either `true` or `false`.', false, {
|
|
160
|
+
id: 'ember-data:deprecate-non-strict-relationships',
|
|
161
|
+
for: 'ember-data',
|
|
162
|
+
until: '5.0',
|
|
163
|
+
since: { enabled: '4.8', available: '4.8' },
|
|
164
|
+
});
|
|
165
|
+
} else {
|
|
166
|
+
assert(`Expected belongsTo options.async to be a boolean`, opts && typeof opts.async === 'boolean');
|
|
167
|
+
}
|
|
129
168
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
opts.
|
|
169
|
+
if (
|
|
170
|
+
DEPRECATE_RELATIONSHIPS_WITHOUT_INVERSE &&
|
|
171
|
+
opts.inverse !== null &&
|
|
172
|
+
(typeof opts.inverse !== 'string' || opts.inverse.length === 0)
|
|
173
|
+
) {
|
|
174
|
+
deprecate(
|
|
175
|
+
'belongsTo(<type>, <options>) must specify options.inverse as either `null` or string type of the related resource.',
|
|
176
|
+
false,
|
|
177
|
+
{
|
|
178
|
+
id: 'ember-data:deprecate-non-strict-relationships',
|
|
179
|
+
for: 'ember-data',
|
|
180
|
+
until: '5.0',
|
|
181
|
+
since: { enabled: '4.8', available: '4.8' },
|
|
182
|
+
}
|
|
183
|
+
);
|
|
184
|
+
} else {
|
|
185
|
+
assert(
|
|
186
|
+
`Expected belongsTo options.inverse to be either null or the string type of the related resource.`,
|
|
187
|
+
opts.inverse === null || (typeof opts.inverse === 'string' && opts.inverse.length > 0)
|
|
188
|
+
);
|
|
133
189
|
}
|
|
134
190
|
|
|
135
191
|
let meta = {
|
|
136
|
-
type: userEnteredModelName,
|
|
192
|
+
type: normalizeType(userEnteredModelName),
|
|
137
193
|
isRelationship: true,
|
|
138
194
|
options: opts,
|
|
139
195
|
kind: 'belongsTo',
|
|
@@ -143,10 +199,16 @@ function belongsTo(modelName, options) {
|
|
|
143
199
|
|
|
144
200
|
return computed({
|
|
145
201
|
get(key) {
|
|
146
|
-
|
|
202
|
+
// this is a legacy behavior we may not carry into a new model setup
|
|
203
|
+
// it's better to error on disconnected records so users find errors
|
|
204
|
+
// in their logic.
|
|
205
|
+
if (this.isDestroying || this.isDestroyed) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
const support = lookupLegacySupport(this);
|
|
147
209
|
|
|
148
210
|
if (DEBUG) {
|
|
149
|
-
if (['
|
|
211
|
+
if (['currentState'].indexOf(key) !== -1) {
|
|
150
212
|
throw new Error(
|
|
151
213
|
`'${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
214
|
);
|
|
@@ -175,15 +237,15 @@ function belongsTo(modelName, options) {
|
|
|
175
237
|
return support.getBelongsTo(key);
|
|
176
238
|
},
|
|
177
239
|
set(key, value) {
|
|
178
|
-
const support =
|
|
240
|
+
const support = lookupLegacySupport(this);
|
|
179
241
|
if (DEBUG) {
|
|
180
|
-
if (['
|
|
242
|
+
if (['currentState'].indexOf(key) !== -1) {
|
|
181
243
|
throw new Error(
|
|
182
244
|
`'${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
245
|
);
|
|
184
246
|
}
|
|
185
247
|
}
|
|
186
|
-
this.store.
|
|
248
|
+
this.store._join(() => {
|
|
187
249
|
support.setDirtyBelongsTo(key, value);
|
|
188
250
|
});
|
|
189
251
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { deprecate } from '@ember/debug';
|
|
2
|
+
|
|
3
|
+
import { resolve } from 'rsvp';
|
|
4
|
+
|
|
5
|
+
import { PromiseObject } from './promise-proxy-base';
|
|
6
|
+
|
|
7
|
+
function promiseObject<T>(promise: Promise<T>): PromiseObject<T> {
|
|
8
|
+
return PromiseObject.create({
|
|
9
|
+
promise: resolve(promise),
|
|
10
|
+
}) as PromiseObject<T>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// constructor is accessed in some internals but not including it in the copyright for the deprecation
|
|
14
|
+
const ALLOWABLE_METHODS = ['constructor', 'then', 'catch', 'finally'];
|
|
15
|
+
const PROXIED_OBJECT_PROPS = ['content', 'isPending', 'isSettled', 'isRejected', 'isFulfilled', 'promise', 'reason'];
|
|
16
|
+
|
|
17
|
+
export function deprecatedPromiseObject<T>(promise: Promise<T>): PromiseObject<T> {
|
|
18
|
+
const promiseObjectProxy: PromiseObject<T> = promiseObject(promise);
|
|
19
|
+
const handler = {
|
|
20
|
+
get(target: object, prop: string, receiver?: object): unknown {
|
|
21
|
+
if (typeof prop === 'symbol') {
|
|
22
|
+
return Reflect.get(target, prop, receiver);
|
|
23
|
+
}
|
|
24
|
+
if (!ALLOWABLE_METHODS.includes(prop)) {
|
|
25
|
+
deprecate(
|
|
26
|
+
`Accessing ${prop} is deprecated. The return type is being changed fomr PromiseObjectProxy to a Promise. The only available methods to access on this promise are .then, .catch and .finally`,
|
|
27
|
+
false,
|
|
28
|
+
{
|
|
29
|
+
id: 'ember-data:model-save-promise',
|
|
30
|
+
until: '5.0',
|
|
31
|
+
for: '@ember-data/store',
|
|
32
|
+
since: {
|
|
33
|
+
available: '4.4',
|
|
34
|
+
enabled: '4.4',
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const value: unknown = target[prop];
|
|
41
|
+
if (value && typeof value === 'function' && typeof value.bind === 'function') {
|
|
42
|
+
return value.bind(target);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (PROXIED_OBJECT_PROPS.includes(prop)) {
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return undefined;
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return new Proxy(promiseObjectProxy, handler);
|
|
54
|
+
}
|
package/addon/-private/errors.ts
CHANGED
|
@@ -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.
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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.
|
|
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.
|
|
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,13 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
@module @ember-data/model
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import { A } from '@ember/array';
|
|
5
|
+
import { assert, deprecate, inspect } from '@ember/debug';
|
|
5
6
|
import { computed } from '@ember/object';
|
|
7
|
+
import { dasherize } from '@ember/string';
|
|
6
8
|
import { DEBUG } from '@glimmer/env';
|
|
7
9
|
|
|
8
|
-
import {
|
|
10
|
+
import { singularize } from 'ember-inflector';
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
DEPRECATE_RELATIONSHIPS_WITHOUT_ASYNC,
|
|
14
|
+
DEPRECATE_RELATIONSHIPS_WITHOUT_INVERSE,
|
|
15
|
+
DEPRECATE_RELATIONSHIPS_WITHOUT_TYPE,
|
|
16
|
+
} from '@ember-data/private-build-infra/deprecations';
|
|
17
|
+
|
|
18
|
+
import { lookupLegacySupport } from './model';
|
|
9
19
|
import { computedMacroWithOptionalParams } from './util';
|
|
10
20
|
|
|
21
|
+
function normalizeType(type) {
|
|
22
|
+
if (DEPRECATE_RELATIONSHIPS_WITHOUT_TYPE && !type) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return singularize(dasherize(type));
|
|
27
|
+
}
|
|
28
|
+
|
|
11
29
|
/**
|
|
12
30
|
`hasMany` is used to define One-To-Many and Many-To-Many
|
|
13
31
|
relationships on a [Model](/ember-data/release/classes/Model).
|
|
@@ -132,7 +150,7 @@ import { computedMacroWithOptionalParams } from './util';
|
|
|
132
150
|
when any of the known related resources have not been loaded.
|
|
133
151
|
|
|
134
152
|
```
|
|
135
|
-
post.
|
|
153
|
+
post.comments.forEach((comment) => {
|
|
136
154
|
|
|
137
155
|
});
|
|
138
156
|
|
|
@@ -150,21 +168,60 @@ import { computedMacroWithOptionalParams } from './util';
|
|
|
150
168
|
@return {Ember.computed} relationship
|
|
151
169
|
*/
|
|
152
170
|
function hasMany(type, options) {
|
|
153
|
-
if (typeof type
|
|
154
|
-
|
|
155
|
-
|
|
171
|
+
if (DEPRECATE_RELATIONSHIPS_WITHOUT_TYPE && (typeof type !== 'string' || !type.length)) {
|
|
172
|
+
deprecate(
|
|
173
|
+
'hasMany(<type>, <options>) must specify the string type of the related resource as the first parameter',
|
|
174
|
+
false,
|
|
175
|
+
{
|
|
176
|
+
id: 'ember-data:deprecate-non-strict-relationships',
|
|
177
|
+
for: 'ember-data',
|
|
178
|
+
until: '5.0',
|
|
179
|
+
since: { enabled: '4.8', available: '4.8' },
|
|
180
|
+
}
|
|
181
|
+
);
|
|
182
|
+
if (typeof type === 'object') {
|
|
183
|
+
options = type;
|
|
184
|
+
type = undefined;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
assert(
|
|
188
|
+
`The first argument to hasMany must be a string representing a model type key, not an instance of ${inspect(
|
|
189
|
+
type
|
|
190
|
+
)}. E.g., to define a relation to the Comment model, use hasMany('comment')`,
|
|
191
|
+
typeof type === 'string' || typeof type === 'undefined'
|
|
192
|
+
);
|
|
156
193
|
}
|
|
157
194
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
195
|
+
if (DEPRECATE_RELATIONSHIPS_WITHOUT_ASYNC && (!options || typeof options.async !== 'boolean')) {
|
|
196
|
+
options = options || {};
|
|
197
|
+
if (!('async' in options)) {
|
|
198
|
+
options.async = true;
|
|
199
|
+
}
|
|
200
|
+
deprecate('hasMany(<type>, <options>) must specify options.async as either `true` or `false`.', false, {
|
|
201
|
+
id: 'ember-data:deprecate-non-strict-relationships',
|
|
202
|
+
for: 'ember-data',
|
|
203
|
+
until: '5.0',
|
|
204
|
+
since: { enabled: '4.8', available: '4.8' },
|
|
205
|
+
});
|
|
206
|
+
} else {
|
|
207
|
+
assert(`Expected hasMany options.async to be a boolean`, options && typeof options.async === 'boolean');
|
|
208
|
+
}
|
|
164
209
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
options.
|
|
210
|
+
if (
|
|
211
|
+
DEPRECATE_RELATIONSHIPS_WITHOUT_INVERSE &&
|
|
212
|
+
options.inverse !== null &&
|
|
213
|
+
(typeof options.inverse !== 'string' || options.inverse.length === 0)
|
|
214
|
+
) {
|
|
215
|
+
deprecate(
|
|
216
|
+
'hasMany(<type>, <options>) must specify options.inverse as either `null` or string type of the related resource.',
|
|
217
|
+
false,
|
|
218
|
+
{
|
|
219
|
+
id: 'ember-data:deprecate-non-strict-relationships',
|
|
220
|
+
for: 'ember-data',
|
|
221
|
+
until: '5.0',
|
|
222
|
+
since: { enabled: '4.8', available: '4.8' },
|
|
223
|
+
}
|
|
224
|
+
);
|
|
168
225
|
}
|
|
169
226
|
|
|
170
227
|
// Metadata about relationships is stored on the meta of
|
|
@@ -172,7 +229,7 @@ function hasMany(type, options) {
|
|
|
172
229
|
// serialization. Note that `key` is populated lazily
|
|
173
230
|
// the first time the CP is called.
|
|
174
231
|
let meta = {
|
|
175
|
-
type,
|
|
232
|
+
type: normalizeType(type),
|
|
176
233
|
options,
|
|
177
234
|
isRelationship: true,
|
|
178
235
|
kind: 'hasMany',
|
|
@@ -183,25 +240,30 @@ function hasMany(type, options) {
|
|
|
183
240
|
return computed({
|
|
184
241
|
get(key) {
|
|
185
242
|
if (DEBUG) {
|
|
186
|
-
if (['
|
|
243
|
+
if (['currentState'].indexOf(key) !== -1) {
|
|
187
244
|
throw new Error(
|
|
188
245
|
`'${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
246
|
);
|
|
190
247
|
}
|
|
191
248
|
}
|
|
192
|
-
|
|
249
|
+
if (this.isDestroying || this.isDestroyed) {
|
|
250
|
+
return A();
|
|
251
|
+
}
|
|
252
|
+
return lookupLegacySupport(this).getHasMany(key);
|
|
193
253
|
},
|
|
194
254
|
set(key, records) {
|
|
195
255
|
if (DEBUG) {
|
|
196
|
-
if (['
|
|
256
|
+
if (['currentState'].indexOf(key) !== -1) {
|
|
197
257
|
throw new Error(
|
|
198
258
|
`'${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
259
|
);
|
|
200
260
|
}
|
|
201
261
|
}
|
|
202
|
-
const support =
|
|
203
|
-
|
|
204
|
-
|
|
262
|
+
const support = lookupLegacySupport(this);
|
|
263
|
+
const manyArray = support.getManyArray(key);
|
|
264
|
+
assert(`You must pass an array of records to set a hasMany relationship`, Array.isArray(records));
|
|
265
|
+
this.store._join(() => {
|
|
266
|
+
manyArray.splice(0, manyArray.length, ...records);
|
|
205
267
|
});
|
|
206
268
|
|
|
207
269
|
return support.getHasMany(key);
|
|
@@ -3,11 +3,15 @@ import { DEBUG } from '@glimmer/env';
|
|
|
3
3
|
|
|
4
4
|
import { resolve } from 'rsvp';
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
DEPRECATE_RELATIONSHIPS_WITHOUT_INVERSE,
|
|
8
|
+
DEPRECATE_RSVP_PROMISE,
|
|
9
|
+
} from '@ember-data/private-build-infra/deprecations';
|
|
7
10
|
|
|
8
11
|
import { iterateData, normalizeResponseHelper } from './legacy-data-utils';
|
|
9
12
|
|
|
10
13
|
export function _findHasMany(adapter, store, identifier, link, relationship, options) {
|
|
14
|
+
const record = store._instanceCache.getRecord(identifier);
|
|
11
15
|
const snapshot = store._instanceCache.createSnapshot(identifier, options);
|
|
12
16
|
let modelClass = store.modelFor(relationship.type);
|
|
13
17
|
let useLink = !link || typeof link === 'string';
|
|
@@ -18,7 +22,7 @@ export function _findHasMany(adapter, store, identifier, link, relationship, opt
|
|
|
18
22
|
promise = guardDestroyedStore(promise, store, label);
|
|
19
23
|
promise = promise.then(
|
|
20
24
|
(adapterPayload) => {
|
|
21
|
-
if (!_objectIsAlive(
|
|
25
|
+
if (!_objectIsAlive(record)) {
|
|
22
26
|
if (DEPRECATE_RSVP_PROMISE) {
|
|
23
27
|
deprecate(
|
|
24
28
|
`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 +61,14 @@ export function _findHasMany(adapter, store, identifier, link, relationship, opt
|
|
|
57
61
|
);
|
|
58
62
|
|
|
59
63
|
if (DEPRECATE_RSVP_PROMISE) {
|
|
60
|
-
promise = _guard(promise, _bind(_objectIsAlive,
|
|
64
|
+
promise = _guard(promise, _bind(_objectIsAlive, record));
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
return promise;
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
export function _findBelongsTo(store, identifier, link, relationship, options) {
|
|
71
|
+
const record = store._instanceCache.getRecord(identifier);
|
|
67
72
|
let adapter = store.adapterFor(identifier.type);
|
|
68
73
|
|
|
69
74
|
assert(`You tried to load a belongsTo relationship but you have no adapter (for ${identifier.type})`, adapter);
|
|
@@ -79,11 +84,11 @@ export function _findBelongsTo(store, identifier, link, relationship, options) {
|
|
|
79
84
|
let label = `DS: Handle Adapter#findBelongsTo of ${identifier.type} : ${relationship.type}`;
|
|
80
85
|
|
|
81
86
|
promise = guardDestroyedStore(promise, store, label);
|
|
82
|
-
promise = _guard(promise, _bind(_objectIsAlive,
|
|
87
|
+
promise = _guard(promise, _bind(_objectIsAlive, record));
|
|
83
88
|
|
|
84
89
|
promise = promise.then(
|
|
85
90
|
(adapterPayload) => {
|
|
86
|
-
if (!_objectIsAlive(
|
|
91
|
+
if (!_objectIsAlive(record)) {
|
|
87
92
|
if (DEPRECATE_RSVP_PROMISE) {
|
|
88
93
|
deprecate(
|
|
89
94
|
`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 +128,7 @@ export function _findBelongsTo(store, identifier, link, relationship, options) {
|
|
|
123
128
|
);
|
|
124
129
|
|
|
125
130
|
if (DEPRECATE_RSVP_PROMISE) {
|
|
126
|
-
promise = _guard(promise, _bind(_objectIsAlive,
|
|
131
|
+
promise = _guard(promise, _bind(_objectIsAlive, record));
|
|
127
132
|
}
|
|
128
133
|
|
|
129
134
|
return promise;
|
|
@@ -229,19 +234,36 @@ function ensureRelationshipIsSetToParent(payload, parentIdentifier, store, paren
|
|
|
229
234
|
}
|
|
230
235
|
}
|
|
231
236
|
|
|
232
|
-
function
|
|
233
|
-
return
|
|
237
|
+
function metaIsRelationshipDefinition(meta) {
|
|
238
|
+
return typeof meta._inverseKey === 'function';
|
|
234
239
|
}
|
|
235
240
|
|
|
236
|
-
function
|
|
241
|
+
function inverseForRelationship(store, identifier, key) {
|
|
242
|
+
const definition = store.getSchemaDefinitionService().relationshipsDefinitionFor(identifier)[key];
|
|
243
|
+
if (!definition) {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (DEPRECATE_RELATIONSHIPS_WITHOUT_INVERSE && metaIsRelationshipDefinition(definition)) {
|
|
248
|
+
const modelClass = store.modelFor(identifier.type);
|
|
249
|
+
return definition._inverseKey(store, modelClass);
|
|
250
|
+
}
|
|
251
|
+
assert(
|
|
252
|
+
`Expected the relationship defintion to specify the inverse type or null.`,
|
|
253
|
+
definition.options?.inverse === null ||
|
|
254
|
+
(typeof definition.options?.inverse === 'string' && definition.options.inverse.length > 0)
|
|
255
|
+
);
|
|
256
|
+
return definition.options.inverse;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function getInverse(store, parentIdentifier, parentRelationship, type) {
|
|
237
260
|
let { name: lhs_relationshipName } = parentRelationship;
|
|
238
261
|
let { type: parentType } = parentIdentifier;
|
|
239
|
-
let inverseKey =
|
|
262
|
+
let inverseKey = inverseForRelationship(store, { type: parentType }, lhs_relationshipName);
|
|
240
263
|
|
|
241
264
|
if (inverseKey) {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
} = store._instanceCache._storeWrapper.relationshipsDefinitionFor(type)[inverseKey];
|
|
265
|
+
const definition = store.getSchemaDefinitionService().relationshipsDefinitionFor({ type });
|
|
266
|
+
let { kind } = definition[inverseKey];
|
|
245
267
|
return {
|
|
246
268
|
inverseKey,
|
|
247
269
|
kind,
|
|
@@ -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';
|