From 4b757b7492f36185b039f155161a42979a4e2d39 Mon Sep 17 00:00:00 2001 From: Nikolay Safin Date: Fri, 16 Dec 2016 09:26:08 +0000 Subject: [PATCH] updateRelationship replaces relations - Add new private method on model to replace relations - Update tests for updateRelationship behaviors --- addon/mixins/resource-operations.js | 7 ++- addon/models/resource.js | 60 +++++++++++++++---- tests/unit/mixins/resource-operations-test.js | 10 ++-- tests/unit/models/resource-test.js | 19 +++++- 4 files changed, 77 insertions(+), 19 deletions(-) diff --git a/addon/mixins/resource-operations.js b/addon/mixins/resource-operations.js index 9df5251..ca3b652 100644 --- a/addon/mixins/resource-operations.js +++ b/addon/mixins/resource-operations.js @@ -96,7 +96,7 @@ export default Ember.Mixin.create({ /** Update a relationship, works with both `to-many` and `to-one`. Primarily use - with `to-one` as `to-many` is for a full replacement only. + with `to-one` and `to-many` is for a full replacement only. For a `to-one` relationship, add, replace or remove, and persist the change using the service. With an id the relation will be added or changed, with @@ -105,7 +105,8 @@ export default Ember.Mixin.create({ See: For `to-many` relationships the backend will need to support editing as a set, - full replacement (most often that may be disabled). + full replacement (most often that may be disabled). The list of all related + resource identifiers will be sent to the server as a replace operation. Update a relationship by adding or removing using a list, id, or null. When adding an id for a to-many relationship send one or more ids, include the @@ -132,7 +133,7 @@ export default Ember.Mixin.create({ } else if (related.kind === 'toMany') { rollback = related.mapBy('id'); } - this._updateRelationshipsData(relationship, ids); + this._replaceRelationshipsData(relationship, ids); return this.get('service').patchRelationship(this, relationship) .catch(function (error) { this._updateRelationshipsData(relationship, rollback); diff --git a/addon/models/resource.js b/addon/models/resource.js index 4d51b07..2bd61a1 100644 --- a/addon/models/resource.js +++ b/addon/models/resource.js @@ -138,18 +138,10 @@ const Resource = Ember.Object.extend(ResourceOperationsMixin, { @param {Array|String|null} ids */ _updateRelationshipsData(relation, ids) { - let relationshipData = 'relationships.' + relation + '.data'; - let existing; if (!Array.isArray(ids)) { - existing = this.get(relationshipData).id; - if (ids === null || isType('string', ids) && existing !== ids) { - this.removeRelationship(relation, existing); - if (ids !== null) { - this.addRelationship(relation, ids); - } - } + this._updateToOneRelationshipData(relation, ids); } else { - existing = this.get(relationshipData).map(function(rel) { return rel.id; }); + let existing = this._existingRelationshipData(relation); if (!existing.length) { this.addRelationships(relation, ids); } else if (ids.length > existing.length) { @@ -160,6 +152,54 @@ const Resource = Ember.Object.extend(ResourceOperationsMixin, { } }, + /** + @private + @method _updateToOneRelationshipData + @param {String} relation + @param {String|null} id + */ + _updateToOneRelationshipData(relation, id) { + let relationshipData = 'relationships.' + relation + '.data'; + let existing = this.get(relationshipData); + existing = (existing) ? existing.id : null; + if (id === null || isType('string', id) && existing !== id) { + this.removeRelationship(relation, existing); + if (id !== null) { + this.addRelationship(relation, id); + } + } + }, + + /** + @private + @method _replaceRelationshipsData + @param {String} relation + @param {Array|String|null} ids + */ + _replaceRelationshipsData(relation, ids) { + if (!Array.isArray(ids)) { + this._updateToOneRelationshipData(relation, ids); + } else { + let existing = this._existingRelationshipData(relation); + if (!existing.length) { + this.addRelationships(relation, ids); + } else { + this.removeRelationships(relation, existing); + this.addRelationships(relation, ids); + } + } + }, + + /** + @private + @method _existingRelationshipData + @param {String} relation + */ + _existingRelationshipData(relation) { + let relationshipData = 'relationships.' + relation + '.data'; + return this.get(relationshipData).map(function(rel) { return rel.id; }); + }, + /** @method addRelationships @param {String} related - resource name diff --git a/tests/unit/mixins/resource-operations-test.js b/tests/unit/mixins/resource-operations-test.js index c51680f..46f768e 100644 --- a/tests/unit/mixins/resource-operations-test.js +++ b/tests/unit/mixins/resource-operations-test.js @@ -30,8 +30,8 @@ module('Unit | Mixin | resource-operations', { this.subject = Cowboy.create({ id: 1, name:'Lone Ranger'}); // mock payload setup this.subject.set('relationships', { - guns: { links: { related: 'url' } }, - horse: { links: { related: 'url' } } + guns: { links: { related: 'url' }, data: [] }, + horse: { links: { related: 'url' }, data: null } }); }, afterEach() { @@ -115,13 +115,13 @@ test('deleteRelationship for to-many relation', function(assert) { test('updateRelationship for to-one relation', function(assert) { - this.sandbox.stub(this.subject, '_updateRelationshipsData'); + this.sandbox.stub(this.subject, '_replaceRelationshipsData'); let promise = this.subject.updateRelationship('horse', 1); assert.ok(typeof promise.then === 'function', 'returns a thenable'); assert.ok( - this.subject._updateRelationshipsData.calledWith('horse', 1), - 'called _updateRelationshipsData' + this.subject._replaceRelationshipsData.calledWith('horse', 1), + 'called _replaceRelationshipsData' ); let args = this.subject.get('service').patchRelationship.firstCall.args; diff --git a/tests/unit/models/resource-test.js b/tests/unit/models/resource-test.js index 6565950..e0b70ba 100644 --- a/tests/unit/models/resource-test.js +++ b/tests/unit/models/resource-test.js @@ -613,24 +613,41 @@ test('#updateRelationship, from resource-operations mixin', function(assert) { }); let author = post.get('relationships.author.data'); let comments = post.get('relationships.comments.data'); + let commentsIds = comments.map(comment => comment.id).sort(); assert.equal(author.id, 2, 'post has author id 2'); + assert.deepEqual(commentsIds, ['4'], 'post has comments with id 4'); post.updateRelationship('comments', ['4', '5']); comments = post.get('relationships.comments.data'); + commentsIds = comments.map(comment => comment.id).sort(); assert.ok(serviceOp.calledOnce, 'service#patchRelationship called once'); assert.equal(comments.length, 2, 'post has 2 comments'); + assert.deepEqual(commentsIds, ['4', '5'], 'post has comments with id 4 and 5'); + + post.updateRelationship('comments', ['2', '5']); + comments = post.get('relationships.comments.data'); + commentsIds = comments.map(comment => comment.id).sort(); + assert.ok(serviceOp.calledTwice, 'service#patchRelationship called once'); + assert.deepEqual(comments.length, 2, 'post has 2 comments'); + assert.deepEqual(commentsIds, ['2', '5'], 'post has comments with id 2 and 5'); post.updateRelationship('comments', ['1', '2', '3', '4']); comments = post.get('relationships.comments.data'); - assert.equal(comments.length, 5, 'post has 5 comments'); + commentsIds = comments.map(comment => comment.id).sort(); + assert.equal(comments.length, 4, 'post has 4 comments'); + assert.deepEqual(commentsIds, ['1', '2', '3', '4'], 'post has comments with id 1, 2, 3 and 4'); post.updateRelationship('comments', ['1', '2']); comments = post.get('relationships.comments.data'); + commentsIds = comments.map(comment => comment.id).sort(); assert.equal(comments.length, 2, 'post has 2 comments'); + assert.deepEqual(commentsIds, ['1', '2'], 'post has comments with id 1 and 2'); post.updateRelationship('comments', []); comments = post.get('relationships.comments.data'); + commentsIds = comments.map(comment => comment.id).sort(); assert.equal(comments.length, 0, 'post has 0 comments'); + assert.deepEqual(commentsIds, [], 'post has not comments'); post.updateRelationship('author', '1'); author = post.get('relationships.author.data');