Skip to content

Commit

Permalink
Defer serializer loading
Browse files Browse the repository at this point in the history
There's no need to load serializers if and until the adapter fetch
promise resolves.
  • Loading branch information
hjdivad committed Mar 21, 2017
1 parent 8e600fe commit a9f30ef
Show file tree
Hide file tree
Showing 2 changed files with 294 additions and 9 deletions.
18 changes: 9 additions & 9 deletions addon/-private/system/store/finders.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ export function _find(adapter, store, modelClass, id, internalModel, options) {
let snapshot = internalModel.createSnapshot(options);
let { modelName } = internalModel;
let promise = adapter.findRecord(store, modelClass, id, snapshot);
let serializer = serializerForAdapter(store, adapter, modelName);
let label = `DS: Handle Adapter#findRecord of '${modelName}' with id: '${id}'`;

promise = Promise.resolve(promise, label);
promise = _guard(promise, _bind(_objectIsAlive, store));

return promise.then(adapterPayload => {
assert(`You made a 'findRecord' request for a '${modelName}' with id '${id}', but the adapter's response did not have any data`, payloadIsNotBlank(adapterPayload));
let serializer = serializerForAdapter(store, adapter, modelName);
let payload = normalizeResponseHelper(serializer, store, modelClass, adapterPayload, id, 'findRecord');
assert(`Ember Data expected the primary data returned from a 'findRecord' response to be an object but instead it found an array.`, !Array.isArray(payload.data));

Expand All @@ -58,7 +58,6 @@ export function _findMany(adapter, store, modelName, ids, internalModels) {
let snapshots = Ember.A(internalModels).invoke('createSnapshot');
let modelClass = store.modelFor(modelName); // `adapter.findMany` gets the modelClass still
let promise = adapter.findMany(store, modelClass, ids, snapshots);
let serializer = serializerForAdapter(store, adapter, modelName);
let label = `DS: Handle Adapter#findMany of '${modelName}'`;

if (promise === undefined) {
Expand All @@ -70,6 +69,7 @@ export function _findMany(adapter, store, modelName, ids, internalModels) {

return promise.then(adapterPayload => {
assert(`You made a 'findMany' request for '${modelName}' records with ids '[${ids}]', but the adapter's response did not have any data`, payloadIsNotBlank(adapterPayload));
let serializer = serializerForAdapter(store, adapter, modelName);
let payload = normalizeResponseHelper(serializer, store, modelClass, adapterPayload, null, 'findMany');
return store._push(payload);
}, null, `DS: Extract payload of ${modelName}`);
Expand All @@ -79,7 +79,6 @@ export function _findHasMany(adapter, store, internalModel, link, relationship)
let snapshot = internalModel.createSnapshot();
let modelClass = store.modelFor(relationship.type);
let promise = adapter.findHasMany(store, snapshot, link, relationship);
let serializer = serializerForAdapter(store, adapter, relationship.type);
let label = `DS: Handle Adapter#findHasMany of '${internalModel.modelName}' : '${relationship.type}'`;

promise = Promise.resolve(promise, label);
Expand All @@ -88,6 +87,7 @@ export function _findHasMany(adapter, store, internalModel, link, relationship)

return promise.then(adapterPayload => {
assert(`You made a 'findHasMany' request for a ${internalModel.modelName}'s '${relationship.key}' relationship, using link '${link}' , but the adapter's response did not have any data`, payloadIsNotBlank(adapterPayload));
let serializer = serializerForAdapter(store, adapter, relationship.type);
let payload = normalizeResponseHelper(serializer, store, modelClass, adapterPayload, null, 'findHasMany');
let internalModelArray = store._push(payload);

Expand All @@ -100,14 +100,14 @@ export function _findBelongsTo(adapter, store, internalModel, link, relationship
let snapshot = internalModel.createSnapshot();
let modelClass = store.modelFor(relationship.type);
let promise = adapter.findBelongsTo(store, snapshot, link, relationship);
let serializer = serializerForAdapter(store, adapter, relationship.type);
let label = `DS: Handle Adapter#findBelongsTo of ${internalModel.modelName} : ${relationship.type}`;

promise = Promise.resolve(promise, label);
promise = _guard(promise, _bind(_objectIsAlive, store));
promise = _guard(promise, _bind(_objectIsAlive, internalModel));

return promise.then(adapterPayload => {
let serializer = serializerForAdapter(store, adapter, relationship.type);
let payload = normalizeResponseHelper(serializer, store, modelClass, adapterPayload, null, 'findBelongsTo');

if (!payload.data) {
Expand All @@ -123,14 +123,14 @@ export function _findAll(adapter, store, modelName, sinceToken, options) {
let recordArray = store.peekAll(modelName);
let snapshotArray = recordArray._createSnapshot(options);
let promise = adapter.findAll(store, modelClass, sinceToken, snapshotArray);
let serializer = serializerForAdapter(store, adapter, modelName);
let label = "DS: Handle Adapter#findAll of " + modelClass;

promise = Promise.resolve(promise, label);
promise = _guard(promise, _bind(_objectIsAlive, store));

return promise.then(adapterPayload => {
assert(`You made a 'findAll' request for '${modelName}' records, but the adapter's response did not have any data`, payloadIsNotBlank(adapterPayload));
let serializer = serializerForAdapter(store, adapter, modelName);
let payload = normalizeResponseHelper(serializer, store, modelClass, adapterPayload, null, 'findAll');

store._push(payload);
Expand All @@ -144,15 +144,15 @@ export function _query(adapter, store, modelName, query, recordArray) {
let modelClass = store.modelFor(modelName); // adapter.query needs the class
let promise = adapter.query(store, modelClass, query, recordArray);

let serializerToken = heimdall.start('initial-serializerFor-lookup');
let serializer = serializerForAdapter(store, adapter, modelName);
heimdall.stop(serializerToken);
let label = `DS: Handle Adapter#query of ${modelClass}`;

promise = Promise.resolve(promise, label);
promise = _guard(promise, _bind(_objectIsAlive, store));

return promise.then(adapterPayload => {
let serializerToken = heimdall.start('initial-serializerFor-lookup');
let serializer = serializerForAdapter(store, adapter, modelName);
heimdall.stop(serializerToken);
let normalizeToken = heimdall.start('finders#_query::normalizeResponseHelper');
let payload = normalizeResponseHelper(serializer, store, modelClass, adapterPayload, null, 'query');
heimdall.stop(normalizeToken);
Expand All @@ -168,13 +168,13 @@ export function _query(adapter, store, modelName, query, recordArray) {
export function _queryRecord(adapter, store, modelName, query) {
let modelClass = store.modelFor(modelName); // adapter.queryRecord needs the class
let promise = adapter.queryRecord(store, modelClass, query);
let serializer = serializerForAdapter(store, adapter, modelName);
let label = `DS: Handle Adapter#queryRecord of ${modelName}`;

promise = Promise.resolve(promise, label);
promise = _guard(promise, _bind(_objectIsAlive, store));

return promise.then(adapterPayload => {
let serializer = serializerForAdapter(store, adapter, modelName);
let payload = normalizeResponseHelper(serializer, store, modelClass, adapterPayload, null, 'queryRecord');

assert(`Expected the primary data returned by the serializer for a 'queryRecord' response to be a single object or null but instead it was an array.`, !Array.isArray(payload.data), {
Expand Down
285 changes: 285 additions & 0 deletions tests/unit/store/finders-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,285 @@
import setupStore from 'dummy/tests/helpers/store';
import Ember from 'ember';

import {module, test} from 'qunit';

const { run } = Ember;
const { defer } = Ember.RSVP;

import DS from 'ember-data';

module('unit/store/finders', {
beforeEach() {
this.Person = DS.Model.extend({
updatedAt: DS.attr('string'),
name: DS.attr('string'),
firstName: DS.attr('string'),
lastName: DS.attr('string')
});

this.Dog = DS.Model.extend({
name: DS.attr('string')
});

this.env = setupStore({ person: this.Person, dog: this.Dog });
this.store = this.env.store;
this.adapter = this.env.adapter;
},

afterEach() {
run(this.env.container, 'destroy');
}
});

test('findRecord does not load a serializer until the adapter promise resolves', function(assert) {
assert.expect(2);

let deferedFind = defer();

this.env.registry.register('adapter:person', DS.Adapter.extend({
findRecord: () => deferedFind.promise
}));

let serializerLoaded = false;
let serializerFor = this.store.serializerFor;
this.store.serializerFor = (modelName) => {
if (modelName === 'person') {
serializerLoaded = true;
}
return serializerFor.call(this.store, modelName);
};

let storePromise = run(() => this.store.findRecord('person', 1));
assert.equal(false, serializerLoaded, 'serializer is not eagerly loaded');

return run(() => {
deferedFind.resolve({ id: 1, name: 'John Churchill' });
return storePromise.then(() => {
assert.equal(true, serializerLoaded, 'serializer is loaded');
});
});
});

test('findMany does not load a serializer until the adapter promise resolves', function(assert) {
assert.expect(2);

let deferedFind = defer();

this.env.registry.register('adapter:person', DS.Adapter.extend({
findMany: () => deferedFind.promise
}));

let serializerLoaded = false;
let serializerFor = this.store.serializerFor;
this.store.serializerFor = (modelName) => {
if (modelName === 'person') {
serializerLoaded = true;
}
return serializerFor.call(this.store, modelName);
};

let storePromise = run(() => {
this.store.findRecord('person', 1)
return this.store.findRecord('person', 2);
});
assert.equal(false, serializerLoaded, 'serializer is not eagerly loaded');

return run(() => {
deferedFind.resolve([{ id: 1, name: 'John Churchill' }, { id: 2, name: 'Louis Joseph' }]);
return storePromise.then(() => {
assert.equal(true, serializerLoaded, 'serializer is loaded');
});
});
});

test('findHasMany does not load a serializer until the adapter promise resolves', function(assert) {
assert.expect(2);

let deferedFind = defer();

this.env.registry.register('adapter:person', DS.Adapter.extend({
findHasMany: () => deferedFind.promise
}));

this.Person.reopen({
dogs: DS.hasMany('dog', { async: true })
});

let serializerLoaded = false;
let serializerFor = this.store.serializerFor;
this.store.serializerFor = (modelName) => {
if (modelName === 'dog') {
serializerLoaded = true;
}
return serializerFor.call(this.store, modelName);
};

let storePromise = run(() => {
this.env.store.push({
data: {
type: 'person',
id: '1',
attributes: {
name: 'John Churchill'
},
relationships: {
dogs: {
links: {
related: 'http://exmaple.com/person/1/dogs'
}
}
}
}
});

return this.store.peekRecord('person', 1).get('dogs');
});
assert.equal(false, serializerLoaded, 'serializer is not eagerly loaded');

return run(() => {
deferedFind.resolve([{ id: 1, name: 'Scooby' }, { id: 2, name: 'Scrappy' }]);
return storePromise.then(() => {
assert.equal(true, serializerLoaded, 'serializer is loaded');
});
});
});

test('findBelongsTo does not load a serializer until the adapter promise resolves', function(assert) {
assert.expect(2);

let deferedFind = defer();

this.env.registry.register('adapter:person', DS.Adapter.extend({
findBelongsTo: () => deferedFind.promise
}));

this.Person.reopen({
favoriteDog: DS.belongsTo('dog', { async: true })
});

let serializerLoaded = false;
let serializerFor = this.store.serializerFor;
this.store.serializerFor = (modelName) => {
if (modelName === 'dog') {
serializerLoaded = true;
}
return serializerFor.call(this.store, modelName);
};

let storePromise = run(() => {
this.env.store.push({
data: {
type: 'person',
id: '1',
attributes: {
name: 'John Churchill'
},
relationships: {
favoriteDog: {
links: {
related: 'http://exmaple.com/person/1/favorite-dog'
}
}
}
}
});

return this.store.peekRecord('person', 1).get('favoriteDog');
});
assert.equal(false, serializerLoaded, 'serializer is not eagerly loaded');

return run(() => {
deferedFind.resolve({ id: 1, name: 'Scooby' });
return storePromise.then(() => {
assert.equal(true, serializerLoaded, 'serializer is loaded');
});
});
});

test('findAll does not load a serializer until the adapter promise resolves', function(assert) {
assert.expect(2);

let deferedFind = defer();

this.env.registry.register('adapter:person', DS.Adapter.extend({
findAll: () => deferedFind.promise
}));

let serializerLoaded = false;
let serializerFor = this.store.serializerFor;
this.store.serializerFor = (modelName) => {
if (modelName === 'person') {
serializerLoaded = true;
}
return serializerFor.call(this.store, modelName);
};

let storePromise = run(() => this.store.findAll('person'));
assert.equal(false, serializerLoaded, 'serializer is not eagerly loaded');

return run(() => {
deferedFind.resolve([{ id: 1, name: 'John Churchill' }]);
return storePromise.then(() => {
assert.equal(true, serializerLoaded, 'serializer is loaded');
});
});
});

test('query does not load a serializer until the adapter promise resolves', function(assert) {
assert.expect(2);

let deferedFind = defer();

this.env.registry.register('adapter:person', DS.Adapter.extend({
query: () => deferedFind.promise
}));

let serializerLoaded = false;
let serializerFor = this.store.serializerFor;
this.store.serializerFor = (modelName) => {
if (modelName === 'person') {
serializerLoaded = true;
}
return serializerFor.call(this.store, modelName);
};

let storePromise = run(() => this.store.query('person', { first_duke_of_marlborough: true }));
assert.equal(false, serializerLoaded, 'serializer is not eagerly loaded');

return run(() => {
deferedFind.resolve([{ id: 1, name: 'John Churchill' }]);
return storePromise.then(() => {
assert.equal(true, serializerLoaded, 'serializer is loaded');
});
});
});

test('queryRecord does not load a serializer until the adapter promise resolves', function(assert) {
assert.expect(2);

let deferedFind = defer();

this.env.registry.register('adapter:person', DS.Adapter.extend({
queryRecord: () => deferedFind.promise
}));

let serializerLoaded = false;
let serializerFor = this.store.serializerFor;
this.store.serializerFor = (modelName) => {
if (modelName === 'person') {
serializerLoaded = true;
}
return serializerFor.call(this.store, modelName);
};

let storePromise = run(() => this.store.queryRecord('person', { first_duke_of_marlborough: true }));
assert.equal(false, serializerLoaded, 'serializer is not eagerly loaded');

return run(() => {
deferedFind.resolve({ id: 1, name: 'John Churchill' });
return storePromise.then(() => {
assert.equal(true, serializerLoaded, 'serializer is loaded');
});
});
});

0 comments on commit a9f30ef

Please sign in to comment.