Skip to content

Commit 7c98ecb

Browse files
committed
Fix bug loopbackio#1795 Count issues related models
1 parent d19e189 commit 7c98ecb

File tree

2 files changed

+654
-54
lines changed

2 files changed

+654
-54
lines changed

lib/scope.js

+236-54
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ ScopeDefinition.prototype.related = function(receiver, scopeParams, condOrRefres
9191
actualRefresh;
9292
if (refreshIsNeeded) {
9393
// It either doesn't hit the cache or refresh is required
94-
const params = mergeQuery(actualCond, scopeParams, {nestedInclude: true});
94+
let params = mergeQuery(actualCond, scopeParams, {nestedInclude: true});
9595
const targetModel = this.targetModel(receiver);
9696

9797
// If there is a through model
@@ -100,64 +100,39 @@ ScopeDefinition.prototype.related = function(receiver, scopeParams, condOrRefres
100100
const scopeOnRelatedModel = params.collect &&
101101
params.include.scope !== null &&
102102
typeof params.include.scope === 'object';
103-
let filter, queryRelated;
103+
let filter, queryRelated, keyFrom, relatedModel, IdKey;
104104
if (scopeOnRelatedModel) {
105105
filter = params.include;
106106
// The filter applied on relatedModel
107107
queryRelated = filter.scope;
108-
delete params.include.scope;
108+
delete params.include;
109+
relatedModel = targetModel.relations[filter.relation].modelTo;
110+
IdKey = idName(relatedModel);
111+
keyFrom = targetModel.relations[filter.relation].keyFrom || IdKey;
112+
params.fields = [keyFrom];
113+
if (queryRelated && queryRelated.where && queryRelated.where[IdKey]) {
114+
params = mergeQuery(params, {
115+
where: {
116+
[keyFrom]: queryRelated.where [IdKey],
117+
},
118+
});
119+
}
109120
}
110121

111-
targetModel.find(params, options, function(err, data) {
122+
targetModel.find(params, options, function(err, findData) {
112123
if (!err && saveOnCache) {
113124
defineCachedRelations(self);
114-
self.__cachedRelations[name] = data;
125+
self.__cachedRelations[name] = findData;
115126
}
116127

117128
if (scopeOnRelatedModel === true) {
118-
const relatedModel = targetModel.relations[filter.relation].modelTo;
119-
const IdKey = idName(relatedModel);
120-
121-
// return {inq: [1,2,3]}}
122-
const smartMerge = function(idCollection, qWhere) {
123-
if (!qWhere[IdKey]) return idCollection;
124-
let merged = {};
125-
126-
const idsA = idCollection.inq;
127-
const idsB = qWhere[IdKey].inq ? qWhere[IdKey].inq : [qWhere[IdKey]];
128-
129-
const intersect = _.intersectionWith(idsA, idsB, _.isEqual);
130-
if (intersect.length === 1) merged = intersect[0];
131-
if (intersect.length > 1) merged = {inq: intersect};
132-
133-
return merged;
134-
};
135-
136-
if (queryRelated.where !== undefined) {
137-
// Merge queryRelated filter and targetId filter
138-
const IdKeyCondition = {};
139-
IdKeyCondition[IdKey] = smartMerge(collectTargetIds(data, IdKey),
140-
queryRelated.where);
141-
142-
// if the id in filter doesn't exist after the merge,
143-
// return empty result
144-
if (_.isObject(IdKeyCondition[IdKey]) && _.isEmpty(IdKeyCondition[IdKey])) return cb(null, []);
145-
146-
const mergedWhere = {
147-
and: [
148-
IdKeyCondition,
149-
_.omit(queryRelated.where, IdKey),
150-
],
151-
};
152-
queryRelated.where = mergedWhere;
153-
} else {
154-
queryRelated.where = {};
155-
queryRelated.where[IdKey] = collectTargetIds(data, IdKey);
129+
const smartMergeSuccessful = smartMergeRelatedModelQuery(findData, queryRelated, keyFrom, IdKey);
130+
if (!smartMergeSuccessful) {
131+
return cb(null, []);
156132
}
157-
158133
relatedModel.find(queryRelated, options, cb);
159134
} else {
160-
cb(err, data);
135+
cb(err, findData);
161136
}
162137
});
163138
} else {
@@ -298,6 +273,16 @@ function defineScope(cls, targetClass, name, params, methods, options) {
298273
options = {};
299274
}
300275
options = options || {};
276+
// Check if there is a through model
277+
// see https://github.com/strongloop/loopback/issues/1076
278+
if (f._scope.collect &&
279+
condOrRefresh !== null && typeof condOrRefresh === 'object') {
280+
f._scope.include = {
281+
relation: f._scope.collect,
282+
scope: condOrRefresh,
283+
};
284+
condOrRefresh = {};
285+
}
301286
return definition.related(self, f._scope, condOrRefresh, options, cb);
302287
};
303288

@@ -431,11 +416,49 @@ function defineScope(cls, targetClass, name, params, methods, options) {
431416
options = {};
432417
}
433418
options = options || {};
434-
419+
cb = cb || utils.createPromiseCallback();
435420
const targetModel = definition.targetModel(this._receiver);
421+
// If there is a through model
422+
// run another query to apply filter on relatedModel(targetModel)
423+
// see github.com/strongloop/loopback-datasource-juggler/issues/1795
424+
let scopeOnRelatedModel = false;
425+
let queryRelated, keyFrom, relatedModel, IdKey, fieldsRelated;
426+
if (this._scope && this._scope.collect &&
427+
where !== null && typeof where === 'object') {
428+
queryRelated = {
429+
relation: this._scope.collect,
430+
scope: {
431+
where: where,
432+
},
433+
};
434+
scopeOnRelatedModel = true;
435+
relatedModel = targetModel.relations[queryRelated.relation].modelTo;
436+
IdKey = idName(relatedModel);
437+
keyFrom = targetModel.relations[queryRelated.relation].keyFrom || IdKey;
438+
fieldsRelated = [keyFrom];
439+
if (where[IdKey]) {
440+
where = {
441+
[keyFrom]: where[IdKey],
442+
};
443+
} else {
444+
where = {};
445+
}
446+
}
436447
const scoped = (this._scope && this._scope.where) || {};
437-
const filter = mergeQuery({where: scoped}, {where: where || {}});
438-
return targetModel.destroyAll(filter.where, options, cb);
448+
const filter = mergeQuery({where: scoped}, {where: where || {}, fields: fieldsRelated});
449+
if (!scopeOnRelatedModel) {
450+
targetModel.destroyAll(filter.where, options, cb);
451+
} else {
452+
targetModel.find(filter, options, function(err, findData) {
453+
const smartMergeSuccessful =
454+
smartMergeRelatedModelQuery(findData, queryRelated.scope, keyFrom, IdKey);
455+
if (!smartMergeSuccessful) {
456+
return cb(null, {count: 0});
457+
}
458+
return relatedModel.destroyAll(queryRelated.scope.where, options, cb);
459+
});
460+
}
461+
return cb.promise;
439462
}
440463

441464
function updateAll(where, data, options, cb) {
@@ -452,10 +475,49 @@ function defineScope(cls, targetClass, name, params, methods, options) {
452475
options = {};
453476
}
454477
options = options || {};
478+
cb = cb || utils.createPromiseCallback();
455479
const targetModel = definition.targetModel(this._receiver);
480+
// If there is a through model
481+
// run another query to apply filter on relatedModel(targetModel)
482+
// see github.com/strongloop/loopback-datasource-juggler/issues/1795
483+
let scopeOnRelatedModel = false;
484+
let queryRelated, keyFrom, relatedModel, IdKey, fieldsRelated;
485+
if (this._scope && this._scope.collect &&
486+
where !== null && typeof where === 'object') {
487+
queryRelated = {
488+
relation: this._scope.collect,
489+
scope: {
490+
where: where,
491+
},
492+
};
493+
scopeOnRelatedModel = true;
494+
relatedModel = targetModel.relations[queryRelated.relation].modelTo;
495+
IdKey = idName(relatedModel);
496+
keyFrom = targetModel.relations[queryRelated.relation].keyFrom || IdKey;
497+
fieldsRelated = [keyFrom];
498+
if (where[IdKey]) {
499+
where = {
500+
[keyFrom]: where[IdKey],
501+
};
502+
} else {
503+
where = {};
504+
}
505+
}
456506
const scoped = (this._scope && this._scope.where) || {};
457-
const filter = mergeQuery({where: scoped}, {where: where || {}});
458-
return targetModel.updateAll(filter.where, data, options, cb);
507+
const filter = mergeQuery({where: scoped}, {where: where || {}, fields: fieldsRelated});
508+
if (!scopeOnRelatedModel) {
509+
targetModel.updateAll(filter.where, data, options, cb);
510+
} else {
511+
targetModel.find(filter, options, function(err, findData) {
512+
const smartMergeSuccessful =
513+
smartMergeRelatedModelQuery(findData, queryRelated.scope, keyFrom, IdKey);
514+
if (!smartMergeSuccessful) {
515+
return cb(null, {count: 0});
516+
}
517+
return relatedModel.updateAll(queryRelated.scope.where, data, options, cb);
518+
});
519+
}
520+
return cb.promise;
459521
}
460522

461523
function findById(id, filter, options, cb) {
@@ -500,10 +562,51 @@ function defineScope(cls, targetClass, name, params, methods, options) {
500562
options = {};
501563
}
502564
options = options || {};
565+
cb = cb || utils.createPromiseCallback();
503566
const targetModel = definition.targetModel(this._receiver);
567+
// If there is a through model
568+
// run another query to apply filter on relatedModel(targetModel)
569+
// see github.com/strongloop/loopback-datasource-juggler/issues/1795
570+
let scopeOnRelatedModel = false;
571+
let queryRelated, keyFrom, relatedModel, IdKey;
572+
if (this._scope && this._scope.collect &&
573+
filter && filter.where !== null && typeof filter.where === 'object') {
574+
queryRelated = {
575+
relation: this._scope.collect,
576+
scope: filter,
577+
};
578+
scopeOnRelatedModel = true;
579+
relatedModel = targetModel.relations[queryRelated.relation].modelTo;
580+
IdKey = idName(relatedModel);
581+
keyFrom = targetModel.relations[queryRelated.relation].keyFrom || IdKey;
582+
if (filter.where[IdKey]) {
583+
filter = {
584+
fields: [keyFrom],
585+
where: {
586+
[keyFrom]: filter.where [IdKey],
587+
},
588+
};
589+
} else {
590+
filter = {
591+
fields: [keyFrom],
592+
};
593+
}
594+
}
504595
const scoped = (this._scope && this._scope.where) || {};
505596
filter = mergeQuery({where: scoped}, filter || {});
506-
return targetModel.findOne(filter, options, cb);
597+
if (!scopeOnRelatedModel) {
598+
targetModel.findOne(filter, options, cb);
599+
} else {
600+
targetModel.find(filter, options, function(err, findData) {
601+
const smartMergeSuccessful =
602+
smartMergeRelatedModelQuery(findData, queryRelated.scope, keyFrom, IdKey);
603+
if (!smartMergeSuccessful) {
604+
return cb(null, null);
605+
}
606+
return relatedModel.findOne(queryRelated.scope, options, cb);
607+
});
608+
}
609+
return cb.promise;
507610
}
508611

509612
function count(where, options, cb) {
@@ -517,12 +620,91 @@ function defineScope(cls, targetClass, name, params, methods, options) {
517620
options = {};
518621
}
519622
options = options || {};
520-
623+
cb = cb || utils.createPromiseCallback();
521624
const targetModel = definition.targetModel(this._receiver);
625+
// If there is a through model
626+
// run another query to apply filter on relatedModel(targetModel)
627+
// see github.com/strongloop/loopback-datasource-juggler/issues/1795
628+
let scopeOnRelatedModel = false;
629+
let queryRelated, keyFrom, relatedModel, IdKey, fieldsRelated;
630+
if (this._scope && this._scope.collect &&
631+
where !== null && typeof where === 'object') {
632+
queryRelated = {
633+
relation: this._scope.collect,
634+
scope: {
635+
where: where,
636+
},
637+
};
638+
scopeOnRelatedModel = true;
639+
relatedModel = targetModel.relations[queryRelated.relation].modelTo;
640+
IdKey = idName(relatedModel);
641+
keyFrom = targetModel.relations[queryRelated.relation].keyFrom || IdKey;
642+
fieldsRelated = [keyFrom];
643+
if (where[IdKey]) {
644+
where = {
645+
[keyFrom]: where[IdKey],
646+
};
647+
} else {
648+
where = {};
649+
}
650+
}
522651
const scoped = (this._scope && this._scope.where) || {};
523-
const filter = mergeQuery({where: scoped}, {where: where || {}});
524-
return targetModel.count(filter.where, options, cb);
652+
const filter = mergeQuery({where: scoped}, {where: where || {}, fields: fieldsRelated});
653+
if (!scopeOnRelatedModel) {
654+
targetModel.count(filter.where, options, cb);
655+
} else {
656+
targetModel.find(filter, options, function(err, findData) {
657+
const smartMergeSuccessful =
658+
smartMergeRelatedModelQuery(findData, queryRelated.scope, keyFrom, IdKey);
659+
if (!smartMergeSuccessful) {
660+
return cb(null, 0);
661+
}
662+
return relatedModel.count(queryRelated.scope.where, options, cb);
663+
});
664+
}
665+
return cb.promise;
525666
}
526667

527668
return definition;
528669
}
670+
671+
function smartMergeRelatedModelQuery(findData, queryRelated, keyFrom, IdKey) {
672+
const smartMerge = function(idCollection, qWhere) {
673+
if (!qWhere[IdKey]) return idCollection;
674+
let merged = {};
675+
676+
const idsA = idCollection.inq;
677+
const idsB = qWhere[IdKey].inq ? qWhere[IdKey].inq : [qWhere[IdKey]];
678+
679+
const intersect = _.intersectionWith(idsA, idsB, _.isEqual);
680+
if (intersect.length === 1) merged = intersect[0];
681+
if (intersect.length > 1) merged = {inq: intersect};
682+
683+
return merged;
684+
};
685+
686+
if (queryRelated.where !== undefined) {
687+
// Merge queryRelated filter and targetId filter
688+
const IdKeyCondition = {};
689+
IdKeyCondition[IdKey] = smartMerge(collectTargetIds(findData, keyFrom),
690+
queryRelated.where);
691+
692+
// if the id in filter doesn't exist after the merge,
693+
// return empty result
694+
if (_.isObject(IdKeyCondition[IdKey]) && _.isEmpty(IdKeyCondition[IdKey])) {
695+
return false;
696+
}
697+
698+
const mergedWhere = {
699+
and: [
700+
IdKeyCondition,
701+
_.omit(queryRelated.where, IdKey),
702+
],
703+
};
704+
queryRelated.where = mergedWhere;
705+
} else {
706+
queryRelated.where = {};
707+
queryRelated.where[IdKey] = collectTargetIds(findData, keyFrom);
708+
}
709+
return true;
710+
}

0 commit comments

Comments
 (0)