Skip to content

Commit 4fb6782

Browse files
committed
Add service methods to create/delete relationships (#80)
1 parent 4f1257e commit 4fb6782

File tree

4 files changed

+208
-40
lines changed

4 files changed

+208
-40
lines changed

addon/adapters/application.js

Lines changed: 88 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -170,28 +170,6 @@ export default Ember.Object.extend(FetchMixin, Evented, {
170170
});
171171
},
172172

173-
/**
174-
Patch a relationship, either add or remove, sends a PATCH request
175-
176-
Adds with payload: `{ "data": { "type": "comments", "id": "12" } }`
177-
Removes with payload: `{ "data": null }` for to-one or `{ "data": [] }` for to-many
178-
179-
@method patchRelationship
180-
@param {Resource} the resource instance, has URLs via it's relationships property
181-
@param {String} resource name (plural) to find the url from the resource instance
182-
@return {Promise}
183-
*/
184-
patchRelationship(resource, relationship) {
185-
let url = resource.get(['relationships', relationship, 'links', 'self'].join('.'));
186-
url = url || [this.get('url'), resource.get('id'), 'relationships', relationship].join('/');
187-
let data = resource.get(['relationships', relationship, 'data'].join('.'));
188-
data = { data: data };
189-
return this.fetch(url, {
190-
method: 'PATCH',
191-
body: JSON.stringify(data)
192-
});
193-
},
194-
195173
/**
196174
Delete an existing resource, sends a DELETE request
197175
@@ -211,6 +189,94 @@ export default Ember.Object.extend(FetchMixin, Evented, {
211189
return this.fetch(url, { method: 'DELETE' });
212190
},
213191

192+
/**
193+
Creates a relationship, sends a POST request
194+
195+
Adds using a payload with the resource object:
196+
197+
- to-one: `{ "data": { "type": "authors", "id": "1" } }`
198+
- to-many: `{ "data": [{ "type": "comments", "id": "12" }] }`
199+
200+
@method createRelationship
201+
@param {Resource} resource instance, has URLs via it's relationships property
202+
@param {String} relationship name (plural) to find the url from the resource instance
203+
@param {String} id of the related resource
204+
@return {Promise}
205+
*/
206+
createRelationship(resource, relationship, id) {
207+
return this.fetch(this._urlForRelationship(resource, relationship), {
208+
method: 'POST',
209+
body: JSON.stringify(this._payloadForRelationship(resource, relationship, id))
210+
});
211+
},
212+
213+
/**
214+
Patch a relationship, either adds or removes everyting, sends a PATCH request
215+
216+
Adds with payload: `{ "data": { "type": "comments", "id": "12" } }`
217+
Removes with payload: `{ "data": null }` for to-one or `{ "data": [] }` for to-many
218+
219+
@method patchRelationship
220+
@param {Resource} resource instance, has URLs via it's relationships property
221+
@param {String} relationship name (plural) to find the url from the resource instance
222+
@return {Promise}
223+
*/
224+
patchRelationship(resource, relationship) {
225+
return this.fetch(this._urlForRelationship(resource, relationship), {
226+
method: 'PATCH',
227+
body: JSON.stringify({
228+
data: resource.get(['relationships', relationship, 'data'].join('.'))
229+
})
230+
});
231+
},
232+
233+
/**
234+
Deletes a relationship, sends a DELETE request
235+
236+
Removes using a payload with the resource object:
237+
238+
- to-one: `{ "data": { "type": "authors", "id": "1" } }`
239+
- to-many: `{ "data": [{ "type": "comments", "id": "12" }] }`
240+
241+
@method deleteRelationship
242+
@param {Resource} resource instance, has URLs via it's relationships property
243+
@param {String} relationship name (plural) to find the url from the resource instance
244+
@param {String} id of the related resource
245+
@return {Promise}
246+
*/
247+
deleteRelationship(resource, relationship, id) {
248+
return this.fetch(this._urlForRelationship(resource, relationship), {
249+
method: 'DELETE',
250+
body: JSON.stringify(this._payloadForRelationship(resource, relationship, id))
251+
});
252+
},
253+
254+
/**
255+
@method _urlForRelationship
256+
@private
257+
@param {Resource} [resource] instance, has URLs via it's relationships property
258+
@param {String} [relationship] name (plural) to find the url from the resource instance
259+
@return {String} url
260+
*/
261+
_urlForRelationship(resource, relationship) {
262+
let url = resource.get(['relationships', relationship, 'links', 'self'].join('.'));
263+
return url || [this.get('url'), resource.get('id'), 'relationships', relationship].join('/');
264+
},
265+
266+
/**
267+
@method _payloadForRelationship
268+
@private
269+
@param {Resource} [resource] instance, has URLs via it's relationships property
270+
@param {String} [relationship] name (plural) to find the url from the resource instance
271+
@param {String} [id] the id for the related resource
272+
@return {Object} payload
273+
*/
274+
_payloadForRelationship(resource, relationship, id) {
275+
let data = resource.get(['relationships', relationship, 'data'].join('.'));
276+
let resourceObject = { type: pluralize(relationship), id: id };
277+
return { data: (Array.isArray(data)) ? [resourceObject] : resourceObject };
278+
},
279+
214280
/**
215281
Fetches data using Fetch API or XMLHttpRequest
216282

addon/services/store.js

Lines changed: 57 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export default Ember.Service.extend({
2424
@return {Promise}
2525
*/
2626
find(type, options) {
27-
const service = this._service(type, options);
27+
let service = this._service(type, options);
2828
return service.find(options);
2929
},
3030

@@ -36,7 +36,7 @@ export default Ember.Service.extend({
3636
@return {Ember.Array}
3737
*/
3838
all(type) {
39-
const service = this._service(type);
39+
let service = this._service(type);
4040
return (service.cache && service.cache.data) ? service.cache.data : Ember.A([]);
4141
},
4242

@@ -48,7 +48,7 @@ export default Ember.Service.extend({
4848
@return {Promise}
4949
*/
5050
createResource(type, resource) {
51-
const service = this._service(type);
51+
let service = this._service(type);
5252
return service.createResource(resource);
5353
},
5454

@@ -61,38 +61,78 @@ export default Ember.Service.extend({
6161
@return {Promise}
6262
*/
6363
updateResource(type, resource) {
64-
const service = this._service(type);
64+
let service = this._service(type);
6565
return service.updateResource(resource);
6666
},
6767

6868
/**
69-
Patch a relationship, either add or remove, sends a PATCH request
69+
Delete an existing resource, sends a DELETE request
70+
71+
@method deleteResource
72+
@param {String} type - the entity or resource name will be pluralized
73+
@param {String|Resource} the name (plural) or resource instance w/ self link
74+
@return {Promise}
75+
*/
76+
deleteResource(type, resource) {
77+
let service = this._service(type);
78+
return service.deleteResource(resource);
79+
},
80+
81+
/**
82+
Creates a relationship, sends a POST request
83+
84+
Adds using a payload with the resource object:
85+
86+
- to-one: `{ "data": { "type": "authors", "id": "1" } }`
87+
- to-many: `{ "data": [{ "type": "comments", "id": "12" }] }`
88+
89+
@method createRelationship
90+
@param {String} type the entity or resource name will be pluralized
91+
@param {Resource} resource instance, has URLs via it's relationships property
92+
@param {String} relationship name (plural) to find the url from the resource instance
93+
@param {String} id of the related resource
94+
@return {Promise}
95+
*/
96+
createRelationship(type, resource, relationship, id) {
97+
let service = this._service(type);
98+
return service.createRelationship(resource, relationship, id);
99+
},
100+
101+
/**
102+
Patch a relationship, either adds or removes everyting, sends a PATCH request
70103
71104
Adds with payload: `{ "data": { "type": "comments", "id": "12" } }`
72-
Removes with payload: `{ "data": null }`
105+
Removes with payload: `{ "data": null }` for to-one or `{ "data": [] }` for to-many
73106
74107
@method patchRelationship
75-
@param {String} type - the entity or resource name will be pluralized
76-
@param {Resource} resource - model instance, has URLs via it's relationships property
77-
@param {String} relationship - resource name (plural) to find the url from the resource instance
108+
@param {String} type the entity or resource name will be pluralized
109+
@param {Resource} resource instance, has URLs via it's relationships property
110+
@param {String} relationship name (plural) to find the url from the resource instance
78111
@return {Promise}
79112
*/
80113
patchRelationship(type, resource, relationship) {
81-
const service = this._service(type);
114+
let service = this._service(type);
82115
return service.patchRelationship(resource, relationship);
83116
},
84117

85118
/**
86-
Delete an existing resource, sends a DELETE request
119+
Deletes a relationship, sends a DELETE request
87120
88-
@method deleteResource
89-
@param {String} type - the entity or resource name will be pluralized
90-
@param {String|Resource} the name (plural) or resource instance w/ self link
121+
Removes using a payload with the resource object:
122+
123+
- to-one: `{ "data": { "type": "authors", "id": "1" } }`
124+
- to-many: `{ "data": [{ "type": "comments", "id": "12" }] }`
125+
126+
@method deleteRelationship
127+
@param {String} type the entity or resource name will be pluralized
128+
@param {Resource} resource instance, has URLs via it's relationships property
129+
@param {String} relationship name (plural) to find the url from the resource instance
130+
@param {String} id of the related resource
91131
@return {Promise}
92132
*/
93-
deleteResource(type, resource) {
94-
const service = this._service(type);
95-
return service.deleteResource(resource);
133+
deleteRelationship(type, resource, relationship, id) {
134+
let service = this._service(type);
135+
return service.deleteRelationship(resource, relationship, id);
96136
},
97137

98138
/**

tests/unit/adapters/application-test.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,66 @@ test('when serializer returns null (nothing changed) #updateResource return prom
211211
});
212212
});
213213

214+
test('#createRelationship (to-many)', function(assert) {
215+
const done = assert.async();
216+
mockServices.call(this);
217+
let adapter = this.subject({type: 'posts', url: '/posts'});
218+
sandbox.stub(adapter, 'fetch', function () { return Ember.RSVP.Promise.resolve(null); });
219+
let resource = this.container.lookup('model:post').create(postMock.data);
220+
let promise = adapter.createRelationship(resource, 'comments', '1');
221+
assert.ok(typeof promise.then === 'function', 'returns a thenable');
222+
promise.then(function() {
223+
let relationURL = 'http://api.pixelhandler.com/api/v1/posts/1/relationships/comments';
224+
let jsonBody = '{"data":[{"type":"comments","id":"1"}]}';
225+
let msg = '#fetch called with url and options with data';
226+
assert.ok(adapter.fetch.calledWith(relationURL, { method: 'POST', body: jsonBody }), msg);
227+
done();
228+
});
229+
});
230+
231+
test('#createRelationship (to-one)', function(assert) {
232+
const adapter = this.subject({type: 'posts', url: '/posts'});
233+
mockServices.call(this);
234+
sandbox.stub(adapter, 'fetch', function () { return Ember.RSVP.Promise.resolve(null); });
235+
let resource = this.container.lookup('model:post').create(postMock.data);
236+
let promise = adapter.createRelationship(resource, 'author', '1');
237+
assert.ok(typeof promise.then === 'function', 'returns a thenable');
238+
let relationURL = 'http://api.pixelhandler.com/api/v1/posts/1/relationships/author';
239+
let jsonBody = '{"data":{"type":"authors","id":"1"}}';
240+
let msg = '#fetch called with url and options with data';
241+
assert.ok(adapter.fetch.calledWith(relationURL, { method: 'POST', body: jsonBody }), msg);
242+
});
243+
244+
test('#deleteRelationship (to-many)', function(assert) {
245+
const done = assert.async();
246+
mockServices.call(this);
247+
let adapter = this.subject({type: 'posts', url: '/posts'});
248+
sandbox.stub(adapter, 'fetch', function () { return Ember.RSVP.Promise.resolve(null); });
249+
let resource = this.container.lookup('model:post').create(postMock.data);
250+
let promise = adapter.deleteRelationship(resource, 'comments', '1');
251+
assert.ok(typeof promise.then === 'function', 'returns a thenable');
252+
promise.then(function() {
253+
let relationURL = 'http://api.pixelhandler.com/api/v1/posts/1/relationships/comments';
254+
let jsonBody = '{"data":[{"type":"comments","id":"1"}]}';
255+
let msg = '#fetch called with url and options with data';
256+
assert.ok(adapter.fetch.calledWith(relationURL, { method: 'DELETE', body: jsonBody }), msg);
257+
done();
258+
});
259+
});
260+
261+
test('#deleteRelationship (to-one)', function(assert) {
262+
const adapter = this.subject({type: 'posts', url: '/posts'});
263+
mockServices.call(this);
264+
sandbox.stub(adapter, 'fetch', function () { return Ember.RSVP.Promise.resolve(null); });
265+
let resource = this.container.lookup('model:post').create(postMock.data);
266+
let promise = adapter.deleteRelationship(resource, 'author', '1');
267+
assert.ok(typeof promise.then === 'function', 'returns a thenable');
268+
let relationURL = 'http://api.pixelhandler.com/api/v1/posts/1/relationships/author';
269+
let jsonBody = '{"data":{"type":"authors","id":"1"}}';
270+
let msg = '#fetch called with url and options with data';
271+
assert.ok(adapter.fetch.calledWith(relationURL, { method: 'DELETE', body: jsonBody }), msg);
272+
});
273+
214274
test('#patchRelationship (to-many)', function(assert) {
215275
const done = assert.async();
216276
mockServices.call(this);

tests/unit/services/store-test.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import { moduleFor, test } from 'ember-qunit';
22
import Ember from 'ember';
33
import { pluralize } from 'ember-inflector';
44

5-
let methods = Ember.String.w('find createResource updateResource patchRelationship deleteResource');
5+
let methods = 'find createResource updateResource deleteResource';
6+
methods += ' createRelationship patchRelationship deleteRelationship';
7+
methods = Ember.String.w(methods);
68

79
let mockServices, entities = ['post', 'author', 'comment'];
810

0 commit comments

Comments
 (0)