Skip to content

Commit 92cf580

Browse files
author
Lee Richmond
committed
Only save id/type of clean records
Previously we were not sending clean records down to the server, which means ember-data would forget about then after save(). Now, we do send clean records down to the server, but only their resource identifiers (avoiding concurrency issues with attributes).
1 parent a693ac0 commit 92cf580

File tree

7 files changed

+127
-51
lines changed

7 files changed

+127
-51
lines changed

addon/mixins/model.js

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import Ember from 'ember';
22

3-
const resetRelations = function(record) {
3+
const resetRelations = function(record, relationshipsDirective) {
44
record.eachRelationship((relationName, meta) => {
55
if (meta.kind === 'hasMany') {
6-
record.set(relationName, Ember.A());
7-
} else {
8-
record.set(relationName, null);
6+
if (relationshipsDirective.hasOwnProperty(relationName)) {
7+
let filtered = record.get(relationName).filter((r) => {
8+
return r.get('isNew') ||
9+
r.get('markedForDestruction') ||
10+
r.get('markedForDeletion');
11+
});
12+
record.get(relationName).removeObjects(filtered);
13+
}
914
}
1015
});
1116
};
@@ -45,9 +50,10 @@ export default Ember.Mixin.create({
4550
defaultOptions(options);
4651
let promise = this._super(...arguments);
4752

48-
if (options.resetRelations) {
49-
promise.then(resetRelations);
50-
}
53+
let directive = options.adapterOptions.relationships;
54+
promise.then((record) => {
55+
resetRelations(record, directive);
56+
});
5157

5258
return promise;
5359
}

addon/mixins/nested-relations.js

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -68,21 +68,17 @@ const jsonapiPayload = function(record) {
6868
const hasManyData = function(relatedRecords, subRelations) {
6969
let payloads = [];
7070
relatedRecords.forEach((relatedRecord) => {
71-
if (relatedRecord.get('hasDirtyAttributes')) {
72-
let payload = jsonapiPayload(relatedRecord);
73-
processRelationships(subRelations, payload, relatedRecord);
74-
payloads.push(payload);
75-
}
71+
let payload = jsonapiPayload(relatedRecord);
72+
processRelationships(subRelations, payload, relatedRecord);
73+
payloads.push(payload);
7674
});
7775
return { data: payloads };
7876
};
7977

8078
const belongsToData = function(relatedRecord, subRelations) {
81-
if (isPresentObject(subRelations) || relatedRecord.get('hasDirtyAttributes')) {
82-
let payload = jsonapiPayload(relatedRecord);
83-
processRelationships(subRelations, payload, relatedRecord);
84-
return { data: payload };
85-
}
79+
let payload = jsonapiPayload(relatedRecord);
80+
processRelationships(subRelations, payload, relatedRecord);
81+
return { data: payload };
8682
};
8783

8884
const processRelationship = function(kind, relationData, subRelations, callback) {

tests/acceptance/delete-nested-object-test.js

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,31 @@ moduleForAcceptance('Acceptance | delete nested objects');
77

88
test('deleting a nested object', function(assert) {
99
page.visit();
10-
page.addTag().tags(0).setName('new tag 1');
10+
page.addTag().tags(0).setName('a');
1111
page.submit();
1212

1313
andThen(function() {
14-
assert.equal(detailPage.tagList, 'new tag 1');
14+
assert.equal(detailPage.tagList, 'a');
1515
detailPage.edit();
1616

1717
andThen(function() {
18-
page.tags(0).remove();
18+
page.addTag().tags(1).setName('b');
19+
page.submit();
1920

2021
andThen(function() {
21-
assert.equal(page.tags().count, 0, 'should not show removed tag');
22-
page.submit();
22+
detailPage.edit();
2323

2424
andThen(function() {
25-
assert.equal(detailPage.tagList, '', 'should delete removed tag');
25+
page.tags(0).remove();
26+
27+
andThen(function() {
28+
assert.equal(page.tags().count, 1, 'should not show removed tag');
29+
page.submit();
30+
31+
andThen(function() {
32+
assert.equal(detailPage.tagList, 'b', 'should delete removed tag');
33+
});
34+
});
2635
});
2736
});
2837
});

tests/acceptance/update-nested-relations-test.js

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,6 @@ import detailPage from 'dummy/tests/pages/show-post';
55

66
moduleForAcceptance('Acceptance | update nested relations');
77

8-
// Todo test ideally
9-
// * delete
10-
// * disassociate
118
test('updating nested relations', function(assert) {
129
let author = server.create('author', { name: 'Joe Author' });
1310
let tag1 = server.create('tag', { name: 'tag1' });
@@ -40,3 +37,23 @@ test('updating nested relations', function(assert) {
4037
});
4138
});
4239
});
40+
41+
test('updating only one member of a hasMany relation', function(assert) {
42+
server.foo = 'bar';
43+
let tag1 = server.create('tag', { name: 'tag1' });
44+
let tag2 = server.create('tag', { name: 'tag2' });
45+
let post = server.create('post', {
46+
tags: [tag1, tag2],
47+
});
48+
visit(`/posts/${post.id}/edit`);
49+
50+
andThen(function() {
51+
assert.equal(page.tags().count, 2);
52+
page.tags(0).setName('tag1 changed');
53+
page.submit();
54+
55+
andThen(function() {
56+
assert.equal(detailPage.tagList, 'tag1 changed, tag2');
57+
});
58+
});
59+
});

tests/dummy/app/post/route.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@ import Ember from 'ember';
22

33
export default Ember.Route.extend({
44
model(params) {
5-
return this.store.findRecord('post', params.postId, {
6-
reload: true,
7-
include: 'author,tags'
8-
});
5+
return this.store.peekRecord('post', params.postId);
96
}
107
});

tests/unit/mixins/model-test.js

Lines changed: 62 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,13 @@ const Author = DS.Model.extend(ModelMixin, {
1212
});
1313

1414
const Post = DS.Model.extend(ModelMixin, {
15-
title: DS.attr('string'),
16-
author: DS.belongsTo({ async: false })
15+
title: DS.attr('string'),
16+
author: DS.belongsTo({ async: false }),
17+
tags: DS.hasMany({ async: true })
18+
});
19+
20+
const Tag = DS.Model.extend(ModelMixin, {
21+
name: DS.attr('string')
1722
});
1823

1924
moduleForAcceptance('Unit | Mixin | model', {
@@ -22,6 +27,7 @@ moduleForAcceptance('Unit | Mixin | model', {
2227
store = getOwner(this).lookup('service:store');
2328
getOwner(this).register('model:post', Post);
2429
getOwner(this).register('model:author', Author);
30+
getOwner(this).register('model:tag', Tag);
2531
}
2632
});
2733

@@ -54,29 +60,65 @@ test('#hasDirtyAttributes', function(assert) {
5460
});
5561
});
5662

57-
test('#save when resetRelations: false', function(assert) {
58-
Ember.run(() => {
59-
let author = store.createRecord('author', { name: 'Joe Author' });
60-
let post = store.createRecord('post', { title: 'title', author: author });
61-
62-
let done = assert.async();
63-
post.save({ resetRelations: false }).then((p) => {
64-
assert.equal(p.get('author'), author);
65-
done();
63+
// note tag with id 3 does not send attributes
64+
test('resetting relations when only sending dirty relations', function(assert) {
65+
let done = assert.async();
66+
server.patch('/posts/:id', (db, request) => {
67+
let relationships = JSON.parse(request.requestBody).data.relationships;
68+
assert.deepEqual(relationships, {
69+
tags: {
70+
data: [
71+
{
72+
id: '2',
73+
type: 'tags',
74+
attributes: { name: 'tag1 changed' }
75+
},
76+
{
77+
id: '3',
78+
type: 'tags'
79+
}
80+
]
81+
}
6682
});
83+
done();
84+
let post = db.posts.find(request.params.id);
85+
post.tags.models[0].update({ name: 'tag1 changed' });
86+
return post;
6787
});
68-
});
6988

70-
test('#save when resetRelations: true', function(assert) {
89+
let post = server.create('post');
90+
post.createTag({ name: 'tag1' });
91+
post.createTag({ name: 'tag2' });
7192
Ember.run(() => {
72-
let author = store.createRecord('author', { name: 'Joe Author' });
73-
let post = store.createRecord('post', { title: 'title', author: author });
93+
store.pushPayload({
94+
data: {
95+
type: 'posts',
96+
id: 1,
97+
relationships: {
98+
tags: {
99+
data: [
100+
{ type: 'tags', id: '2' },
101+
{ type: 'tags', id: '3' }
102+
]
103+
}
104+
}
105+
},
106+
included: [
107+
{ type: 'tags', id: '2', attributes: { name: 'tag1' } },
108+
{ type: 'tags', id: '3', attributes: { name: 'tag2' } }
109+
]
110+
});
111+
});
74112

75-
let done = assert.async();
76-
post.save().then((p) => {
77-
let author = p.get('author');
78-
assert.equal(author, null);
79-
done();
113+
post = store.peekRecord('post', 1);
114+
assert.equal(post.get('tags.length'), 2);
115+
post.set('tags.firstObject.name', 'tag1 changed');
116+
117+
let done2 = assert.async();
118+
Ember.run(() => {
119+
post.save({ adapterOptions: { relationships: 'tags' } }).then((p) => {
120+
assert.equal(p.get('tags.length'), 2);
121+
done2();
80122
});
81123
});
82124
});

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,7 @@ test('it serializes one-to-many correctly', function(assert) {
293293
assert.deepEqual(json, expectedJSON, 'has correct json');
294294
});
295295

296+
// note tag 2 does not pass attributes
296297
test('one-to-many deletion/destruction', function(assert) {
297298
seedPostWithTags();
298299
let post = store.peekRecord('post', 1);
@@ -306,6 +307,7 @@ test('one-to-many deletion/destruction', function(assert) {
306307
relationships: {
307308
tags: {
308309
data: [
310+
{ type: 'tags', id: '2' },
309311
{ type: 'tags', id: '3', attributes: { _delete: true } },
310312
{ type: 'tags', id: '4', attributes: { _destroy: true } },
311313
]
@@ -328,7 +330,7 @@ test('relationship specified but not present', function(assert) {
328330
assert.deepEqual(json, expectedJSON, 'it does not blow up');
329331
});
330332

331-
test('does not serialize non-dirty relations', function(assert) {
333+
test('does not serialize attributes of non-dirty relations', function(assert) {
332334
store.pushPayload({
333335
data: {
334336
id: '1',
@@ -376,6 +378,12 @@ test('does not serialize non-dirty relations', function(assert) {
376378
id: '1',
377379
type: 'posts',
378380
relationships: {
381+
author: {
382+
data: {
383+
id: '99',
384+
type: 'authors'
385+
}
386+
},
379387
genre: {
380388
data: {
381389
type: 'genres',
@@ -386,6 +394,7 @@ test('does not serialize non-dirty relations', function(assert) {
386394
tags: {
387395
data: [
388396
{ type: 'tags', id: '2', attributes: { name: 'tag1 change' } },
397+
{ type: 'tags', id: '3' },
389398
{ type: 'tags', attributes: { name: 'new tag' } }
390399
]
391400
}

0 commit comments

Comments
 (0)