Preserve related models from "include" filter

Before this change, when making a remote call with "include" filter
(for example `findById(11, {include:['children']})`), the related
models were removed from the result.

This commit fixes the implementation to correctly preserve related
models and also to cast them to correct model instances.
This commit is contained in:
Dimitris 2017-10-16 19:43:31 +03:00 committed by Miroslav Bajtoš
parent ffaaec8973
commit a3d110b78c
No known key found for this signature in database
GPG Key ID: 6F2304BA9361C7E3
3 changed files with 185 additions and 6 deletions

View File

@ -211,10 +211,23 @@ RelationMixin.embedsMany = function embedsMany(modelTo, params) {
function defineRelationProperty(modelClass, def) {
Object.defineProperty(modelClass.prototype, def.name, {
get: function() {
var that = this;
var scope = function() {
return that['__get__' + def.name].apply(that, arguments);
const that = this;
const scope = function() {
const cachedEntities = that.__cachedRelations &&
that.__cachedRelations[def.name];
if (arguments.length || !cachedEntities) {
return that['__get__' + def.name].apply(that, arguments);
}
// return the cached data
if (Array.isArray(cachedEntities)) {
return cachedEntities.map(data => new def.modelTo(data));
} else {
return new def.modelTo(cachedEntities);
}
};
scope.count = function() {
return that['__count__' + def.name].apply(that, arguments);
};

View File

@ -87,7 +87,17 @@ RemoteConnector.prototype.resolve = function(Model) {
// setup a remoting type converter for this model
remotes.defineObjectType(Model.modelName, function(data) {
return new Model(data);
const model = new Model(data);
// process cached relations
if (model.__cachedRelations) {
for (const relation in model.__cachedRelations) {
const relatedModel = model.__cachedRelations[relation];
model.__data[relation] = relatedModel;
}
}
return model;
});
};

View File

@ -11,7 +11,8 @@ const loopback = require('loopback');
const TaskEmitter = require('strong-task-emitter');
describe('Remote model tests', function() {
let serverApp, ServerModel, clientApp, ClientModel;
let serverApp, ServerModel, ServerRelatedModel, ServerModelWithSingleChild,
clientApp, ClientModel, ClientRelatedModel, ClientModelWithSingleChild;
beforeEach(function setupServer(done) {
const app = serverApp = helper.createRestAppAndListen();
@ -19,10 +20,46 @@ describe('Remote model tests', function() {
ServerModel = app.registry.createModel({
name: 'TestModel',
properties: helper.userProperties,
properties: helper.getUserProperties(),
options: {
forceId: false,
relations: {
children: {
type: 'hasMany',
model: 'ChildModel',
foreignKey: 'parentId',
},
},
},
});
ServerModelWithSingleChild = app.registry.createModel({
name: 'TestModelWithSingleChild',
properties: helper.getUserProperties(),
options: {
forceId: false,
relations: {
child: {
type: 'hasOne',
model: 'ChildModel',
foreignKey: 'parentId',
},
},
},
});
ServerRelatedModel = app.registry.createModel({
name: 'ChildModel',
properties: {
note: {type: 'text'},
parentId: {type: 'number'},
},
options: {forceId: false},
});
app.model(ServerModel, {dataSource: db});
app.model(ServerRelatedModel, {dataSource: db});
app.model(ServerModelWithSingleChild, {dataSource: db});
serverApp.locals.handler.on('listening', function() { done(); });
});
@ -31,15 +68,56 @@ describe('Remote model tests', function() {
const app = clientApp = loopback({localRegistry: true});
const remoteDs = helper.createRemoteDataSource(clientApp, serverApp);
ClientRelatedModel = app.registry.createModel({
name: 'ChildModel',
properties: {
note: {type: 'text'},
parentId: {type: 'number'},
},
options: {
strict: true,
},
});
ClientModel = app.registry.createModel({
name: 'TestModel',
properties: helper.getUserProperties(),
options: {
relations: {
children: {
type: 'hasMany',
model: 'ChildModel',
foreignKey: 'parentId',
},
},
strict: true,
},
});
ClientModelWithSingleChild = app.registry.createModel({
name: 'TestModelWithSingleChild',
properties: helper.getUserProperties(),
options: {
relations: {
child: {
type: 'hasOne',
model: 'ChildModel',
foreignKey: 'parentId',
},
},
strict: true,
},
});
app.model(ClientModel, {dataSource: remoteDs});
app.model(ClientRelatedModel, {dataSource: remoteDs});
app.model(ClientModelWithSingleChild, {dataSource: remoteDs});
});
afterEach(function() {
serverApp.locals.handler.close();
ServerModel = null;
ServerRelatedModel = null;
ClientModel = null;
});
@ -160,4 +238,82 @@ describe('Remote model tests', function() {
});
});
});
describe('Model find with include filter', function() {
let hasManyParent, hasManyChild, hasOneParent, hasOneChild;
beforeEach(givenSampleData);
it('should return also the included requested models', function() {
const parentId = hasManyParent.id;
return ClientModel.findById(hasManyParent.id, {include: 'children'})
.then(returnedUser => {
assert(returnedUser instanceof ClientModel);
const user = returnedUser.toJSON();
assert.equal(user.id, parentId);
assert.equal(user.first, hasManyParent.first);
assert(Array.isArray(user.children));
assert.equal(user.children.length, 1);
assert.deepEqual(user.children[0], hasManyChild.toJSON());
});
});
it('should return cachedRelated entity without call', function() {
const parentId = hasManyParent.id;
return ClientModel.findById(parentId, {include: 'children'})
.then(returnedUser => {
assert(returnedUser instanceof ClientModel);
const children = returnedUser.children();
assert.equal(returnedUser.id, parentId);
assert.equal(returnedUser.first, hasManyParent.first);
assert(Array.isArray(children));
assert.equal(children.length, 1);
assert(children[0] instanceof ClientRelatedModel);
assert.deepEqual(children[0].toJSON(), hasManyChild.toJSON());
});
});
it('should also work for single (non array) relations', function() {
const parentId = hasOneParent.id;
return ClientModelWithSingleChild.findById(parentId, {include: 'child'})
.then(returnedUser => {
assert(returnedUser instanceof ClientModelWithSingleChild);
const child = returnedUser.child();
assert.equal(returnedUser.id, parentId);
assert.equal(returnedUser.first, hasOneParent.first);
assert(child instanceof ClientRelatedModel);
assert.deepEqual(child.toJSON(), hasOneChild.toJSON());
});
});
function givenSampleData() {
return ServerModel.create({first: 'eiste', last: 'kopries'})
.then(parent => {
hasManyParent = parent;
return ServerRelatedModel.create({
note: 'mitsos',
parentId: parent.id,
id: 11,
});
})
.then(child => {
hasManyChild = child;
return ServerModelWithSingleChild.create({
first: 'mipos',
last: 'tora',
id: 12,
});
})
.then(parent => {
hasOneParent = parent;
return ServerRelatedModel.create({
note: 'mitsos3',
parentId: parent.id,
id: 13,
});
})
.then(child => {
hasOneChild = child;
});
}
});
});