Skip to content

Commit

Permalink
Fix bug #1795 Count issues related models
Browse files Browse the repository at this point in the history
  • Loading branch information
regevbr committed Nov 30, 2019
1 parent 770f11b commit b693a57
Show file tree
Hide file tree
Showing 2 changed files with 311 additions and 9 deletions.
194 changes: 185 additions & 9 deletions lib/scope.js
Original file line number Diff line number Diff line change
Expand Up @@ -431,11 +431,55 @@ function defineScope(cls, targetClass, name, params, methods, options) {
options = {};
}
options = options || {};

const targetModel = definition.targetModel(this._receiver);
// If there is a through model
// run another query to apply filter on relatedModel(targetModel)
// see github.com/strongloop/loopback-datasource-juggler/issues/1795
let scopeOnRelatedModel = false;
let queryRelated;
let keyFrom;
let relatedModel;
let IdKey;
let fieldsRelated;
if (this._scope && this._scope.collect &&
where !== null && typeof where === 'object') {
queryRelated = {
relation: this._scope.collect,
scope: {
where: where,
},
};
where = {};
scopeOnRelatedModel = true;
relatedModel = targetModel.relations[queryRelated.relation].modelTo;
IdKey = idName(relatedModel);
keyFrom = targetModel.relations[queryRelated.relation].keyFrom || IdKey;
fieldsRelated = [keyFrom];
}
const scoped = (this._scope && this._scope.where) || {};
const filter = mergeQuery({where: scoped}, {where: where || {}});
return targetModel.destroyAll(filter.where, options, cb);
const filter = mergeQuery({where: scoped}, {where: where || {}, fields: fieldsRelated});
if (!scopeOnRelatedModel) {
return targetModel.destroyAll(filter.where, options, cb);
}
return targetModel.find(filter, options, function(err, findData) {
// Merge queryRelated filter and targetId filter
const buildWhere = function() {
return {
and: [
{
[IdKey]: collectTargetIds(findData, keyFrom),
},
queryRelated.scope.where],
};
};
if (queryRelated.scope.where !== undefined) {
queryRelated.scope.where = buildWhere();
} else {
queryRelated.scope.where = {};
queryRelated.scope.where[IdKey] = collectTargetIds(findData, keyFrom);
}
return relatedModel.destroyAll(queryRelated.scope.where, options, cb);
});
}

function updateAll(where, data, options, cb) {
Expand All @@ -453,9 +497,54 @@ function defineScope(cls, targetClass, name, params, methods, options) {
}
options = options || {};
const targetModel = definition.targetModel(this._receiver);
// If there is a through model
// run another query to apply filter on relatedModel(targetModel)
// see github.com/strongloop/loopback-datasource-juggler/issues/1795
let scopeOnRelatedModel = false;
let queryRelated;
let keyFrom;
let relatedModel;
let IdKey;
let fieldsRelated;
if (this._scope && this._scope.collect &&
where !== null && typeof where === 'object') {
queryRelated = {
relation: this._scope.collect,
scope: {
where: where,
},
};
where = {};
scopeOnRelatedModel = true;
relatedModel = targetModel.relations[queryRelated.relation].modelTo;
IdKey = idName(relatedModel);
keyFrom = targetModel.relations[queryRelated.relation].keyFrom || IdKey;
fieldsRelated = [keyFrom];
}
const scoped = (this._scope && this._scope.where) || {};
const filter = mergeQuery({where: scoped}, {where: where || {}});
return targetModel.updateAll(filter.where, data, options, cb);
const filter = mergeQuery({where: scoped}, {where: where || {}, fields: fieldsRelated});
if (!scopeOnRelatedModel) {
return targetModel.updateAll(filter.where, data, options, cb);
}
return targetModel.find(filter, options, function(err, findData) {
// Merge queryRelated filter and targetId filter
const buildWhere = function() {
return {
and: [
{
[IdKey]: collectTargetIds(findData, keyFrom),
},
queryRelated.scope.where],
};
};
if (queryRelated.scope.where !== undefined) {
queryRelated.scope.where = buildWhere();
} else {
queryRelated.scope.where = {};
queryRelated.scope.where[IdKey] = collectTargetIds(findData, keyFrom);
}
return relatedModel.updateAll(queryRelated.scope.where, data, options, cb);
});
}

function findById(id, filter, options, cb) {
Expand Down Expand Up @@ -501,9 +590,52 @@ function defineScope(cls, targetClass, name, params, methods, options) {
}
options = options || {};
const targetModel = definition.targetModel(this._receiver);
// If there is a through model
// run another query to apply filter on relatedModel(targetModel)
// see github.com/strongloop/loopback-datasource-juggler/issues/1795
let scopeOnRelatedModel = false;
let queryRelated;
let keyFrom;
let relatedModel;
let IdKey;
if (this._scope && this._scope.collect &&
filter && filter.where !== null && typeof filter.where === 'object') {
queryRelated = {
relation: this._scope.collect,
scope: filter,
};
scopeOnRelatedModel = true;
relatedModel = targetModel.relations[queryRelated.relation].modelTo;
IdKey = idName(relatedModel);
keyFrom = targetModel.relations[queryRelated.relation].keyFrom || IdKey;
filter = {
fields: [keyFrom],
};
}
const scoped = (this._scope && this._scope.where) || {};
filter = mergeQuery({where: scoped}, filter || {});
return targetModel.findOne(filter, options, cb);
if (!scopeOnRelatedModel) {
return targetModel.findOne(filter, options, cb);
}
return targetModel.find(filter, options, function(err, data) {
// Merge queryRelated filter and targetId filter
const buildWhere = function() {
return {
and: [
{
[IdKey]: collectTargetIds(data, keyFrom),
},
queryRelated.scope.where],
};
};
if (queryRelated.scope.where !== undefined) {
queryRelated.scope.where = buildWhere();
} else {
queryRelated.scope.where = {};
queryRelated.scope.where[IdKey] = collectTargetIds(data, keyFrom);
}
return relatedModel.findOne(queryRelated.scope, options, cb);
});
}

function count(where, options, cb) {
Expand All @@ -517,11 +649,55 @@ function defineScope(cls, targetClass, name, params, methods, options) {
options = {};
}
options = options || {};

const targetModel = definition.targetModel(this._receiver);
// If there is a through model
// run another query to apply filter on relatedModel(targetModel)
// see github.com/strongloop/loopback-datasource-juggler/issues/1795
let scopeOnRelatedModel = false;
let queryRelated;
let keyFrom;
let relatedModel;
let IdKey;
let fieldsRelated;
if (this._scope && this._scope.collect &&
where !== null && typeof where === 'object') {
queryRelated = {
relation: this._scope.collect,
scope: {
where: where,
},
};
where = {};
scopeOnRelatedModel = true;
relatedModel = targetModel.relations[queryRelated.relation].modelTo;
IdKey = idName(relatedModel);
keyFrom = targetModel.relations[queryRelated.relation].keyFrom || IdKey;
fieldsRelated = [keyFrom];
}
const scoped = (this._scope && this._scope.where) || {};
const filter = mergeQuery({where: scoped}, {where: where || {}});
return targetModel.count(filter.where, options, cb);
const filter = mergeQuery({where: scoped}, {where: where || {}, fields: fieldsRelated});
if (!scopeOnRelatedModel) {
return targetModel.count(filter.where, options, cb);
}
return targetModel.find(filter, options, function(err, data) {
// Merge queryRelated filter and targetId filter
const buildWhere = function() {
return {
and: [
{
[IdKey]: collectTargetIds(data, keyFrom),
},
queryRelated.scope.where],
};
};
if (queryRelated.scope.where !== undefined) {
queryRelated.scope.where = buildWhere();
} else {
queryRelated.scope.where = {};
queryRelated.scope.where[IdKey] = collectTargetIds(data, keyFrom);
}
return relatedModel.count(queryRelated.scope.where, options, cb);
});
}

return definition;
Expand Down
126 changes: 126 additions & 0 deletions test/relations.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,132 @@ describe('relations', function() {
db.automigrate(['Physician', 'Patient', 'Appointment', 'Address'], done);
});

it('should count scoped record with promises based on related model properties', function(done) {
let id;
Physician.create()
.then(function(physician) {
return physician.patients.create({name: 'a'})
.then(function(ch) {
id = ch.id;
return physician.patients.create({name: 'z'});
})
.then(function() {
return physician.patients.create({name: 'c'});
})
.then(function() {
return verify(physician);
});
}).catch(done);

function verify(physician) {
return physician.patients.count({
name: 'a',
}, function(err, count) {
if (err) return done(err);
count.should.equal(1);
done();
});
}
});

it('should find one scoped record with promises based on related model properties', function(done) {
let id;
Physician.create()
.then(function(physician) {
return physician.patients.create({name: 'a'})
.then(function(ch) {
id = ch.id;
return physician.patients.create({name: 'z'});
})
.then(function() {
return physician.patients.create({name: 'c'});
})
.then(function() {
return verify(physician);
});
}).catch(done);

function verify(physician) {
return physician.patients.findOne({
where: {
name: 'a',
},
}, function(err, patient) {
if (err) return done(err);
should.exist(patient);
patient.name.should.equal('a');
done();
});
}
});

it('should update all scoped record with promises based on related model properties', function(done) {
let id;
Physician.create()
.then(function(physician) {
return physician.patients.create({name: 'a'})
.then(function(ch) {
id = ch.id;
return physician.patients.create({name: 'z'});
})
.then(function() {
return physician.patients.create({name: 'c'});
})
.then(function() {
return verify(physician);
});
}).catch(done);

function verify(physician) {
return physician.patients.updateAll({
name: 'a',
}, {age: 5}, function(err, result) {
if (err) return done(err);
should.exist(result);
result.count.should.equal(1);
physician.patients.findOne({
where: {
name: 'a',
},
}, function(err, patient) {
if (err) return done(err);
should.exist(patient);
patient.age.should.equal(5);
done();
});
});
}
});

it('should destroyAll all scoped record with promises based on related model properties', function(done) {
let id;
Physician.create()
.then(function(physician) {
return physician.patients.create({name: 'a'})
.then(function(ch) {
id = ch.id;
return physician.patients.create({name: 'z'});
})
.then(function() {
return physician.patients.create({name: 'c'});
})
.then(function() {
return verify(physician);
});
}).catch(done);

function verify(physician) {
return physician.patients.destroyAll({
name: 'a',
}, function(err, result) {
if (err) return done(err);
should.exist(result);
result.count.should.equal(1);
done();
});
}
});

it('should build record on scope', function(done) {
Physician.create(function(err, physician) {
const patient = physician.patients.build();
Expand Down

0 comments on commit b693a57

Please sign in to comment.