Skip to content

Commit cb26ff8

Browse files
authored
Merge pull request #202 from AthennaIO/develop
feat: add whereHas method
2 parents da5cc5f + 9d7fab7 commit cb26ff8

File tree

15 files changed

+467
-86
lines changed

15 files changed

+467
-86
lines changed

package-lock.json

Lines changed: 138 additions & 64 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@athenna/database",
3-
"version": "5.23.0",
3+
"version": "5.24.0",
44
"description": "The Athenna database handler for SQL/NoSQL.",
55
"license": "MIT",
66
"author": "João Lenon <lenon@athenna.io>",
@@ -77,7 +77,7 @@
7777
},
7878
"devDependencies": {
7979
"@athenna/artisan": "^5.6.0",
80-
"@athenna/common": "^5.9.0",
80+
"@athenna/common": "^5.10.0",
8181
"@athenna/config": "^5.3.0",
8282
"@athenna/ioc": "^5.1.0",
8383
"@athenna/logger": "^5.3.0",

src/models/builders/ModelQueryBuilder.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,23 @@ export class ModelQueryBuilder<
512512
return this
513513
}
514514

515+
/**
516+
* Only returns the data if the closure returns a result.
517+
*/
518+
public whereHas<K extends ModelRelations<M>>(
519+
relation: K | string,
520+
closure: (
521+
query: ModelQueryBuilder<
522+
Extract<M[K] extends BaseModel[] ? M[K][0] : M[K], BaseModel>,
523+
Driver
524+
>
525+
) => any
526+
) {
527+
this.schema.includeWhereHasRelation(relation, closure)
528+
529+
return this
530+
}
531+
515532
/**
516533
* Executes the given closure when the first argument is true.
517534
*/

src/models/factories/ModelGenerator.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ export class ModelGenerator<M extends BaseModel = any> extends Macroable {
128128
model = await this.includeRelation(model, relation)
129129
}
130130

131+
if (!model) {
132+
return undefined
133+
}
134+
131135
return model.setOriginal()
132136
}
133137

src/models/relations/BelongsTo/BelongsToRelation.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ export class BelongsToRelation {
4242
.when(relation.closure, relation.closure)
4343
.find()
4444

45+
if (relation.isWhereHasIncluded && !model[relation.property]) {
46+
return undefined
47+
}
48+
4549
return model
4650
}
4751

@@ -66,10 +70,16 @@ export class BelongsToRelation {
6670

6771
results.forEach(result => map.set(result[relation.primaryKey], result))
6872

69-
return models.map(model => {
70-
model[relation.property] = map.get(model[relation.foreignKey])
73+
return models
74+
.map(model => {
75+
model[relation.property] = map.get(model[relation.foreignKey])
76+
77+
if (relation.isWhereHasIncluded && !model[relation.property]) {
78+
return undefined
79+
}
7180

72-
return model
73-
})
81+
return model
82+
})
83+
.filter(Boolean)
7484
}
7585
}

src/models/relations/BelongsToMany/BelongsToManyRelation.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ export class BelongsToManyRelation {
5151
.when(relation.closure, relation.closure)
5252
.findMany()
5353

54+
if (relation.isWhereHasIncluded && !model[relation.property]?.length) {
55+
return undefined
56+
}
57+
5458
return model
5559
}
5660

@@ -96,14 +100,20 @@ export class BelongsToManyRelation {
96100
map.set(result[relation.relationPrimaryKey], result)
97101
)
98102

99-
return models.map(model => {
100-
const ids = pivotDataMap.get(model[relation.primaryKey]) || []
103+
return models
104+
.map(model => {
105+
const ids = pivotDataMap.get(model[relation.primaryKey]) || []
101106

102-
model[relation.property] = ids
103-
.map(id => map.get(id))
104-
.filter(data => data !== undefined)
107+
model[relation.property] = ids
108+
.map(id => map.get(id))
109+
.filter(data => data !== undefined)
105110

106-
return model
107-
})
111+
if (relation.isWhereHasIncluded && !model[relation.property]?.length) {
112+
return undefined
113+
}
114+
115+
return model
116+
})
117+
.filter(Boolean)
108118
}
109119
}

src/models/relations/HasMany/HasManyRelation.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ export class HasManyRelation {
2525
.when(relation.closure, relation.closure)
2626
.findMany()
2727

28+
if (relation.isWhereHasIncluded && !model[relation.property]?.length) {
29+
return undefined
30+
}
31+
2832
return model
2933
}
3034

@@ -53,10 +57,16 @@ export class HasManyRelation {
5357
map.set(result[relation.foreignKey], array)
5458
})
5559

56-
return models.map(model => {
57-
model[relation.property] = map.get(model[relation.primaryKey]) || []
60+
return models
61+
.map(model => {
62+
model[relation.property] = map.get(model[relation.primaryKey]) || []
5863

59-
return model
60-
})
64+
if (relation.isWhereHasIncluded && !model[relation.property]?.length) {
65+
return undefined
66+
}
67+
68+
return model
69+
})
70+
.filter(Boolean)
6171
}
6272
}

src/models/relations/HasOne/HasOneRelation.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ export class HasOneRelation {
2525
.when(relation.closure, relation.closure)
2626
.find()
2727

28+
if (relation.isWhereHasIncluded && !model[relation.property]) {
29+
return undefined
30+
}
31+
2832
return model
2933
}
3034

@@ -47,10 +51,16 @@ export class HasOneRelation {
4751

4852
results.forEach(result => map.set(result[relation.foreignKey], result))
4953

50-
return models.map(model => {
51-
model[relation.property] = map.get(model[relation.primaryKey])
54+
return models
55+
.map(model => {
56+
model[relation.property] = map.get(model[relation.primaryKey])
57+
58+
if (relation.isWhereHasIncluded && !model[relation.property]) {
59+
return undefined
60+
}
5261

53-
return model
54-
})
62+
return model
63+
})
64+
.filter(Boolean)
5565
}
5666
}

src/models/schemas/ModelSchema.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ export class ModelSchema<M extends BaseModel = any> extends Macroable {
322322
* that are included.
323323
*/
324324
public getIncludedRelations(): RelationOptions[] {
325-
return this.relations.filter(r => r.isIncluded)
325+
return this.relations.filter(r => r.isIncluded || r.isWhereHasIncluded)
326326
}
327327

328328
/**
@@ -369,6 +369,43 @@ export class ModelSchema<M extends BaseModel = any> extends Macroable {
369369
return options
370370
}
371371

372+
/**
373+
* Include a relation by setting the isWhereHasIncluded
374+
* option to true.
375+
*/
376+
public includeWhereHasRelation(
377+
property: string | ModelRelations<M>,
378+
closure?: (query: ModelQueryBuilder) => any
379+
) {
380+
const model = this.Model.name
381+
382+
if (property.includes('.')) {
383+
const [first, ...rest] = property.split('.')
384+
385+
property = first
386+
closure = this.createdNestedRelationClosure(rest)
387+
}
388+
389+
const options = this.getRelationByProperty(property)
390+
391+
if (!options) {
392+
throw new NotImplementedRelationException(
393+
property as string,
394+
model,
395+
this.relations.map(r => r.property).join(', ')
396+
)
397+
}
398+
399+
const i = this.relations.indexOf(options)
400+
401+
options.isWhereHasIncluded = true
402+
options.closure = closure
403+
404+
this.relations[i] = options
405+
406+
return options
407+
}
408+
372409
/**
373410
* Created nested relationships closure to
374411
* load relationship's relationships

src/types/relations/BelongsToManyOptions.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,16 @@ export type BelongsToManyOptions<
6767
*/
6868
isIncluded?: boolean
6969

70+
/**
71+
* Set if the model will be included when fetching
72+
* data.
73+
* If this option is true, you don't need to call
74+
* methods like `whereHas()` to eager load your relation.
75+
*
76+
* @default false
77+
*/
78+
isWhereHasIncluded?: boolean
79+
7080
/**
7181
* The primary key is always the primary key
7282
* of the main model.

0 commit comments

Comments
 (0)