Skip to content

Commit

Permalink
Merge pull request #4884 from emberjs/hjdivad/defer-class-loading
Browse files Browse the repository at this point in the history
Defer serializer loading
  • Loading branch information
bmac authored Mar 31, 2017
2 parents 357cd99 + a9f30ef commit de59092
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 de59092

Please sign in to comment.