Skip to content
This repository was archived by the owner on Jan 22, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions addon/mixins/resource-operations.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -105,7 +105,8 @@ export default Ember.Mixin.create({
See: <http://jsonapi.org/format/#crud-updating-resource-relationships>

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
Expand All @@ -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);
Expand Down
60 changes: 50 additions & 10 deletions addon/models/resource.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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
Expand Down
10 changes: 5 additions & 5 deletions tests/unit/mixins/resource-operations-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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;
Expand Down
19 changes: 18 additions & 1 deletion tests/unit/models/resource-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down