ember-data-model-fragments 4.0.0 → 5.0.0-beta.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.
- package/.vscode/settings.json +7 -0
- package/CHANGELOG.md +10 -1
- package/LICENSE.md +9 -0
- package/README.md +11 -6
- package/addon/array/fragment.js +7 -7
- package/addon/array/stateful.js +3 -3
- package/addon/attributes.js +26 -190
- package/addon/ext.js +43 -72
- package/addon/fragment.js +7 -7
- package/addon/record-data.js +531 -0
- package/addon/states.js +15 -12
- package/addon/transforms/array.js +7 -6
- package/addon/transforms/fragment-array.js +3 -2
- package/addon/transforms/fragment.js +9 -7
- package/app/initializers/model-fragments.js +3 -4
- package/index.js +1 -1
- package/package.json +43 -31
- package/.eslintignore +0 -16
- package/.eslintrc.js +0 -66
package/addon/fragment.js
CHANGED
|
@@ -110,16 +110,16 @@ const Fragment = Model.extend(Ember.Comparable, Copyable, {
|
|
|
110
110
|
},
|
|
111
111
|
|
|
112
112
|
/**
|
|
113
|
-
@method
|
|
113
|
+
@method _didCommit
|
|
114
114
|
*/
|
|
115
|
-
|
|
115
|
+
_didCommit(data) {
|
|
116
116
|
internalModelFor(this).adapterDidCommit({
|
|
117
117
|
attributes: data || Object.create(null)
|
|
118
118
|
});
|
|
119
119
|
},
|
|
120
120
|
|
|
121
121
|
/**
|
|
122
|
-
@method
|
|
122
|
+
@method _didCommit
|
|
123
123
|
*/
|
|
124
124
|
_adapterDidError() {
|
|
125
125
|
internalModelFor(this)._recordData.commitWasRejected();
|
|
@@ -127,7 +127,7 @@ const Fragment = Model.extend(Ember.Comparable, Copyable, {
|
|
|
127
127
|
|
|
128
128
|
toStringExtension() {
|
|
129
129
|
let internalModel = internalModelFor(this);
|
|
130
|
-
let owner = internalModel && internalModel._recordData.
|
|
130
|
+
let owner = internalModel && internalModel._recordData._owner;
|
|
131
131
|
if (owner) {
|
|
132
132
|
let ownerId = get(owner, 'id');
|
|
133
133
|
return `owner(${ownerId})`;
|
|
@@ -179,10 +179,10 @@ export function internalModelFor(record) {
|
|
|
179
179
|
export function setFragmentOwner(fragment, record, key) {
|
|
180
180
|
let internalModel = internalModelFor(fragment);
|
|
181
181
|
|
|
182
|
-
assert('To preserve rollback semantics, fragments can only belong to one owner. Try copying instead', !internalModel._recordData.
|
|
182
|
+
assert('To preserve rollback semantics, fragments can only belong to one owner. Try copying instead', !internalModel._recordData._owner || internalModel._recordData._owner === record);
|
|
183
183
|
|
|
184
|
-
internalModel._recordData.
|
|
185
|
-
internalModel._recordData.
|
|
184
|
+
internalModel._recordData._owner = record;
|
|
185
|
+
internalModel._recordData._name = key;
|
|
186
186
|
|
|
187
187
|
// Notify any observers of `fragmentOwner` properties
|
|
188
188
|
get(fragment.constructor, 'fragmentOwnerProperties').forEach(name => {
|
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
// eslint-disable-next-line ember/use-ember-data-rfc-395-imports
|
|
2
|
+
import { RecordData } from 'ember-data/-private';
|
|
3
|
+
import { assert } from '@ember/debug';
|
|
4
|
+
import { typeOf } from '@ember/utils';
|
|
5
|
+
import { setProperties, get } from '@ember/object';
|
|
6
|
+
import { copy } from 'ember-copy';
|
|
7
|
+
import isInstanceOfType from './util/instance-of-type';
|
|
8
|
+
import { isArray } from '@ember/array';
|
|
9
|
+
import { fragmentDidDirty, fragmentDidReset } from './states';
|
|
10
|
+
import StatefulArray from './array/stateful';
|
|
11
|
+
import FragmentArray from './array/fragment';
|
|
12
|
+
import {
|
|
13
|
+
setFragmentOwner,
|
|
14
|
+
createFragment,
|
|
15
|
+
isFragment
|
|
16
|
+
} from './fragment';
|
|
17
|
+
import { assign } from '@ember/polyfills';
|
|
18
|
+
import { gte } from 'ember-compatibility-helpers';
|
|
19
|
+
|
|
20
|
+
let fragmentRecordDatas = new WeakMap();
|
|
21
|
+
|
|
22
|
+
export default class FragmentRecordData extends RecordData {
|
|
23
|
+
constructor(identifier, store) {
|
|
24
|
+
if (gte('ember-data', '3.15.0')) {
|
|
25
|
+
super(identifier, store);
|
|
26
|
+
} else {
|
|
27
|
+
super(identifier.type, identifier.id, identifier.clientId, store);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// @patocallaghan - We need to keep a copy of the record so we can recreate the fragment/fragmentArray.
|
|
31
|
+
this._record = null;
|
|
32
|
+
this.fragmentData = Object.create(null);
|
|
33
|
+
this.serverFragments = Object.create(null);
|
|
34
|
+
this.inFlightFragments = Object.create(null);
|
|
35
|
+
|
|
36
|
+
this.fragments = Object.create(null);
|
|
37
|
+
// @patocallaghan - We need to keep the fragment definitions for later on in case we need recreate the fragment or fragmentArray
|
|
38
|
+
this.fragmentDefs = Object.create(null);
|
|
39
|
+
let defs = store.attributesDefinitionFor(identifier.type, identifier.id, identifier.lid);
|
|
40
|
+
|
|
41
|
+
Object.keys(defs).forEach(key => {
|
|
42
|
+
let options = defs[key];
|
|
43
|
+
if (options.isFragment) {
|
|
44
|
+
this.fragmentDefs[key] = defs[key];
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
fragmentRecordDatas.set(identifier, this);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Returns the value of the property or the default propery
|
|
51
|
+
getFragmentDataWithDefault(key, options, type) {
|
|
52
|
+
let data = this.fragmentData[key];
|
|
53
|
+
if (data !== undefined) {
|
|
54
|
+
return data;
|
|
55
|
+
}
|
|
56
|
+
return getFragmentDefaultValue(options, type);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
setupFragment(key, options, declaredModelName, record) {
|
|
60
|
+
// @patocallaghan - This is extremely janky way of making sure we always have a copy of the record saved. There must be a better way of doing this?
|
|
61
|
+
this._record = record;
|
|
62
|
+
let data = this.getFragmentDataWithDefault(key, options, 'object');
|
|
63
|
+
let fragment = this.fragments[key];
|
|
64
|
+
|
|
65
|
+
if (!data) {
|
|
66
|
+
this.serverFragments[key] = data;
|
|
67
|
+
return data;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!fragment) {
|
|
71
|
+
fragment = createFragment(
|
|
72
|
+
record.store,
|
|
73
|
+
declaredModelName,
|
|
74
|
+
record,
|
|
75
|
+
key,
|
|
76
|
+
options,
|
|
77
|
+
data
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
this.serverFragments[key] = fragment;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return fragment;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
getFragment(key, options, declaredModelName, record) {
|
|
87
|
+
this._record = record;
|
|
88
|
+
let fragment = this.getFragmentWithoutCreating(key);
|
|
89
|
+
if (fragment === undefined) {
|
|
90
|
+
return this.setupFragment(key, options, declaredModelName, record);
|
|
91
|
+
} else {
|
|
92
|
+
return fragment;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
setFragmentArrayValue(key, fragments, value, record, declaredModelName, options, isFragmentArray) {
|
|
97
|
+
this._record = record;
|
|
98
|
+
if (isArray(value)) {
|
|
99
|
+
if (!fragments) {
|
|
100
|
+
if (isFragmentArray) {
|
|
101
|
+
fragments = FragmentArray.create({
|
|
102
|
+
type: declaredModelName,
|
|
103
|
+
options: options,
|
|
104
|
+
name: key,
|
|
105
|
+
owner: record
|
|
106
|
+
});
|
|
107
|
+
} else {
|
|
108
|
+
fragments = StatefulArray.create({
|
|
109
|
+
options: options,
|
|
110
|
+
name: key,
|
|
111
|
+
owner: record
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
fragments.setObjects(value);
|
|
116
|
+
} else if (value === null) {
|
|
117
|
+
fragments = null;
|
|
118
|
+
this.fragments[key] = null;
|
|
119
|
+
} else {
|
|
120
|
+
assert('A fragment array property can only be assigned an array or null');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (this.serverFragments[key] !== fragments || get(fragments, 'hasDirtyAttributes')) {
|
|
124
|
+
fragmentDidDirty(record, key, fragments);
|
|
125
|
+
} else {
|
|
126
|
+
fragmentDidReset(record, key);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return fragments;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
setFragmentValue(key, fragment, value, record, declaredModelName, options) {
|
|
133
|
+
|
|
134
|
+
// Model Fragments are hard tied to DS.Model, and all DS.Models have the store on them.
|
|
135
|
+
// We need access to the store because EDMF uses the store for `createRecord`
|
|
136
|
+
this._record = record;
|
|
137
|
+
let store = record.store;
|
|
138
|
+
assert(
|
|
139
|
+
`You can only assign \`null\`, an object literal or a '${declaredModelName}' fragment instance to this property`,
|
|
140
|
+
value === null ||
|
|
141
|
+
typeOf(value) === 'object' ||
|
|
142
|
+
isInstanceOfType(store.modelFor(declaredModelName), value)
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
if (!value) {
|
|
146
|
+
fragment = null;
|
|
147
|
+
} else if (isFragment(value)) {
|
|
148
|
+
// A fragment instance was given, so just replace the existing value
|
|
149
|
+
fragment = setFragmentOwner(value, record, key);
|
|
150
|
+
} else if (!fragment) {
|
|
151
|
+
// A property hash was given but the property was null, so create a new
|
|
152
|
+
// fragment with the data
|
|
153
|
+
fragment = createFragment(
|
|
154
|
+
store,
|
|
155
|
+
declaredModelName,
|
|
156
|
+
record,
|
|
157
|
+
key,
|
|
158
|
+
options,
|
|
159
|
+
value
|
|
160
|
+
);
|
|
161
|
+
} else {
|
|
162
|
+
// The fragment already exists and a property hash is given, so just set
|
|
163
|
+
// its values and let the state machine take care of the dirtiness
|
|
164
|
+
setProperties(fragment, value);
|
|
165
|
+
|
|
166
|
+
return fragment;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
let currentFragment = this.getFragment(key, options, declaredModelName, record);
|
|
170
|
+
|
|
171
|
+
if (currentFragment !== fragment) {
|
|
172
|
+
this.fragments[key] = fragment;
|
|
173
|
+
fragmentDidDirty(record, key, fragment);
|
|
174
|
+
} else {
|
|
175
|
+
fragmentDidReset(record, key);
|
|
176
|
+
}
|
|
177
|
+
return fragment;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
getFragmentArray(key, options, declaredModelName, record, isFragmentArray) {
|
|
181
|
+
this._record = record;
|
|
182
|
+
let data = this.getFragmentDataWithDefault(key, options, 'array');
|
|
183
|
+
let fragmentArray = this.getFragmentWithoutCreating(key);
|
|
184
|
+
|
|
185
|
+
// If we already have a processed fragment in _data and our current fragment is
|
|
186
|
+
// null simply reuse the one from data. We can be in this state after a rollback
|
|
187
|
+
// for example
|
|
188
|
+
if (fragmentArray === undefined) {
|
|
189
|
+
fragmentArray = this.setupFragmentArray(key, record, data, declaredModelName, options, isFragmentArray);
|
|
190
|
+
return fragmentArray;
|
|
191
|
+
} else {
|
|
192
|
+
return fragmentArray;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
setupFragmentArray(key, record, data, declaredModelName, options, isFragmentArray) {
|
|
197
|
+
this._record = record;
|
|
198
|
+
let fragmentArray;
|
|
199
|
+
if (data !== null) {
|
|
200
|
+
if (isFragmentArray) {
|
|
201
|
+
fragmentArray = FragmentArray.create({
|
|
202
|
+
type: declaredModelName,
|
|
203
|
+
options: options,
|
|
204
|
+
name: key,
|
|
205
|
+
owner: record
|
|
206
|
+
});
|
|
207
|
+
} else {
|
|
208
|
+
fragmentArray = StatefulArray.create({
|
|
209
|
+
options: options,
|
|
210
|
+
name: key,
|
|
211
|
+
owner: record
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
fragmentArray.setupData(data);
|
|
215
|
+
} else {
|
|
216
|
+
fragmentArray = null;
|
|
217
|
+
}
|
|
218
|
+
this.serverFragments[key] = fragmentArray;
|
|
219
|
+
return fragmentArray;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
getFragmentWithoutCreating(key) {
|
|
223
|
+
if (this.fragments[key] !== undefined) {
|
|
224
|
+
return this.fragments[key];
|
|
225
|
+
} else if (this.inFlightFragments[key] !== undefined) {
|
|
226
|
+
return this.inFlightFragments[key];
|
|
227
|
+
} else if (this.serverFragments[key] !== undefined) {
|
|
228
|
+
return this.serverFragments[key];
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// PUBLIC API
|
|
232
|
+
|
|
233
|
+
setupFragmentData(data, calculateChange) {
|
|
234
|
+
let keys = [];
|
|
235
|
+
if (data.attributes) {
|
|
236
|
+
Object.keys(this.fragmentDefs).forEach(name => {
|
|
237
|
+
if (calculateChange && this.getFragmentWithoutCreating(name) !== undefined) {
|
|
238
|
+
keys.push(name);
|
|
239
|
+
}
|
|
240
|
+
if (name in data.attributes) {
|
|
241
|
+
this.fragmentData[name] = data.attributes[name];
|
|
242
|
+
let serverFragment = this.serverFragments[name];
|
|
243
|
+
if (serverFragment) {
|
|
244
|
+
let fragmentKeys = [];
|
|
245
|
+
if (data.attributes[name] === null) {
|
|
246
|
+
// if we have data with a Fragment set up, but now we've received null,
|
|
247
|
+
// delete the null fragment from serverFragments.
|
|
248
|
+
delete this.serverFragments[name];
|
|
249
|
+
} else if (serverFragment instanceof StatefulArray) {
|
|
250
|
+
serverFragment.setupData(data.attributes[name]);
|
|
251
|
+
} else {
|
|
252
|
+
fragmentKeys = serverFragment._internalModel._recordData.pushData({ attributes: data.attributes[name] }, calculateChange);
|
|
253
|
+
}
|
|
254
|
+
if (calculateChange) {
|
|
255
|
+
// TODO (Custom Model Classes) cleanup this api usage
|
|
256
|
+
fragmentKeys.forEach((fragmentKey) => serverFragment.notifyPropertyChange(fragmentKey));
|
|
257
|
+
}
|
|
258
|
+
} else if (serverFragment === null) {
|
|
259
|
+
// if we received data that set the fragment to null, but now we've received different data,
|
|
260
|
+
// delete the null fragment from serverFragments.
|
|
261
|
+
delete this.serverFragments[name];
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
return keys;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
pushData(tempData, calculateChange) {
|
|
270
|
+
let data = tempData;
|
|
271
|
+
|
|
272
|
+
let ourAttributes = {};
|
|
273
|
+
if (data.attributes) {
|
|
274
|
+
Object.keys(this.fragmentDefs).forEach(name => {
|
|
275
|
+
if (name in data.attributes) {
|
|
276
|
+
ourAttributes[name] = data.attributes[name];
|
|
277
|
+
delete data.attributes[name];
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
let keys = this.setupFragmentData({ attributes: ourAttributes }, calculateChange);
|
|
282
|
+
let edKeys = super.pushData(data, calculateChange);
|
|
283
|
+
// TODO: for some reason, tempData is actually being modified. We need to merge
|
|
284
|
+
// the fragment data back in here so that it's not lost when we go back to the
|
|
285
|
+
// function calling pushData.
|
|
286
|
+
if (data.attributes) {
|
|
287
|
+
assign(data.attributes, ourAttributes);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return keys.concat(edKeys);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
willCommit() {
|
|
294
|
+
let key, fragment;
|
|
295
|
+
for (key in this.fragments) {
|
|
296
|
+
fragment = this.fragments[key];
|
|
297
|
+
if (fragment) {
|
|
298
|
+
// TODO (Custom Model Classes) this bad, we should keep track of fragment record datas ourself
|
|
299
|
+
if (fragment.content) {
|
|
300
|
+
fragment.content.forEach(frag => {
|
|
301
|
+
// check to see if the array is a non-fragment array, if so, don't call
|
|
302
|
+
// method on _internalModel.recordData
|
|
303
|
+
// TODO: We need to add inFlight details to statefulArray, then change this.
|
|
304
|
+
if (frag._internalModel) {
|
|
305
|
+
frag._internalModel._recordData.willCommit();
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
} else {
|
|
309
|
+
fragment._internalModel._recordData.willCommit();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
delete this.fragments[key];
|
|
313
|
+
this.inFlightFragments[key] = fragment;
|
|
314
|
+
}
|
|
315
|
+
super.willCommit();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
hasChangedAttributes() {
|
|
319
|
+
return super.hasChangedAttributes() ||
|
|
320
|
+
Object.keys(this.fragmentDefs).some(fragmentName => {
|
|
321
|
+
const fragment = this.getFragmentWithoutCreating(fragmentName);
|
|
322
|
+
return fragment && fragment.hasDirtyAttributes;
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
resetRecord() {
|
|
327
|
+
super.resetRecord();
|
|
328
|
+
this.resetFragments();
|
|
329
|
+
}
|
|
330
|
+
resetFragments() {
|
|
331
|
+
let key, fragment;
|
|
332
|
+
for (key in this.fragments) {
|
|
333
|
+
fragment = this.fragments[key];
|
|
334
|
+
if (fragment) {
|
|
335
|
+
fragment.destroy();
|
|
336
|
+
delete this.fragments[key];
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
for (key in this.inFlightFragments) {
|
|
341
|
+
fragment = this.inFlightFragments[key];
|
|
342
|
+
if (fragment) {
|
|
343
|
+
fragment.destroy();
|
|
344
|
+
delete this.inFlightFragments[key];
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
for (key in this.serverFragments) {
|
|
349
|
+
fragment = this.serverFragments[key];
|
|
350
|
+
if (fragment) {
|
|
351
|
+
fragment.destroy();
|
|
352
|
+
delete this.serverFragments[key];
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/*
|
|
358
|
+
Returns an object, whose keys are changed properties, and value is an
|
|
359
|
+
[oldProp, newProp] array.
|
|
360
|
+
|
|
361
|
+
@method changedAttributes
|
|
362
|
+
@private
|
|
363
|
+
*/
|
|
364
|
+
changedAttributes() {
|
|
365
|
+
// NOTE: This is currently implemented in a very odd way in the case where the property on a fragment changes
|
|
366
|
+
// In that case, the expected return of changedAttributes is [ currentFragment, currentFragment ]
|
|
367
|
+
// This seems very broken, but might be a breaking change to fix.
|
|
368
|
+
// See the test named `changes to fragments are indicated in the owner record\'s `changedAttributes`
|
|
369
|
+
// for more details
|
|
370
|
+
|
|
371
|
+
let ourChanges = super.changedAttributes();
|
|
372
|
+
for (let key of Object.keys(this.fragmentDefs)) {
|
|
373
|
+
// either the whole fragment was replaced, or a property on the fragment was replaced
|
|
374
|
+
// this is the case where we replaced the whole framgent
|
|
375
|
+
let newFragment;
|
|
376
|
+
// We give priority to client set fragments in this.fragments but fall back to inFlight ones
|
|
377
|
+
// in case the record is already on the way
|
|
378
|
+
if (this.inFlightFragments[key] !== undefined) {
|
|
379
|
+
// TODD this code path isn't tested right now
|
|
380
|
+
newFragment = this.inFlightFragments[key];
|
|
381
|
+
}
|
|
382
|
+
if (this.fragments[key] !== undefined) {
|
|
383
|
+
newFragment = this.fragments[key];
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (newFragment !== undefined) {
|
|
387
|
+
// if we have a local fragment defined that means that we set that locally, so we need to diff against whatever was on the server
|
|
388
|
+
ourChanges[key] = [this.serverFragments[key], this.fragments[key]];
|
|
389
|
+
} else {
|
|
390
|
+
// this is the case where the fragment did not change but the props on it might have
|
|
391
|
+
// TODO diff against server
|
|
392
|
+
// otherwise we check to see if there are changes on the serverFragment and in that case the whole change is just the
|
|
393
|
+
// local change of that fragment
|
|
394
|
+
let fragment = this.serverFragments[key];
|
|
395
|
+
|
|
396
|
+
let hasChanged;
|
|
397
|
+
if (fragment && fragment instanceof StatefulArray) {
|
|
398
|
+
hasChanged = get(fragment, 'hasDirtyAttributes');
|
|
399
|
+
} else if (fragment) {
|
|
400
|
+
hasChanged = fragment._internalModel._recordData.hasChangedAttributes();
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (hasChanged) {
|
|
404
|
+
// NOTE: As explained above, this is very odd, and we should probably change it.
|
|
405
|
+
ourChanges[key] = [fragment, fragment];
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return ourChanges;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
rollbackAttributes() {
|
|
413
|
+
let keys = [];
|
|
414
|
+
for (let key in this.fragments) {
|
|
415
|
+
keys.push(key);
|
|
416
|
+
delete this.fragments[key];
|
|
417
|
+
}
|
|
418
|
+
Object.keys(this.fragmentDefs).forEach((key) => {
|
|
419
|
+
let fragment = this.getFragmentWithoutCreating(key);
|
|
420
|
+
if (fragment) {
|
|
421
|
+
fragment.rollbackAttributes();
|
|
422
|
+
keys.push(key);
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
return keys.concat(super.rollbackAttributes());
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
didCommit(data) {
|
|
429
|
+
let fragment, attributes;
|
|
430
|
+
if (data && data.attributes) {
|
|
431
|
+
attributes = data.attributes;
|
|
432
|
+
} else {
|
|
433
|
+
attributes = Object.create(null);
|
|
434
|
+
}
|
|
435
|
+
for (let key in this.inFlightFragments) {
|
|
436
|
+
fragment = this.inFlightFragments[key];
|
|
437
|
+
delete this.inFlightFragments[key];
|
|
438
|
+
this.serverFragments[key] = fragment;
|
|
439
|
+
}
|
|
440
|
+
Object.keys(this.fragmentDefs).forEach(key => {
|
|
441
|
+
fragment = this.serverFragments[key];
|
|
442
|
+
// @patocallaghan - So basically if the existing fragment/fragmentArray is null but it is part of the response we need to recreate the fragment/fragment array and re-add it to the record.
|
|
443
|
+
// Unfortunately we have no access to the `record` (is there a way to get it from `RecordData`?) in this codepath hence why we need to do the `this._record` hack.
|
|
444
|
+
if (!fragment && attributes[key] && this._record) {
|
|
445
|
+
let def = this.fragmentDefs[key];
|
|
446
|
+
let declaredModelName = def.type.split('$')[1];
|
|
447
|
+
if (def.type.includes('-array')) {
|
|
448
|
+
fragment = this.setupFragmentArray(key, this._record, attributes[key], declaredModelName, def.options, def.type.includes('fragment-array'));
|
|
449
|
+
} else {
|
|
450
|
+
fragment = createFragment(this._record.store, def.type.split('$')[1], this._record, key, def.options, attributes[key]);
|
|
451
|
+
}
|
|
452
|
+
// @patocallaghan - Not sure of the repercussions of just re-assigning the new fragment here. Should it be done a better way?
|
|
453
|
+
this._record[key] = fragment;
|
|
454
|
+
}
|
|
455
|
+
if (fragment) {
|
|
456
|
+
fragment._didCommit(attributes[key]);
|
|
457
|
+
}
|
|
458
|
+
delete attributes[key];
|
|
459
|
+
});
|
|
460
|
+
return super.didCommit(data);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
commitWasRejected() {
|
|
464
|
+
let key, fragment;
|
|
465
|
+
for (key in this.inFlightFragments) {
|
|
466
|
+
fragment = this.inFlightFragments[key];
|
|
467
|
+
delete this.inFlightFragments[key];
|
|
468
|
+
if (this.fragments[key] === undefined) {
|
|
469
|
+
this.fragments[key] = fragment;
|
|
470
|
+
}
|
|
471
|
+
if (fragment) {
|
|
472
|
+
// TODO this bad, we should keep track of fragment record datas ourself
|
|
473
|
+
if (fragment.content) {
|
|
474
|
+
fragment.content.forEach(frag => {
|
|
475
|
+
// check to see if the array is a non-fragment array, if so, don't call
|
|
476
|
+
// method on _internalModel.recordData
|
|
477
|
+
// TODO: We need to add inFlight details to statefulArray, then change this.
|
|
478
|
+
if (frag._internalModel) {
|
|
479
|
+
frag._internalModel._recordData.commitWasRejected();
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
} else {
|
|
483
|
+
fragment._internalModel._recordData.commitWasRejected();
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
return super.commitWasRejected(...arguments);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
setAttr(key, value) {
|
|
491
|
+
return super.setAttr(key, value);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
getAttr(key) {
|
|
495
|
+
return super.getAttr(key);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
hasAttr(key) {
|
|
499
|
+
return super.hasAttr(key);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
didCreateLocally() {
|
|
503
|
+
return super.didCreateLocally();
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
export { fragmentRecordDatas };
|
|
508
|
+
|
|
509
|
+
// The default value of a fragment is either an array or an object,
|
|
510
|
+
// which should automatically get deep copied
|
|
511
|
+
function getFragmentDefaultValue(options, type) {
|
|
512
|
+
let value;
|
|
513
|
+
|
|
514
|
+
if (typeof options.defaultValue === 'function') {
|
|
515
|
+
return options.defaultValue();
|
|
516
|
+
} else if ('defaultValue' in options) {
|
|
517
|
+
value = options.defaultValue;
|
|
518
|
+
} else if (type === 'array') {
|
|
519
|
+
value = [];
|
|
520
|
+
} else {
|
|
521
|
+
return null;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
assert(
|
|
525
|
+
`The fragment's default value must be an ${type}`,
|
|
526
|
+
typeOf(value) == type || value === null
|
|
527
|
+
);
|
|
528
|
+
|
|
529
|
+
// Create a deep copy of the resulting value to avoid shared reference errors
|
|
530
|
+
return copy(value, true);
|
|
531
|
+
}
|
package/addon/states.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { get } from '@ember/object';
|
|
2
|
+
// eslint-disable-next-line ember/use-ember-data-rfc-395-imports
|
|
2
3
|
import { RootState } from 'ember-data/-private';
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -8,15 +9,15 @@ import { RootState } from 'ember-data/-private';
|
|
|
8
9
|
const didSetProperty = RootState.loaded.saved.didSetProperty;
|
|
9
10
|
const propertyWasReset = RootState.loaded.updated.uncommitted.propertyWasReset;
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
const dirtySetup = function(internalModel) {
|
|
13
|
+
let record = internalModel._recordData._owner;
|
|
14
|
+
let key = internalModel._recordData._name;
|
|
14
15
|
|
|
15
16
|
// A newly created fragment may not have an owner yet
|
|
16
17
|
if (record) {
|
|
17
18
|
fragmentDidDirty(record, key, internalModel);
|
|
18
19
|
}
|
|
19
|
-
}
|
|
20
|
+
};
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
Like `DS.Model` instances, all fragments have a `currentState` property
|
|
@@ -73,17 +74,20 @@ let FragmentRootState = {
|
|
|
73
74
|
},
|
|
74
75
|
|
|
75
76
|
loaded: {
|
|
77
|
+
unloadRecord() {
|
|
78
|
+
},
|
|
79
|
+
|
|
76
80
|
pushedData(internalModel) {
|
|
77
81
|
internalModel.transitionTo('saved');
|
|
78
82
|
},
|
|
79
83
|
|
|
80
84
|
saved: {
|
|
81
85
|
setup(internalModel) {
|
|
82
|
-
let record = internalModel._recordData.
|
|
83
|
-
let key = internalModel._recordData.
|
|
86
|
+
let record = internalModel._recordData._owner;
|
|
87
|
+
let key = internalModel._recordData._name;
|
|
84
88
|
|
|
85
89
|
// Abort if fragment is still initializing
|
|
86
|
-
if (!record._internalModel._recordData.
|
|
90
|
+
if (!record._internalModel._recordData.getFragmentWithoutCreating(key)) {
|
|
87
91
|
return;
|
|
88
92
|
}
|
|
89
93
|
|
|
@@ -101,6 +105,7 @@ let FragmentRootState = {
|
|
|
101
105
|
becomeDirty(internalModel) {
|
|
102
106
|
internalModel.transitionTo('updated');
|
|
103
107
|
}
|
|
108
|
+
|
|
104
109
|
},
|
|
105
110
|
|
|
106
111
|
created: {
|
|
@@ -148,6 +153,7 @@ function wireState(object, parent, name) {
|
|
|
148
153
|
object.stateName = name;
|
|
149
154
|
|
|
150
155
|
for (let prop in object) {
|
|
156
|
+
// eslint-disable-next-line no-prototype-builtins
|
|
151
157
|
if (!object.hasOwnProperty(prop) || prop === 'parentState' || prop === 'stateName') {
|
|
152
158
|
continue;
|
|
153
159
|
}
|
|
@@ -163,11 +169,8 @@ FragmentRootState = wireState(FragmentRootState, null, 'root');
|
|
|
163
169
|
|
|
164
170
|
export default FragmentRootState;
|
|
165
171
|
|
|
166
|
-
export function fragmentDidDirty(record
|
|
172
|
+
export function fragmentDidDirty(record) {
|
|
167
173
|
if (!record.currentState.isDeleted) {
|
|
168
|
-
// Add the fragment as a placeholder in the owner record's
|
|
169
|
-
// `_attributes` hash to indicate it is dirty
|
|
170
|
-
record._internalModel._recordData.setDirtyAttribute(key, fragment);
|
|
171
174
|
record.send('becomeDirty');
|
|
172
175
|
}
|
|
173
176
|
}
|
|
@@ -175,7 +178,7 @@ export function fragmentDidDirty(record, key, fragment) {
|
|
|
175
178
|
export function fragmentDidReset(record, key) {
|
|
176
179
|
// Make sure there's no entry in the owner record's
|
|
177
180
|
// `_attributes` hash to indicate the fragment is dirty
|
|
178
|
-
delete record._internalModel.
|
|
181
|
+
// delete record._internalModel._attributes[key];
|
|
179
182
|
|
|
180
183
|
// Don't reset if the record is new, otherwise it will enter the 'deleted' state
|
|
181
184
|
// NOTE: This case almost never happens with attributes because their initial value
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { assert } from '@ember/debug';
|
|
2
2
|
import { getOwner } from '@ember/application';
|
|
3
3
|
import { makeArray } from '@ember/array';
|
|
4
|
-
import {
|
|
5
|
-
import Transform from 'ember-data/transform';
|
|
4
|
+
import { computed } from '@ember/object';
|
|
5
|
+
import Transform from '@ember-data/serializer/transform';
|
|
6
|
+
import { inject as service } from '@ember/service';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
@module ember-data-model-fragments
|
|
@@ -17,7 +18,7 @@ import Transform from 'ember-data/transform';
|
|
|
17
18
|
@extends DS.Transform
|
|
18
19
|
*/
|
|
19
20
|
const ArrayTransform = Transform.extend({
|
|
20
|
-
store:
|
|
21
|
+
store: service(),
|
|
21
22
|
type: null,
|
|
22
23
|
|
|
23
24
|
deserialize: function deserializeArray(data) {
|
|
@@ -25,7 +26,7 @@ const ArrayTransform = Transform.extend({
|
|
|
25
26
|
return null;
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
let transform =
|
|
29
|
+
let transform = this.transform;
|
|
29
30
|
|
|
30
31
|
data = makeArray(data);
|
|
31
32
|
|
|
@@ -41,7 +42,7 @@ const ArrayTransform = Transform.extend({
|
|
|
41
42
|
return null;
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
let transform =
|
|
45
|
+
let transform = this.transform;
|
|
45
46
|
|
|
46
47
|
array = array.toArray ? array.toArray() : array;
|
|
47
48
|
|
|
@@ -53,7 +54,7 @@ const ArrayTransform = Transform.extend({
|
|
|
53
54
|
},
|
|
54
55
|
|
|
55
56
|
transform: computed('type', function() {
|
|
56
|
-
let attributeType = this.
|
|
57
|
+
let attributeType = this.type;
|
|
57
58
|
|
|
58
59
|
if (!attributeType) {
|
|
59
60
|
return null;
|