Skip to content

Commit

Permalink
Allow include query parameter with store.findRecord & store.findAll
Browse files Browse the repository at this point in the history
Related RFC: emberjs/rfcs#99

These changes allow an `include` paremeter to be specified when using
`store.findRecord` and `store.findAll`:

```javascript
store.findRecord('post', 123, { include: 'comments' });
store.findAll('post', { include: 'comments' });
```

The value for `include` is copied into the `adapterOptions` that are
passed to the corresponding adapter function as part of the `snapshot`
or `snapshotRecordArray`. This value is then pulled from the snapshot
and used as a query parameter (`include`) when making an AJAX request
via `adapter.ajax`.
  • Loading branch information
HeroicEric committed Dec 8, 2015
1 parent 41b203d commit 97193d0
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 22 deletions.
30 changes: 25 additions & 5 deletions addon/adapters/rest-adapter.js
Expand Up @@ -10,8 +10,12 @@ import {
AbortError
} from 'ember-data/adapters/errors';
import EmptyObject from "ember-data/system/empty-object";
var get = Ember.get;
var MapWithDefault = Ember.MapWithDefault;

const {
MapWithDefault,
get,
merge
} = Ember;

import BuildURLMixin from "ember-data/adapters/build-url-mixin";

Expand Down Expand Up @@ -371,7 +375,17 @@ export default Adapter.extend(BuildURLMixin, {
@return {Promise} promise
*/
findRecord(store, type, id, snapshot) {
return this.ajax(this.buildURL(type.modelName, id, snapshot, 'findRecord'), 'GET');
const url = this.buildURL(type.modelName, id, snapshot, 'findRecord');
const adapterOptions = snapshot.adapterOptions || {};
const { include } = adapterOptions;

let query;

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

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

/**
Expand All @@ -389,13 +403,19 @@ export default Adapter.extend(BuildURLMixin, {
@return {Promise} promise
*/
findAll(store, type, sinceToken, snapshotRecordArray) {
var query, url;
const url = this.buildURL(type.modelName, null, null, 'findAll');
const adapterOptions = snapshotRecordArray.adapterOptions || {};
const { include } = adapterOptions;

let query;

if (sinceToken) {
query = { since: sinceToken };
}

url = this.buildURL(type.modelName, null, null, 'findAll');
if (include) {
query = merge(query || {}, { include });
}

return this.ajax(url, 'GET', { data: query });
},
Expand Down
21 changes: 16 additions & 5 deletions addon/system/store.js
Expand Up @@ -59,6 +59,7 @@ export let badIdFormatAssertion = '`id` has to be non-empty string or number';
var Backburner = Ember._Backburner || Ember.Backburner || Ember.__loader.require('backburner')['default'] || Ember.__loader.require('backburner')['Backburner'];
var Map = Ember.Map;
var isArray = Array.isArray || Ember.isArray;
var merge = Ember.merge;

//Shim Backburner.join
if (!Backburner.prototype.join) {
Expand Down Expand Up @@ -499,12 +500,16 @@ Store = Service.extend({
@param {Object} options
@return {Promise} promise
*/
findRecord(modelName, id, options) {
findRecord(modelName, id, options = {}) {
assert('Passing classes to store methods has been removed. Please pass a dasherized string instead of '+ Ember.inspect(modelName), typeof modelName === 'string');
assert(badIdFormatAssertion, (typeof id === 'string' && id.length > 0) || (typeof id === 'number' && !isNaN(id)));

var internalModel = this._internalModelForId(modelName, id);
options = options || {};
const internalModel = this._internalModelForId(modelName, id);
const { include } = options;

if (include) {
options.adapterOptions = merge(options.adapterOptions || {}, { include });
}

if (!this.hasRecordForId(modelName, id)) {
return this._findByInternalModel(internalModel, options);
Expand Down Expand Up @@ -1001,9 +1006,15 @@ Store = Service.extend({
@param {Object} options
@return {DS.AdapterPopulatedRecordArray}
*/
findAll(modelName, options) {
findAll(modelName, options = {}) {
assert('Passing classes to store methods has been removed. Please pass a dasherized string instead of '+ Ember.inspect(modelName), typeof modelName === 'string');
var typeClass = this.modelFor(modelName);

const typeClass = this.modelFor(modelName);
const { include } = options;

if (include) {
options.adapterOptions = merge(options.adapterOptions || {}, { include });
}

return this._fetchAll(typeClass, this.peekAll(modelName), options);
},
Expand Down
45 changes: 38 additions & 7 deletions tests/integration/adapter/rest-adapter-test.js
Expand Up @@ -56,7 +56,7 @@ test("find - basic payload", function(assert) {
run(store, 'find', 'post', 1).then(assert.wait(function(post) {
assert.equal(passedUrl, "/posts/1");
assert.equal(passedVerb, "GET");
assert.equal(passedHash, undefined);
assert.equal(passedHash.data, undefined);

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

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

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

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

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

assert.equal(post.get('id'), "1");
assert.equal(post.get('name'), "Rails is omakase");
assert.equal(post.get('createdAt'), 2013);
}));
});

test("findRecord - passes `include` as a query parameter to ajax", function(assert) {
adapter.ajax = function(url, verb, hash) {
assert.deepEqual(hash.data, { include: 'comments' },
'`include` parameter sent to adapter.ajax');

return run(Ember.RSVP, 'resolve', {
post: { id: 1, name: 'Rails is very expensive sushi' }
});
};

run(store, 'findRecord', 'post', 1, { include: 'comments' }).then(assert.wait(function() {
// Noop
}));
});

test("create - an empty payload is a basic success if an id was specified", function(assert) {
ajaxResponse();
var post;
Expand Down Expand Up @@ -1028,6 +1043,22 @@ test("findAll - passes buildURL the requestType", function(assert) {
}));
});

test("findAll - passed `include` as a query parameter to ajax", function(assert) {
adapter.ajax = function(url, verb, hash) {
assert.deepEqual(hash.data, { include: 'comments' },
'`include` params sent to adapter.ajax');

return run(Ember.RSVP, 'resolve', {
posts: [{ id: 1, name: 'Rails is very expensive sushi' }]
});
};

run(store, 'findAll', 'post', { include: 'comments' }).then(assert.wait(function() {
// Noop
}));
});


test("findAll - returning sideloaded data loads the data", function(assert) {
ajaxResponse({
posts: [
Expand Down Expand Up @@ -1461,7 +1492,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.equal(passedHash, null);
assert.equal(passedHash.data, null);
}));
});

Expand Down
25 changes: 23 additions & 2 deletions tests/integration/adapter/store-adapter-test.js
Expand Up @@ -1303,7 +1303,7 @@ test("record.save should pass adapterOptions to the deleteRecord method", functi
});


test("findRecord should pass adapterOptions to the find method", function(assert) {
test("store.findRecord should pass adapterOptions to adapter.findRecord", function(assert) {
assert.expect(1);

env.adapter.findRecord = assert.wait(function(store, type, id, snapshot) {
Expand All @@ -1316,8 +1316,18 @@ test("findRecord should pass adapterOptions to the find method", function(assert
});
});

test("store.findRecord should pass 'include' inside adapterOptions passed to adapter.findRecord", function(assert) {
assert.expect(1);

env.adapter.findRecord = assert.wait(function(store, type, id, snapshot) {
assert.deepEqual(snapshot.adapterOptions, { include: 'books' });
return Ember.RSVP.resolve({ id: 1 });
});

run(() => store.findRecord('person', 1, { include: 'books' }));
});

test("findAll should pass adapterOptions to the findAll method", function(assert) {
test("store.findAll should pass adapterOptions to the adapter.findAll method", function(assert) {
assert.expect(1);

env.adapter.findAll = assert.wait(function(store, type, sinceToken, arraySnapshot) {
Expand All @@ -1331,6 +1341,17 @@ test("findAll should pass adapterOptions to the findAll method", function(assert
});
});

test("store.findAll should pass 'include' inside adapterOptions passed to adapter.findAll", function(assert) {
assert.expect(1);

env.adapter.findAll = assert.wait(function(store, type, sinceToken, arraySnapshot) {
const { adapterOptions } = arraySnapshot;
assert.deepEqual(adapterOptions, { include: 'books' });
return Ember.RSVP.resolve([{ id: 1 }]);
});

run(() => store.findAll('person', { include: 'books' }));
});

test("An async hasMany relationship with links should not trigger shouldBackgroundReloadRecord", function(assert) {
var Post = DS.Model.extend({
Expand Down
6 changes: 3 additions & 3 deletions tests/unit/adapters/rest-adapter/ajax-test.js
Expand Up @@ -35,8 +35,8 @@ test("When an id is searched, the correct url should be generated", function(ass
return Ember.RSVP.resolve();
};
run(function() {
adapter.findRecord(store, Person, 1);
adapter.findRecord(store, Place, 1);
adapter.findRecord(store, Person, 1, {});
adapter.findRecord(store, Place, 1, {});
});
});

Expand All @@ -47,7 +47,7 @@ test("id's should be sanatized", function(assert) {
return Ember.RSVP.resolve();
};
run(function() {
adapter.findRecord(store, Person, '../place/1');
adapter.findRecord(store, Person, '../place/1', {});
});
});

Expand Down

0 comments on commit 97193d0

Please sign in to comment.