Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit c95b691

Browse files
jnaglickleondmello
authored andcommittedOct 24, 2018
Updates for new json suite changes + fixes (#12)
* Updates for new json suite changes * serialize relationships in resource objects ('included'), not resource identifier objects ('relationships') * restore markManyToManyDeletion functionality * reuse variable * Ember.guidFor instead of uuid() * hide side effects better
1 parent f363496 commit c95b691

File tree

9 files changed

+278
-105
lines changed

9 files changed

+278
-105
lines changed
 

‎.eslintrc.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
module.exports = {
2+
globals: {
3+
server: true,
4+
},
5+
root: true,
6+
parserOptions: {
7+
ecmaVersion: 2017,
8+
sourceType: 'module'
9+
},
10+
plugins: [
11+
'ember'
12+
],
13+
extends: [
14+
'eslint:recommended',
15+
'plugin:ember/recommended'
16+
],
17+
env: {
18+
browser: true
19+
},
20+
rules: {
21+
},
22+
overrides: [
23+
// node files
24+
{
25+
files: [
26+
'ember-cli-build.js',
27+
'testem.js',
28+
'blueprints/*/index.js',
29+
'config/**/*.js',
30+
'lib/*/index.js'
31+
],
32+
parserOptions: {
33+
sourceType: 'script',
34+
ecmaVersion: 2015
35+
},
36+
env: {
37+
browser: false,
38+
node: true
39+
}
40+
}
41+
]
42+
};

‎addon/mixins/model.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ export default Ember.Mixin.create({
7575

7676
manyToManyMarkedForDeletionModels(relation) {
7777
const relationModels = this.get('_manyToManyDeleted') &&
78-
this.get(`_manyToManyDeleted.${relation}`);
79-
return relationModels && relationModels.toArray();
78+
this.get(`_manyToManyDeleted.${relation}`);
79+
return relationModels && relationModels.toArray() || [];
8080
},
8181

8282
unmarkManyToManyDeletion(relation, model) {
@@ -85,6 +85,12 @@ export default Ember.Mixin.create({
8585
this.get(`_manyToManyDeleted.${relation}`).removeObject(model);
8686
},
8787

88+
tempId() {
89+
if (!this._tempId) {
90+
this._tempId = Ember.guidFor(this);
91+
}
92+
return this._tempId;
93+
},
8894

8995
jsonapiType() {
9096
return this.store

‎addon/mixins/nested-relations.js

Lines changed: 71 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ const iterateRelations = function(record, relations, callback) {
1616
let relatedRecord = record.get(relationName);
1717
let manyToManyDeleted = record.manyToManyMarkedForDeletionModels(relationName);
1818

19-
2019
if (metadata.options.async !== false) {
2120
relatedRecord = relatedRecord.get('content');
2221
}
@@ -31,11 +30,10 @@ const isPresentObject = function(val) {
3130
return val && Object.keys(val).length > 0;
3231
};
3332

34-
const attributesFor = function(record, isManyToManyDelete) {
33+
const attributesFor = function(record) {
3534
let attrs = {};
3635

3736
let changes = record.changedAttributes();
38-
3937
let serializer = record.store.serializerFor(record.constructor.modelName);
4038

4139
record.eachAttribute((name/* meta */) => {
@@ -50,71 +48,115 @@ const attributesFor = function(record, isManyToManyDelete) {
5048
}
5149
});
5250

53-
if (record.get('markedForDeletion') || isManyToManyDelete) {
54-
attrs = { _delete: true };
55-
}
56-
57-
if (!record.get('isNew') && record.get('markedForDestruction')) {
58-
attrs = { _destroy: true };
59-
}
60-
6151
return attrs;
6252
};
6353

6454
const jsonapiPayload = function(record, isManyToManyDelete) {
65-
let attributes = attributesFor(record, isManyToManyDelete);
55+
let attributes = attributesFor(record);
6656

6757
let payload = { type: record.jsonapiType() };
6858

6959
if (isPresentObject(attributes)) {
7060
payload.attributes = attributes;
7161
}
7262

63+
if (record.get('isNew')) {
64+
payload['temp-id'] = record.tempId();
65+
payload['method'] = 'create';
66+
}
67+
else if (record.get('markedForDestruction')) {
68+
payload['method'] = 'destroy';
69+
}
70+
else if (record.get('markedForDeletion') || isManyToManyDelete) {
71+
payload['method'] = 'disassociate';
72+
}
73+
else if (record.get('currentState.isDirty')) {
74+
payload['method'] = 'update';
75+
}
76+
7377
if (record.id) {
7478
payload.id = record.id;
7579
}
7680

7781
return payload;
7882
};
7983

80-
const hasManyData = function(relationName, relatedRecords, subRelations, manyToManyDeleted) {
84+
const payloadForInclude = function(payload) {
85+
let payloadCopy = Ember.copy(payload, true);
86+
delete(payloadCopy.method);
87+
88+
return payloadCopy;
89+
};
90+
91+
const payloadForRelationship = function(payload) {
92+
let payloadCopy = Ember.copy(payload, true);
93+
delete(payloadCopy.attributes);
94+
delete(payloadCopy.relationships);
95+
96+
return payloadCopy;
97+
};
98+
99+
const addToIncludes = function(payload, includedRecords) {
100+
let includedPayload = payloadForInclude(payload);
101+
102+
if (!includedPayload.attributes && !isPresentObject(includedPayload.relationships)) {
103+
return;
104+
}
105+
106+
const alreadyIncluded = includedRecords.find((includedRecord) =>
107+
includedPayload['type'] === includedRecord['type'] &&
108+
((includedPayload['temp-id'] && includedPayload['temp-id'] === includedRecord['temp-id']) ||
109+
(includedPayload['id'] && includedPayload['id'] === includedRecord['id']))
110+
) !== undefined;
111+
112+
if (!alreadyIncluded) {
113+
includedRecords.push(includedPayload);
114+
}
115+
};
116+
117+
const hasManyData = function(relationName, relatedRecords, subRelations, manyToManyDeleted, includedRecords) {
81118
let payloads = [];
82119
savedRecords[relationName] = [];
120+
83121
relatedRecords.forEach((relatedRecord) => {
84122
let payload = jsonapiPayload(relatedRecord, manyToManyDeleted && manyToManyDeleted.includes(relatedRecord));
85-
processRelationships(subRelations, payload, relatedRecord);
86-
payloads.push(payload);
123+
processRelationships(subRelations, payload, relatedRecord, includedRecords);
124+
addToIncludes(payload, includedRecords);
125+
126+
payloads.push(payloadForRelationship(payload));
87127
savedRecords[relationName].push(relatedRecord);
88128
});
89129
return { data: payloads };
90130
};
91131

92-
const belongsToData = function(relatedRecord, subRelations) {
132+
const belongsToData = function(relatedRecord, subRelations, includedRecords) {
93133
let payload = jsonapiPayload(relatedRecord);
94-
processRelationships(subRelations, payload, relatedRecord);
95-
return { data: payload };
134+
processRelationships(subRelations, payload, relatedRecord, includedRecords);
135+
addToIncludes(payload, includedRecords);
136+
137+
return { data: payloadForRelationship(payload) };
96138
};
97139

98-
const processRelationship = function(name, kind, relationData, subRelations, manyToManyDeleted, callback) {
140+
const processRelationship = function(name, kind, relationData, subRelations, manyToManyDeleted, includedRecords, callback) {
99141
let payload = null;
100142

101143
if (kind === 'hasMany') {
102-
payload = hasManyData(name, relationData, subRelations, manyToManyDeleted);
144+
payload = hasManyData(name, relationData, subRelations, manyToManyDeleted, includedRecords);
103145
} else {
104-
payload = belongsToData(relationData, subRelations);
146+
payload = belongsToData(relationData, subRelations, includedRecords);
105147
}
106148

107149
if (payload && payload.data) {
108150
callback(payload);
109151
}
110152
};
111153

112-
const processRelationships = function(relationshipHash, jsonData, record) {
154+
const processRelationships = function(relationshipHash, jsonData, record, includedRecords) {
113155
if (isPresentObject(relationshipHash)) {
114156
jsonData.relationships = {};
115157

116158
iterateRelations(record, relationshipHash, (name, kind, related, subRelations, manyToManyDeleted) => {
117-
processRelationship(name, kind, related, subRelations, manyToManyDeleted, (payload) => {
159+
processRelationship(name, kind, related, subRelations, manyToManyDeleted, includedRecords, (payload) => {
118160
let serializer = record.store.serializerFor(record.constructor.modelName);
119161
let serializedName = serializer.keyForRelationship(name);
120162
jsonData.relationships[serializedName] = payload;
@@ -148,7 +190,9 @@ const relationshipsDirective = function(value) {
148190
export default Ember.Mixin.create({
149191
serialize(snapshot/*, options */) {
150192
savedRecords = [];
193+
151194
let json = this._super(...arguments);
195+
let includedRecords = [];
152196

153197
if (snapshot.record.get('emberDataExtensions') !== false) {
154198
delete(json.data.relationships);
@@ -180,7 +224,10 @@ export default Ember.Mixin.create({
180224
}
181225

182226
let relationships = relationshipsDirective(adapterOptions.relationships);
183-
processRelationships(relationships, json.data, snapshot.record);
227+
processRelationships(relationships, json.data, snapshot.record, includedRecords);
228+
if (includedRecords && includedRecords.length > 0) {
229+
json.included = includedRecords;
230+
}
184231
snapshot.record.set('__recordsJustSaved', savedRecords);
185232
}
186233

‎index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,8 @@
22
'use strict';
33

44
module.exports = {
5-
name: 'ember-data-extensions'
5+
name: 'ember-data-extensions',
6+
isDevelopingAddon() {
7+
return true;
8+
}
69
};

‎testem.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@ module.exports = {
44
"test_page": "tests/index.html?hidepassed",
55
"disable_watching": true,
66
"launch_in_ci": [
7-
"PhantomJS"
7+
"Chrome"
88
],
99
"launch_in_dev": [
10-
"PhantomJS",
1110
"Chrome"
1211
]
1312
};
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
import Ember from 'ember';
22

33
export default Ember.Controller.extend({
4-
tags: Ember.computed.filterBy(
5-
'model.tags',
6-
'markedForDeletion',
7-
false
8-
)
4+
tags: Ember.computed.filterBy('model.tags', 'markedForDeletion', false)
95
});

‎tests/dummy/mirage/config.js

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,29 @@ const iterateRelations = function(request, callback) {
1414

1515
// Omit anything that has all blank attributes
1616
// Akin to rails' accepts_nested_attributes_for :foos, reject_if: :all_blank
17-
const recordFromJson = function(db, data, callback) {
18-
let attributes = data.attributes || {};
17+
const recordFromJson = function(db, data, includedData, callback) {
18+
19+
let found;
20+
21+
if (data['temp-id']) {
22+
found = includedData.filter(item => (item['temp-id'] === data['temp-id']))[0];
23+
}
24+
else {
25+
found = includedData.filter(item => (item.id === data.id))[0];
26+
}
27+
28+
let attributes = found ? found.attributes : {};
1929

2030
if (data.id) {
2131
let record = db[data.type].find(data.id);
22-
record.update(attributes);
32+
33+
if (data['method'] === 'update') {
34+
record.update(attributes);
35+
}
2336
callback(record);
2437
return;
2538
}
26-
39+
2740
let notNull = false;
2841
Object.keys(attributes).forEach((key) => {
2942
if (Ember.isPresent(attributes[key])) {
@@ -53,17 +66,16 @@ const hasRecord = function(array, record) {
5366
}
5467
};
5568

56-
const markedForRemoval= function(record) {
57-
return record._delete || record._destroy;
58-
};
59-
60-
const buildOneToMany = function(db, relationData, originalRecords) {
69+
const buildOneToMany = function(db, relationData, includedRecords, originalRecords) {
6170
relationData.forEach((data) => {
62-
recordFromJson(db, data, (record) => {
63-
if (markedForRemoval(record)) {
71+
let method = data.method;
72+
73+
recordFromJson(db, data, includedRecords, (record) => {
74+
if (method === 'disassociate' || method === 'destroy') {
6475
let index = originalRecords.indexOf(record);
6576
originalRecords.splice(index, 1);
66-
} else {
77+
}
78+
else {
6779
if (!hasRecord(originalRecords, record)) {
6880
originalRecords.push(record);
6981
}
@@ -74,13 +86,18 @@ const buildOneToMany = function(db, relationData, originalRecords) {
7486
};
7587

7688
const processRelations = function(record, db, request) {
89+
let includedRecords = JSON.parse(request.requestBody).included || [];
90+
7791
iterateRelations(request, (relationName, relationData) => {
7892
if (Array.isArray(relationData)) {
7993
let originals = record[relationName].models;
80-
record[relationName] = buildOneToMany(db, relationData, originals);
94+
record[relationName] = buildOneToMany(db, relationData, includedRecords, originals);
8195
} else {
82-
recordFromJson(db, relationData, (relationRecord) => {
96+
recordFromJson(db, relationData, includedRecords, (relationRecord, remove) => {
8397
record[relationName] = relationRecord;
98+
if (remove) {
99+
delete record[relationName];
100+
}
84101
});
85102
}
86103
});

‎tests/unit/mixins/model-test.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ test('resetting relations when only sending dirty relations', function(assert) {
7070
data: [
7171
{
7272
id: '2',
73+
method: 'update',
7374
type: 'tags',
74-
attributes: { name: 'tag1 changed' }
7575
},
7676
{
7777
id: '3',
@@ -80,6 +80,17 @@ test('resetting relations when only sending dirty relations', function(assert) {
8080
]
8181
}
8282
});
83+
84+
let included = JSON.parse(request.requestBody).included;
85+
assert.deepEqual(included, [
86+
{
87+
id: '2',
88+
type: 'tags',
89+
attributes: { name: 'tag1 changed' }
90+
}
91+
]);
92+
93+
8394
done();
8495
let post = db.posts.find(request.params.id);
8596
post.tags.models[0].update({ name: 'tag1 changed' });

‎tests/unit/mixins/nested-relations-test.js

Lines changed: 108 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ test('it does not serialize non-dirty attributes', function(assert) {
186186
}
187187
});
188188
let post = store.peekRecord('post', 1);
189+
189190
let json = serialize(post, {});
190191
let expectedJSON = {
191192
data: {
@@ -205,8 +206,9 @@ test('excluding attributes', function(assert) {
205206
});
206207

207208
test('it serializes one-to-one correctly', function(assert) {
209+
let author = store.createRecord('author', { name: 'Joe Author' });
208210
let post = store.createRecord('post', {
209-
author: store.createRecord('author', { name: 'Joe Author' })
211+
author: author
210212
});
211213
let json = serialize(post, { attributes: false, relationships: 'author' });
212214
let expectedJSON = {
@@ -216,20 +218,25 @@ test('it serializes one-to-one correctly', function(assert) {
216218
author: {
217219
data: {
218220
type: 'authors',
219-
attributes: {
220-
name: 'Joe Author'
221-
}
221+
'temp-id': author.tempId(),
222+
method: 'create'
222223
}
223224
}
224225
}
225-
}
226+
},
227+
included: [
228+
{type: 'authors', 'temp-id': author.tempId(), attributes: { name: 'Joe Author' }}
229+
]
226230
};
227231
assert.deepEqual(json, expectedJSON, 'has correct json');
228232
});
229233

230234
test('it serializes async: false relationships correctly', function(assert) {
235+
236+
// NOTE: Not sure if I'm doing this right with the async thing
237+
let author = store.createRecord('author', { name: 'Joe Author' });
231238
let post = store.createRecord('post', {
232-
asyncFalseAuthor: store.createRecord('author', { name: 'Joe Author' })
239+
asyncFalseAuthor: author
233240
});
234241
let json = serialize(post, { attributes: false, relationships: 'asyncFalseAuthor' });
235242
let expectedJSON = {
@@ -239,13 +246,15 @@ test('it serializes async: false relationships correctly', function(assert) {
239246
'async-false-author': {
240247
data: {
241248
type: 'authors',
242-
attributes: {
243-
name: 'Joe Author'
244-
}
249+
method: 'create',
250+
'temp-id': author.tempId()
245251
}
246252
}
247253
}
248-
}
254+
},
255+
included: [
256+
{ type: 'authors', 'temp-id': author.tempId(), attributes: { name: 'Joe Author' }}
257+
]
249258
};
250259
assert.deepEqual(json, expectedJSON, 'has correct json');
251260
});
@@ -266,9 +275,7 @@ test('it serializes has one marked for deletion correctly', function(assert) {
266275
data: {
267276
id: '2',
268277
type: 'authors',
269-
attributes: {
270-
_delete: true
271-
}
278+
method: 'disassociate'
272279
}
273280
}
274281
}
@@ -294,9 +301,7 @@ test('it serializes has one marked for destruction correctly', function(assert)
294301
data: {
295302
id: '2',
296303
type: 'authors',
297-
attributes: {
298-
_destroy: true
299-
}
304+
method: 'destroy'
300305
}
301306
}
302307
}
@@ -307,9 +312,10 @@ test('it serializes has one marked for destruction correctly', function(assert)
307312
});
308313

309314
test('it serializes one-to-many correctly', function(assert) {
315+
let tag = store.createRecord('tag', { name: 'tag1' });
310316
let post = store.createRecord('post', {
311317
tags: [
312-
store.createRecord('tag', { name: 'tag1' })
318+
tag
313319
]
314320
});
315321

@@ -323,14 +329,16 @@ test('it serializes one-to-many correctly', function(assert) {
323329
data: [
324330
{
325331
type: 'tags',
326-
attributes: {
327-
name: 'tag1'
328-
}
332+
'temp-id': tag.tempId(),
333+
method: 'create'
329334
}
330335
]
331336
}
332337
}
333-
}
338+
},
339+
included: [
340+
{ type: 'tags', 'temp-id': tag.tempId(), attributes: { name: 'tag1' } }
341+
]
334342
};
335343
assert.deepEqual(json, expectedJSON, 'has correct json');
336344
});
@@ -350,8 +358,8 @@ test('one-to-many deletion/destruction', function(assert) {
350358
tags: {
351359
data: [
352360
{ type: 'tags', id: '2' },
353-
{ type: 'tags', id: '3', attributes: { _delete: true } },
354-
{ type: 'tags', id: '4', attributes: { _destroy: true } },
361+
{ type: 'tags', id: '3', method: 'disassociate' },
362+
{ type: 'tags', id: '4', method: 'destroy' }
355363
]
356364
}
357365
}
@@ -430,18 +438,23 @@ test('does not serialize attributes of non-dirty relations', function(assert) {
430438
data: {
431439
type: 'genres',
432440
id: '88',
433-
attributes: { name: 'drama' }
441+
method: 'update'
434442
}
435443
},
436444
tags: {
437445
data: [
438-
{ type: 'tags', id: '2', attributes: { name: 'tag1 change' } },
446+
{ type: 'tags', id: '2', method: 'update' },
439447
{ type: 'tags', id: '3' },
440-
{ type: 'tags', attributes: { name: 'new tag' } }
448+
{ type: 'tags', 'temp-id': newTag.tempId(), method: 'create' }
441449
]
442450
}
443451
}
444-
}
452+
},
453+
included: [
454+
{type: 'tags', id:'2', attributes: { name: 'tag1 change' }},
455+
{type: 'tags', 'temp-id': newTag.tempId(), attributes: { name: 'new tag' }},
456+
{type: 'genres', id: '88', attributes: { name: 'drama' }}
457+
]
445458
};
446459
assert.deepEqual(json, expectedJSON);
447460
});
@@ -472,19 +485,33 @@ test('nested one-to-one', function(assert) {
472485
author: {
473486
data: {
474487
type: 'authors',
475-
attributes: { name: 'Joe Author' },
476-
relationships: {
477-
state: {
478-
data: {
479-
type: 'states',
480-
attributes: { name: 'New York'}
481-
}
482-
}
488+
method: 'create',
489+
'temp-id': author.tempId(),
490+
}
491+
}
492+
}
493+
},
494+
included: [
495+
{
496+
type: 'states',
497+
'temp-id': state.tempId(),
498+
attributes: { name: 'New York' }
499+
},
500+
{
501+
type: 'authors',
502+
'temp-id': author.tempId(),
503+
attributes: { name: 'Joe Author' },
504+
relationships: {
505+
state: {
506+
data: {
507+
type: 'states',
508+
'temp-id': state.tempId(),
509+
method: 'create',
483510
}
484511
}
485512
}
486513
}
487-
}
514+
]
488515
};
489516
assert.deepEqual(json, expectedJSON);
490517
});
@@ -516,20 +543,34 @@ test('nested one-to-many', function(assert) {
516543
data: [
517544
{
518545
type: 'tags',
519-
attributes: { name: 'tag1' },
520-
relationships: {
521-
creator: {
522-
data: {
523-
type: 'users',
524-
attributes: { name: 'Joe User' }
525-
}
526-
}
527-
}
546+
'temp-id': tag.tempId(),
547+
method: 'create'
528548
}
529549
]
530550
}
531551
}
532-
}
552+
},
553+
included: [
554+
{
555+
type: 'users',
556+
'temp-id': user.tempId(),
557+
attributes: { name: 'Joe User' }
558+
},
559+
{
560+
type: 'tags',
561+
'temp-id': tag.tempId(),
562+
attributes: { name: 'tag1' },
563+
relationships: {
564+
creator: {
565+
data: {
566+
type: 'users',
567+
'temp-id': user.tempId(),
568+
method: 'create'
569+
}
570+
}
571+
}
572+
}
573+
]
533574
};
534575
assert.deepEqual(json, expectedJSON);
535576
});
@@ -565,25 +606,36 @@ test('array with nesting', function(assert) {
565606
relationships: {
566607
tags: {
567608
data: [
568-
{ type: 'tags', attributes: { name: 'tag1' } }
609+
{ type: 'tags', 'temp-id': tag.tempId(), method: 'create' }
569610
]
570611
},
571612
author: {
572613
data: {
573614
type: 'authors',
574-
attributes: { name: 'Joe Author' },
575-
relationships: {
576-
state: {
577-
data: {
578-
type: 'states',
579-
attributes: { name: 'New York' }
580-
}
581-
}
615+
'temp-id': author.tempId(),
616+
method: 'create'
617+
}
618+
}
619+
}
620+
},
621+
included: [
622+
{ type: 'tags', 'temp-id': tag.tempId(), attributes: { name: 'tag1' } },
623+
{ type: 'states', 'temp-id': state.tempId(), attributes: { name: 'New York' } },
624+
{
625+
type: 'authors',
626+
'temp-id': author.tempId(),
627+
attributes: { name: 'Joe Author' },
628+
relationships: {
629+
state: {
630+
data: {
631+
method: 'create',
632+
type: 'states',
633+
'temp-id': state.tempId(),
582634
}
583635
}
584636
}
585637
}
586-
}
638+
]
587639
};
588640
assert.deepEqual(json, expectedJSON);
589641
});

0 commit comments

Comments
 (0)
Please sign in to comment.