Skip to content

Commit b693a57

Browse files
committed
Fix bug #1795 Count issues related models
1 parent 770f11b commit b693a57

File tree

2 files changed

+311
-9
lines changed

2 files changed

+311
-9
lines changed

Diff for: lib/scope.js

+185-9
Original file line numberDiff line numberDiff line change
@@ -431,11 +431,55 @@ function defineScope(cls, targetClass, name, params, methods, options) {
431431
options = {};
432432
}
433433
options = options || {};
434-
435434
const targetModel = definition.targetModel(this._receiver);
435+
// If there is a through model
436+
// run another query to apply filter on relatedModel(targetModel)
437+
// see github.com/strongloop/loopback-datasource-juggler/issues/1795
438+
let scopeOnRelatedModel = false;
439+
let queryRelated;
440+
let keyFrom;
441+
let relatedModel;
442+
let IdKey;
443+
let fieldsRelated;
444+
if (this._scope && this._scope.collect &&
445+
where !== null && typeof where === 'object') {
446+
queryRelated = {
447+
relation: this._scope.collect,
448+
scope: {
449+
where: where,
450+
},
451+
};
452+
where = {};
453+
scopeOnRelatedModel = true;
454+
relatedModel = targetModel.relations[queryRelated.relation].modelTo;
455+
IdKey = idName(relatedModel);
456+
keyFrom = targetModel.relations[queryRelated.relation].keyFrom || IdKey;
457+
fieldsRelated = [keyFrom];
458+
}
436459
const scoped = (this._scope && this._scope.where) || {};
437-
const filter = mergeQuery({where: scoped}, {where: where || {}});
438-
return targetModel.destroyAll(filter.where, options, cb);
460+
const filter = mergeQuery({where: scoped}, {where: where || {}, fields: fieldsRelated});
461+
if (!scopeOnRelatedModel) {
462+
return targetModel.destroyAll(filter.where, options, cb);
463+
}
464+
return targetModel.find(filter, options, function(err, findData) {
465+
// Merge queryRelated filter and targetId filter
466+
const buildWhere = function() {
467+
return {
468+
and: [
469+
{
470+
[IdKey]: collectTargetIds(findData, keyFrom),
471+
},
472+
queryRelated.scope.where],
473+
};
474+
};
475+
if (queryRelated.scope.where !== undefined) {
476+
queryRelated.scope.where = buildWhere();
477+
} else {
478+
queryRelated.scope.where = {};
479+
queryRelated.scope.where[IdKey] = collectTargetIds(findData, keyFrom);
480+
}
481+
return relatedModel.destroyAll(queryRelated.scope.where, options, cb);
482+
});
439483
}
440484

441485
function updateAll(where, data, options, cb) {
@@ -453,9 +497,54 @@ function defineScope(cls, targetClass, name, params, methods, options) {
453497
}
454498
options = options || {};
455499
const targetModel = definition.targetModel(this._receiver);
500+
// If there is a through model
501+
// run another query to apply filter on relatedModel(targetModel)
502+
// see github.com/strongloop/loopback-datasource-juggler/issues/1795
503+
let scopeOnRelatedModel = false;
504+
let queryRelated;
505+
let keyFrom;
506+
let relatedModel;
507+
let IdKey;
508+
let fieldsRelated;
509+
if (this._scope && this._scope.collect &&
510+
where !== null && typeof where === 'object') {
511+
queryRelated = {
512+
relation: this._scope.collect,
513+
scope: {
514+
where: where,
515+
},
516+
};
517+
where = {};
518+
scopeOnRelatedModel = true;
519+
relatedModel = targetModel.relations[queryRelated.relation].modelTo;
520+
IdKey = idName(relatedModel);
521+
keyFrom = targetModel.relations[queryRelated.relation].keyFrom || IdKey;
522+
fieldsRelated = [keyFrom];
523+
}
456524
const scoped = (this._scope && this._scope.where) || {};
457-
const filter = mergeQuery({where: scoped}, {where: where || {}});
458-
return targetModel.updateAll(filter.where, data, options, cb);
525+
const filter = mergeQuery({where: scoped}, {where: where || {}, fields: fieldsRelated});
526+
if (!scopeOnRelatedModel) {
527+
return targetModel.updateAll(filter.where, data, options, cb);
528+
}
529+
return targetModel.find(filter, options, function(err, findData) {
530+
// Merge queryRelated filter and targetId filter
531+
const buildWhere = function() {
532+
return {
533+
and: [
534+
{
535+
[IdKey]: collectTargetIds(findData, keyFrom),
536+
},
537+
queryRelated.scope.where],
538+
};
539+
};
540+
if (queryRelated.scope.where !== undefined) {
541+
queryRelated.scope.where = buildWhere();
542+
} else {
543+
queryRelated.scope.where = {};
544+
queryRelated.scope.where[IdKey] = collectTargetIds(findData, keyFrom);
545+
}
546+
return relatedModel.updateAll(queryRelated.scope.where, data, options, cb);
547+
});
459548
}
460549

461550
function findById(id, filter, options, cb) {
@@ -501,9 +590,52 @@ function defineScope(cls, targetClass, name, params, methods, options) {
501590
}
502591
options = options || {};
503592
const targetModel = definition.targetModel(this._receiver);
593+
// If there is a through model
594+
// run another query to apply filter on relatedModel(targetModel)
595+
// see github.com/strongloop/loopback-datasource-juggler/issues/1795
596+
let scopeOnRelatedModel = false;
597+
let queryRelated;
598+
let keyFrom;
599+
let relatedModel;
600+
let IdKey;
601+
if (this._scope && this._scope.collect &&
602+
filter && filter.where !== null && typeof filter.where === 'object') {
603+
queryRelated = {
604+
relation: this._scope.collect,
605+
scope: filter,
606+
};
607+
scopeOnRelatedModel = true;
608+
relatedModel = targetModel.relations[queryRelated.relation].modelTo;
609+
IdKey = idName(relatedModel);
610+
keyFrom = targetModel.relations[queryRelated.relation].keyFrom || IdKey;
611+
filter = {
612+
fields: [keyFrom],
613+
};
614+
}
504615
const scoped = (this._scope && this._scope.where) || {};
505616
filter = mergeQuery({where: scoped}, filter || {});
506-
return targetModel.findOne(filter, options, cb);
617+
if (!scopeOnRelatedModel) {
618+
return targetModel.findOne(filter, options, cb);
619+
}
620+
return targetModel.find(filter, options, function(err, data) {
621+
// Merge queryRelated filter and targetId filter
622+
const buildWhere = function() {
623+
return {
624+
and: [
625+
{
626+
[IdKey]: collectTargetIds(data, keyFrom),
627+
},
628+
queryRelated.scope.where],
629+
};
630+
};
631+
if (queryRelated.scope.where !== undefined) {
632+
queryRelated.scope.where = buildWhere();
633+
} else {
634+
queryRelated.scope.where = {};
635+
queryRelated.scope.where[IdKey] = collectTargetIds(data, keyFrom);
636+
}
637+
return relatedModel.findOne(queryRelated.scope, options, cb);
638+
});
507639
}
508640

509641
function count(where, options, cb) {
@@ -517,11 +649,55 @@ function defineScope(cls, targetClass, name, params, methods, options) {
517649
options = {};
518650
}
519651
options = options || {};
520-
521652
const targetModel = definition.targetModel(this._receiver);
653+
// If there is a through model
654+
// run another query to apply filter on relatedModel(targetModel)
655+
// see github.com/strongloop/loopback-datasource-juggler/issues/1795
656+
let scopeOnRelatedModel = false;
657+
let queryRelated;
658+
let keyFrom;
659+
let relatedModel;
660+
let IdKey;
661+
let fieldsRelated;
662+
if (this._scope && this._scope.collect &&
663+
where !== null && typeof where === 'object') {
664+
queryRelated = {
665+
relation: this._scope.collect,
666+
scope: {
667+
where: where,
668+
},
669+
};
670+
where = {};
671+
scopeOnRelatedModel = true;
672+
relatedModel = targetModel.relations[queryRelated.relation].modelTo;
673+
IdKey = idName(relatedModel);
674+
keyFrom = targetModel.relations[queryRelated.relation].keyFrom || IdKey;
675+
fieldsRelated = [keyFrom];
676+
}
522677
const scoped = (this._scope && this._scope.where) || {};
523-
const filter = mergeQuery({where: scoped}, {where: where || {}});
524-
return targetModel.count(filter.where, options, cb);
678+
const filter = mergeQuery({where: scoped}, {where: where || {}, fields: fieldsRelated});
679+
if (!scopeOnRelatedModel) {
680+
return targetModel.count(filter.where, options, cb);
681+
}
682+
return targetModel.find(filter, options, function(err, data) {
683+
// Merge queryRelated filter and targetId filter
684+
const buildWhere = function() {
685+
return {
686+
and: [
687+
{
688+
[IdKey]: collectTargetIds(data, keyFrom),
689+
},
690+
queryRelated.scope.where],
691+
};
692+
};
693+
if (queryRelated.scope.where !== undefined) {
694+
queryRelated.scope.where = buildWhere();
695+
} else {
696+
queryRelated.scope.where = {};
697+
queryRelated.scope.where[IdKey] = collectTargetIds(data, keyFrom);
698+
}
699+
return relatedModel.count(queryRelated.scope.where, options, cb);
700+
});
525701
}
526702

527703
return definition;

Diff for: test/relations.test.js

+126
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,132 @@ describe('relations', function() {
605605
db.automigrate(['Physician', 'Patient', 'Appointment', 'Address'], done);
606606
});
607607

608+
it('should count scoped record with promises based on related model properties', function(done) {
609+
let id;
610+
Physician.create()
611+
.then(function(physician) {
612+
return physician.patients.create({name: 'a'})
613+
.then(function(ch) {
614+
id = ch.id;
615+
return physician.patients.create({name: 'z'});
616+
})
617+
.then(function() {
618+
return physician.patients.create({name: 'c'});
619+
})
620+
.then(function() {
621+
return verify(physician);
622+
});
623+
}).catch(done);
624+
625+
function verify(physician) {
626+
return physician.patients.count({
627+
name: 'a',
628+
}, function(err, count) {
629+
if (err) return done(err);
630+
count.should.equal(1);
631+
done();
632+
});
633+
}
634+
});
635+
636+
it('should find one scoped record with promises based on related model properties', function(done) {
637+
let id;
638+
Physician.create()
639+
.then(function(physician) {
640+
return physician.patients.create({name: 'a'})
641+
.then(function(ch) {
642+
id = ch.id;
643+
return physician.patients.create({name: 'z'});
644+
})
645+
.then(function() {
646+
return physician.patients.create({name: 'c'});
647+
})
648+
.then(function() {
649+
return verify(physician);
650+
});
651+
}).catch(done);
652+
653+
function verify(physician) {
654+
return physician.patients.findOne({
655+
where: {
656+
name: 'a',
657+
},
658+
}, function(err, patient) {
659+
if (err) return done(err);
660+
should.exist(patient);
661+
patient.name.should.equal('a');
662+
done();
663+
});
664+
}
665+
});
666+
667+
it('should update all scoped record with promises based on related model properties', function(done) {
668+
let id;
669+
Physician.create()
670+
.then(function(physician) {
671+
return physician.patients.create({name: 'a'})
672+
.then(function(ch) {
673+
id = ch.id;
674+
return physician.patients.create({name: 'z'});
675+
})
676+
.then(function() {
677+
return physician.patients.create({name: 'c'});
678+
})
679+
.then(function() {
680+
return verify(physician);
681+
});
682+
}).catch(done);
683+
684+
function verify(physician) {
685+
return physician.patients.updateAll({
686+
name: 'a',
687+
}, {age: 5}, function(err, result) {
688+
if (err) return done(err);
689+
should.exist(result);
690+
result.count.should.equal(1);
691+
physician.patients.findOne({
692+
where: {
693+
name: 'a',
694+
},
695+
}, function(err, patient) {
696+
if (err) return done(err);
697+
should.exist(patient);
698+
patient.age.should.equal(5);
699+
done();
700+
});
701+
});
702+
}
703+
});
704+
705+
it('should destroyAll all scoped record with promises based on related model properties', function(done) {
706+
let id;
707+
Physician.create()
708+
.then(function(physician) {
709+
return physician.patients.create({name: 'a'})
710+
.then(function(ch) {
711+
id = ch.id;
712+
return physician.patients.create({name: 'z'});
713+
})
714+
.then(function() {
715+
return physician.patients.create({name: 'c'});
716+
})
717+
.then(function() {
718+
return verify(physician);
719+
});
720+
}).catch(done);
721+
722+
function verify(physician) {
723+
return physician.patients.destroyAll({
724+
name: 'a',
725+
}, function(err, result) {
726+
if (err) return done(err);
727+
should.exist(result);
728+
result.count.should.equal(1);
729+
done();
730+
});
731+
}
732+
});
733+
608734
it('should build record on scope', function(done) {
609735
Physician.create(function(err, physician) {
610736
const patient = physician.patients.build();

0 commit comments

Comments
 (0)