@@ -91,7 +91,7 @@ ScopeDefinition.prototype.related = function(receiver, scopeParams, condOrRefres
91
91
actualRefresh ;
92
92
if ( refreshIsNeeded ) {
93
93
// 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 } ) ;
95
95
const targetModel = this . targetModel ( receiver ) ;
96
96
97
97
// If there is a through model
@@ -100,64 +100,39 @@ ScopeDefinition.prototype.related = function(receiver, scopeParams, condOrRefres
100
100
const scopeOnRelatedModel = params . collect &&
101
101
params . include . scope !== null &&
102
102
typeof params . include . scope === 'object' ;
103
- let filter , queryRelated ;
103
+ let filter , queryRelated , keyFrom , relatedModel , IdKey ;
104
104
if ( scopeOnRelatedModel ) {
105
105
filter = params . include ;
106
106
// The filter applied on relatedModel
107
107
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
+ }
109
120
}
110
121
111
- targetModel . find ( params , options , function ( err , data ) {
122
+ targetModel . find ( params , options , function ( err , findData ) {
112
123
if ( ! err && saveOnCache ) {
113
124
defineCachedRelations ( self ) ;
114
- self . __cachedRelations [ name ] = data ;
125
+ self . __cachedRelations [ name ] = findData ;
115
126
}
116
127
117
128
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 , [ ] ) ;
156
132
}
157
-
158
133
relatedModel . find ( queryRelated , options , cb ) ;
159
134
} else {
160
- cb ( err , data ) ;
135
+ cb ( err , findData ) ;
161
136
}
162
137
} ) ;
163
138
} else {
@@ -298,6 +273,16 @@ function defineScope(cls, targetClass, name, params, methods, options) {
298
273
options = { } ;
299
274
}
300
275
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
+ }
301
286
return definition . related ( self , f . _scope , condOrRefresh , options , cb ) ;
302
287
} ;
303
288
@@ -431,11 +416,49 @@ function defineScope(cls, targetClass, name, params, methods, options) {
431
416
options = { } ;
432
417
}
433
418
options = options || { } ;
434
-
419
+ cb = cb || utils . createPromiseCallback ( ) ;
435
420
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
+ }
436
447
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 ;
439
462
}
440
463
441
464
function updateAll ( where , data , options , cb ) {
@@ -452,10 +475,49 @@ function defineScope(cls, targetClass, name, params, methods, options) {
452
475
options = { } ;
453
476
}
454
477
options = options || { } ;
478
+ cb = cb || utils . createPromiseCallback ( ) ;
455
479
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
+ }
456
506
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 ;
459
521
}
460
522
461
523
function findById ( id , filter , options , cb ) {
@@ -500,10 +562,51 @@ function defineScope(cls, targetClass, name, params, methods, options) {
500
562
options = { } ;
501
563
}
502
564
options = options || { } ;
565
+ cb = cb || utils . createPromiseCallback ( ) ;
503
566
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
+ }
504
595
const scoped = ( this . _scope && this . _scope . where ) || { } ;
505
596
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 ;
507
610
}
508
611
509
612
function count ( where , options , cb ) {
@@ -517,12 +620,91 @@ function defineScope(cls, targetClass, name, params, methods, options) {
517
620
options = { } ;
518
621
}
519
622
options = options || { } ;
520
-
623
+ cb = cb || utils . createPromiseCallback ( ) ;
521
624
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
+ }
522
651
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 ;
525
666
}
526
667
527
668
return definition ;
528
669
}
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