Skip to content

Commit c95b691

Browse files
jnaglickleondmello
authored andcommitted
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' });

0 commit comments

Comments
 (0)