Skip to content

Commit faf3d1e

Browse files
feat(builder): add support to nested filters (#141)
Add nested filters support to `where` and `whereIn`.
1 parent 0d71cf4 commit faf3d1e

File tree

4 files changed

+138
-7
lines changed

4 files changed

+138
-7
lines changed

Diff for: docs/content/en/api/query-builder-methods.md

+20
Original file line numberDiff line numberDiff line change
@@ -50,20 +50,40 @@ await Post.select({
5050

5151
Add a basic where clause to the query.
5252

53+
**Simple:**
54+
5355
```js
5456
await Model.where('status', 'active')
5557
```
5658

59+
**Nested:**
60+
61+
<alert type="success">Available in version >= v1.8.0</alert>
62+
63+
```js
64+
await Model.where(['user', 'status'], 'active')
65+
```
66+
5767
## `whereIn`
5868
- Arguments: `(field, array)`
5969
- Returns: `self`
6070

6171
Add a "where in" clause to the query.
6272

73+
**Simple:**
74+
6375
```js
6476
await Model.whereIn('id', [1, 2, 3])
6577
```
6678

79+
**Nested:**
80+
81+
<alert type="success">Available in version >= v1.8.0</alert>
82+
83+
```js
84+
await Model.whereIn(['user', 'id'], [1, 2, 3])
85+
```
86+
6787
## `orderBy`
6888
- Arguments: `(...args)`
6989
- Returns: `self`

Diff for: docs/content/en/building-the-query.md

+54
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,33 @@ We can filter our **Posts** to only get results where `status` is `published`:
242242
</code-block>
243243
</code-group>
244244

245+
#### Nested Filter
246+
247+
<alert type="success">Available in version >= v1.8.0</alert>
248+
249+
The first argument of `where` also accepts an array of keys, which are used to build a nested filter.
250+
251+
So we can filter our **Posts** to only get results where `status` of `user` is `active`:
252+
253+
<code-group>
254+
<code-block label="Query" active>
255+
256+
```js
257+
const posts = await Post.where([
258+
'user', 'status'
259+
], 'active').get()
260+
```
261+
262+
</code-block>
263+
<code-block label="Request">
264+
265+
```http request
266+
GET /posts?filter[user][status]=active
267+
```
268+
269+
</code-block>
270+
</code-group>
271+
245272
### Evaluating Multiple Values
246273

247274
See the [API reference](/api/query-builder-methods#wherein)
@@ -271,6 +298,33 @@ We can filter our **Posts** to only get results where `status` is `published` or
271298
</code-block>
272299
</code-group>
273300

301+
#### Nested Filter
302+
303+
<alert type="success">Available in version >= v1.8.0</alert>
304+
305+
The first argument of `whereIn` also accepts an array of keys, which are used to build a nested filter.
306+
307+
So we can filter our **Posts** to only get results where `status` of `user` is `active` or `inactive`:
308+
309+
<code-group>
310+
<code-block label="Query" active>
311+
312+
```js
313+
const posts = await Post.whereIn(['user', 'status'], [
314+
'active', 'inactive'
315+
]).get()
316+
```
317+
318+
</code-block>
319+
<code-block label="Request">
320+
321+
```http request
322+
GET /posts?filter[user][status]=active,inactive
323+
```
324+
325+
</code-block>
326+
</code-group>
327+
274328
## Sorting
275329

276330
See the [API reference](/api/query-builder-methods#orderby)

Diff for: src/Builder.js

+54-6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*/
44

55
import Parser from './Parser';
6+
import setProp from 'dset'
67

78
export default class Builder {
89

@@ -21,11 +22,43 @@ export default class Builder {
2122
this.parser = new Parser(this)
2223
}
2324

24-
// query string parsed
25+
// query string parsed
2526
query() {
2627
return this.parser.query()
2728
}
2829

30+
/**
31+
* Helpers
32+
*/
33+
34+
/**
35+
* Nested filter via array-type keys.
36+
*
37+
* @example
38+
* const [_key, _value] = this._nestedFilter(keys, value)
39+
* this.filters[_key] = _value
40+
*
41+
* @param {string[]} keys - Array-type keys, like `['a', 'b', 'c']`.
42+
* @param {*} value - The value to be set.
43+
*
44+
* @return {[]} - An array containing the first key, which is the index to be used in `filters`
45+
* object, and a value, which is the nested filter.
46+
*
47+
*/
48+
_nestedFilter (keys, value) {
49+
// Get first key from `keys` array, then remove it from array
50+
const _key = keys.shift()
51+
// Initialize an empty object
52+
const _value = {}
53+
54+
// Convert the keys into a deeply nested object, which the value of the deepest key is
55+
// the `value` property.
56+
// Then assign the object to `_value` property.
57+
setProp(_value, keys, value)
58+
59+
return [_key, _value]
60+
}
61+
2962
/**
3063
* Query builder
3164
*/
@@ -63,22 +96,37 @@ export default class Builder {
6396
}
6497

6598
where(key, value) {
66-
if (key === undefined || value === undefined)
99+
if (key === undefined || value === undefined) {
67100
throw new Error('The KEY and VALUE are required on where() method.')
101+
}
68102

69-
if (Array.isArray(value) || value instanceof Object)
103+
if (Array.isArray(value) || value instanceof Object) {
70104
throw new Error('The VALUE must be primitive on where() method.')
105+
}
71106

72-
this.filters[key] = value
107+
if (Array.isArray(key)) {
108+
const [_key, _value] = this._nestedFilter(key, value)
109+
110+
this.filters[_key] = _value
111+
} else {
112+
this.filters[key] = value
113+
}
73114

74115
return this
75116
}
76117

77118
whereIn(key, array) {
78-
if (!Array.isArray(array))
119+
if (!Array.isArray(array)) {
79120
throw new Error('The second argument on whereIn() method must be an array.')
121+
}
122+
123+
if (Array.isArray(key)) {
124+
const [_key, _value] = this._nestedFilter(key, array.join(','))
80125

81-
this.filters[key] = array.join(',')
126+
this.filters[_key] = _value
127+
} else {
128+
this.filters[key] = array.join(',')
129+
}
82130

83131
return this
84132
}

Diff for: tests/builder.test.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ describe('Query builder', () => {
9494
post = Post.where('id', 1).where('title', 'Cool')
9595

9696
expect(post._builder.filters).toEqual({ id: 1, title: 'Cool' })
97+
98+
post = Post.where(['user', 'status'], 'active')
99+
100+
expect(post._builder.filters).toEqual({ user: { status: 'active' } })
101+
expect(post._builder.query()).toEqual('?filter[user][status]=active')
97102
})
98103

99104
test('where() throws a exception when doest not have params or only first param', () => {
@@ -122,6 +127,10 @@ describe('Query builder', () => {
122127
let post = Post.whereIn('status', ['ACTIVE', 'ARCHIVED'])
123128

124129
expect(post._builder.filters).toEqual({ status: 'ACTIVE,ARCHIVED' })
130+
131+
post = Post.whereIn(['user', 'status'], ['active', 'inactive'])
132+
133+
expect(post._builder.query()).toEqual('?filter[user][status]=active,inactive')
125134
})
126135

127136
test('whereIn() throws a exception when second parameter is not a array', () => {
@@ -209,4 +218,4 @@ describe('Query builder', () => {
209218
expect(post._builder.query()).toEqual(query)
210219
expect(post._builder.query()).toEqual(query)
211220
})
212-
})
221+
})

0 commit comments

Comments
 (0)