Skip to content

Commit

Permalink
Add include param support for create/update/delete operations
Browse files Browse the repository at this point in the history
Fixes emberjs#4344

This allows an `include` parameter for be specified when creating,
updating, or deleting a model.

This feature was originally discussed in
emberjs/rfcs#99 but was not part of the initial
implementation.

Examples:

```javascript
// POST /posts?include=author

let post = this.store.createRecord('post', { title: 'Hurray' });

post.save({ include: 'author' });
```

```javascript
// PUT /posts/123?include=comments

let post = this.store.peekRecord('post', 123);

set(post, 'title', 'Hurray');

post.save({ include: 'comments' });
```

```javascript
// DEL /posts/123?include=comments

let post = this.store.peekRecord('post', 123);

post.deleteRecord();

post.save({ include: 'comments' });
```
  • Loading branch information
HeroicEric committed Apr 30, 2016
1 parent b754321 commit 6897017
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 69 deletions.
33 changes: 26 additions & 7 deletions addon/-private/adapters/build-url-mixin.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import Ember from 'ember';
import isEnabled from 'ember-data/-private/features';

var get = Ember.get;
const {
$,
get
} = Ember;

/**
Expand Down Expand Up @@ -85,11 +89,12 @@ export default Ember.Mixin.create({
@param {String} id
@return {String} url
*/
_buildURL(modelName, id) {
_buildURL(modelName, id, snapshot) {
var url = [];
var host = get(this, 'host');
var prefix = this.urlPrefix();
var path;
var queryParams = this._buildQueryParams(snapshot);

if (modelName) {
path = this.pathForType(modelName);
Expand All @@ -104,9 +109,23 @@ export default Ember.Mixin.create({
url = '/' + url;
}

if (queryParams) {
url = `${url}?${queryParams}`;
}

return url;
},

_buildQueryParams(snapshot) {
let queryParams = {};

if (snapshot && snapshot.include) {
queryParams.include = snapshot.include;
}

return $.param(queryParams);
},

/**
* @method urlForFindRecord
* @param {String} id
Expand All @@ -115,7 +134,7 @@ export default Ember.Mixin.create({
* @return {String} url
*/
urlForFindRecord(id, modelName, snapshot) {
return this._buildURL(modelName, id);
return this._buildURL(modelName, id, snapshot);
},

/**
Expand All @@ -125,7 +144,7 @@ export default Ember.Mixin.create({
* @return {String} url
*/
urlForFindAll(modelName, snapshot) {
return this._buildURL(modelName);
return this._buildURL(modelName, null, snapshot);
},

/**
Expand Down Expand Up @@ -188,7 +207,7 @@ export default Ember.Mixin.create({
* @return {String} url
*/
urlForCreateRecord(modelName, snapshot) {
return this._buildURL(modelName);
return this._buildURL(modelName, null, snapshot);
},

/**
Expand All @@ -199,7 +218,7 @@ export default Ember.Mixin.create({
* @return {String} url
*/
urlForUpdateRecord(id, modelName, snapshot) {
return this._buildURL(modelName, id);
return this._buildURL(modelName, id, snapshot);
},

/**
Expand All @@ -210,7 +229,7 @@ export default Ember.Mixin.create({
* @return {String} url
*/
urlForDeleteRecord(id, modelName, snapshot) {
return this._buildURL(modelName, id);
return this._buildURL(modelName, id, snapshot);
},

/**
Expand Down
27 changes: 5 additions & 22 deletions addon/adapters/rest.js
Original file line number Diff line number Diff line change
Expand Up @@ -426,9 +426,8 @@ var RESTAdapter = Adapter.extend(BuildURLMixin, {
return this._makeRequest(request);
} else {
const url = this.buildURL(type.modelName, id, snapshot, 'findRecord');
const query = this.buildQuery(snapshot);

return this.ajax(url, 'GET', { data: query });
return this.ajax(url, 'GET');
}
},

Expand All @@ -447,24 +446,23 @@ var RESTAdapter = Adapter.extend(BuildURLMixin, {
@return {Promise} promise
*/
findAll(store, type, sinceToken, snapshotRecordArray) {
const query = this.buildQuery(snapshotRecordArray);

if (isEnabled('ds-improved-ajax')) {
const request = this._requestFor({
store, type, sinceToken, query,
store, type, sinceToken,
snapshots: snapshotRecordArray,
requestType: 'findAll'
});

return this._makeRequest(request);
} else {
const url = this.buildURL(type.modelName, null, snapshotRecordArray, 'findAll');
let options;

if (sinceToken) {
query.since = sinceToken;
options = { data: { since: sinceToken } };
}

return this.ajax(url, 'GET', { data: query });
return this.ajax(url, 'GET', options);
}
},

Expand Down Expand Up @@ -1149,20 +1147,6 @@ var RESTAdapter = Adapter.extend(BuildURLMixin, {
return ['Ember Data Request ' + requestDescription + ' returned a ' + status,
payloadDescription,
shortenedPayload].join('\n');
},

buildQuery(snapshot) {
let query = {};

if (snapshot) {
const { include } = snapshot;

if (include) {
query.include = include;
}
}

return query;
}
});

Expand Down Expand Up @@ -1197,7 +1181,6 @@ if (isEnabled('ds-improved-ajax')) {
break;

case 'findRecord':
data = this.buildQuery(snapshot);
break;

case 'findAll':
Expand Down
85 changes: 75 additions & 10 deletions tests/integration/adapter/rest-adapter-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ test("findRecord - basic payload", function(assert) {
run(store, 'findRecord', 'post', 1).then(assert.wait(function(post) {
assert.equal(passedUrl, "/posts/1");
assert.equal(passedVerb, "GET");
assert.deepEqual(passedHash.data, {});
assert.equal(passedHash, undefined);

assert.equal(post.get('id'), "1");
assert.equal(post.get('name'), "Rails is omakase");
Expand All @@ -93,7 +93,7 @@ test("findRecord - basic payload (with legacy singular name)", function(assert)
run(store, 'findRecord', 'post', 1).then(assert.wait(function(post) {
assert.equal(passedUrl, "/posts/1");
assert.equal(passedVerb, "GET");
assert.deepEqual(passedHash.data, {});
assert.equal(passedHash, undefined);

assert.equal(post.get('id'), "1");
assert.equal(post.get('name'), "Rails is omakase");
Expand All @@ -111,7 +111,7 @@ test("findRecord - payload with sideloaded records of the same type", function(a
run(store, 'findRecord', 'post', 1).then(assert.wait(function(post) {
assert.equal(passedUrl, "/posts/1");
assert.equal(passedVerb, "GET");
assert.deepEqual(passedHash.data, {});
assert.equal(passedHash, undefined);

assert.equal(post.get('id'), "1");
assert.equal(post.get('name'), "Rails is omakase");
Expand All @@ -131,7 +131,7 @@ test("findRecord - payload with sideloaded records of a different type", functio
run(store, 'findRecord', 'post', 1).then(assert.wait(function(post) {
assert.equal(passedUrl, "/posts/1");
assert.equal(passedVerb, "GET");
assert.deepEqual(passedHash.data, {});
assert.equal(passedHash, undefined);

assert.equal(post.get('id'), "1");
assert.equal(post.get('name'), "Rails is omakase");
Expand All @@ -153,7 +153,7 @@ test("findRecord - payload with an serializer-specified primary key", function(a
run(store, 'findRecord', 'post', 1).then(assert.wait(function(post) {
assert.equal(passedUrl, "/posts/1");
assert.equal(passedVerb, "GET");
assert.deepEqual(passedHash.data, {});
assert.equal(passedHash, undefined);

assert.equal(post.get('id'), "1");
assert.equal(post.get('name'), "Rails is omakase");
Expand All @@ -177,7 +177,7 @@ test("findRecord - payload with a serializer-specified attribute mapping", funct
run(store, 'findRecord', 'post', 1).then(assert.wait(function(post) {
assert.equal(passedUrl, "/posts/1");
assert.equal(passedVerb, "GET");
assert.deepEqual(passedHash.data, {});
assert.equal(passedHash, undefined);

assert.equal(post.get('id'), "1");
assert.equal(post.get('name'), "Rails is omakase");
Expand All @@ -191,7 +191,7 @@ test("findRecord - passes `include` as a query parameter to ajax", function(asse
});

run(store, 'findRecord', 'post', 1, { include: 'comments' }).then(assert.wait(function() {
assert.deepEqual(passedHash.data, { include: 'comments' }, '`include` parameter sent to adapter.ajax');
assert.deepEqual(passedUrl, '/posts/1?include=comments', '`include` parameter sent to adapter.ajax');
}));
});

Expand Down Expand Up @@ -590,6 +590,19 @@ test("createRecord - relationships are not duplicated", function(assert) {
}));
});

if (isEnabled('ds-finder-include')) {
test("createRecord - passes `include` as a query parameter to ajax", function(assert) {
ajaxResponse();

run(() => {
let post = store.createRecord('post');
post.save({ include: 'comments' }).then(assert.wait((post) => {
assert.equal(passedUrl, '/posts?include=comments', '`include` parameter sent to adapter.ajax');
}));
});
});
}

test("updateRecord - an empty payload is a basic success", function(assert) {
run(function() {
store.push({
Expand Down Expand Up @@ -852,6 +865,32 @@ test("updateRecord - hasMany relationships faithfully reflect simultaneous adds
}));
});

if (isEnabled('ds-finder-include')) {
test("updateRecord - passes `include` as a query parameter to ajax", function(assert) {
run(() => {
store.push({
data: {
type: 'post',
id: '1',
attributes: {
name: 'Rails is omakase'
}
}
});
});

ajaxResponse();

run(() => {
let post = store.peekRecord('post', '1');
post.set('name', 'Gimme the comments');
post.save({ include: 'comments' }).then(assert.wait((post) => {
assert.equal(passedUrl, '/posts/1?include=comments', '`include` parameter sent to adapter.ajax');
}));
});
});
}

test("deleteRecord - an empty payload is a basic success", function(assert) {
adapter.shouldBackgroundReloadRecord = () => false;
run(function() {
Expand Down Expand Up @@ -992,6 +1031,32 @@ test("deleteRecord - deleting a newly created record should not throw an error",
});
});

if (isEnabled('ds-finder-include')) {
test("deleteRecord - passes `include` as a query parameter to ajax", function(assert) {
run(() => {
store.push({
data: {
type: 'post',
id: '1',
attributes: {
name: 'Rails is omakase'
}
}
});
});

ajaxResponse();

run(() => {
let post = store.peekRecord('post', '1');
post.deleteRecord();
post.save({ include: 'comments' }).then(assert.wait((post) => {
assert.equal(passedUrl, '/posts/1?include=comments', '`include` parameter sent to adapter.ajax');
}));
});
});
}

test("findAll - returning an array populates the array", function(assert) {
ajaxResponse({
posts: [
Expand All @@ -1003,7 +1068,7 @@ test("findAll - returning an array populates the array", function(assert) {
store.findAll('post').then(assert.wait(function(posts) {
assert.equal(passedUrl, "/posts");
assert.equal(passedVerb, "GET");
assert.deepEqual(passedHash.data, {});
assert.equal(passedHash, undefined);

var post1 = store.peekRecord('post', 1);
var post2 = store.peekRecord('post', 2);
Expand Down Expand Up @@ -1057,7 +1122,7 @@ test("findAll - passed `include` as a query parameter to ajax", function(assert)
});

run(store, 'findAll', 'post', { include: 'comments' }).then(assert.wait(function() {
assert.deepEqual(passedHash.data, { include: 'comments' }, '`include` params sent to adapter.ajax');
assert.deepEqual(passedUrl, '/posts?include=comments', '`include` params sent to adapter.ajax');
}));
});

Expand Down Expand Up @@ -1474,7 +1539,7 @@ test("findMany - findMany does not coalesce by default", function(assert) {
});
run(post, 'get', 'comments').then(assert.wait(function(comments) {
assert.equal(passedUrl, "/comments/3");
assert.deepEqual(passedHash.data, {});
assert.equal(passedHash, undefined);
}));
});

Expand Down
Loading

0 comments on commit 6897017

Please sign in to comment.