Skip to content

feat(builder): add support to nested filters #141

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Nov 9, 2020
20 changes: 20 additions & 0 deletions docs/content/en/api/query-builder-methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,40 @@ await Post.select({

Add a basic where clause to the query.

**Simple:**

```js
await Model.where('status', 'active')
```

**Nested:**

<alert type="success">Available in version >= v1.8.0</alert>

```js
await Model.where(['user', 'status'], 'active')
```

## `whereIn`
- Arguments: `(field, array)`
- Returns: `self`

Add a "where in" clause to the query.

**Simple:**

```js
await Model.whereIn('id', [1, 2, 3])
```

**Nested:**

<alert type="success">Available in version >= v1.8.0</alert>

```js
await Model.whereIn(['user', 'id'], [1, 2, 3])
```

## `orderBy`
- Arguments: `(...args)`
- Returns: `self`
Expand Down
54 changes: 54 additions & 0 deletions docs/content/en/building-the-query.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,33 @@ We can filter our **Posts** to only get results where `status` is `published`:
</code-block>
</code-group>

#### Nested Filter

<alert type="success">Available in version >= v1.8.0</alert>

The first argument of `where` also accepts an array of keys, which are used to build a nested filter.

So we can filter our **Posts** to only get results where `status` of `user` is `active`:

<code-group>
<code-block label="Query" active>

```js
const posts = await Post.where([
'user', 'status'
], 'active').get()
```

</code-block>
<code-block label="Request">

```http request
GET /posts?filter[user][status]=active
```

</code-block>
</code-group>

### Evaluating Multiple Values

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

#### Nested Filter

<alert type="success">Available in version >= v1.8.0</alert>

The first argument of `whereIn` also accepts an array of keys, which are used to build a nested filter.

So we can filter our **Posts** to only get results where `status` of `user` is `active` or `inactive`:

<code-group>
<code-block label="Query" active>

```js
const posts = await Post.whereIn(['user', 'status'], [
'active', 'inactive'
]).get()
```

</code-block>
<code-block label="Request">

```http request
GET /posts?filter[user][status]=active,inactive
```

</code-block>
</code-group>

## Sorting

See the [API reference](/api/query-builder-methods#orderby)
Expand Down
60 changes: 54 additions & 6 deletions src/Builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/

import Parser from './Parser';
import setProp from 'dset'

export default class Builder {

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

// query string parsed
// query string parsed
query() {
return this.parser.query()
}

/**
* Helpers
*/

/**
* Nested filter via array-type keys.
*
* @example
* const [_key, _value] = this._nestedFilter(keys, value)
* this.filters[_key] = _value
*
* @param {string[]} keys - Array-type keys, like `['a', 'b', 'c']`.
* @param {*} value - The value to be set.
*
* @return {[]} - An array containing the first key, which is the index to be used in `filters`
* object, and a value, which is the nested filter.
*
*/
_nestedFilter (keys, value) {
// Get first key from `keys` array, then remove it from array
const _key = keys.shift()
// Initialize an empty object
const _value = {}

// Convert the keys into a deeply nested object, which the value of the deepest key is
// the `value` property.
// Then assign the object to `_value` property.
setProp(_value, keys, value)

return [_key, _value]
}

/**
* Query builder
*/
Expand Down Expand Up @@ -63,22 +96,37 @@ export default class Builder {
}

where(key, value) {
if (key === undefined || value === undefined)
if (key === undefined || value === undefined) {
throw new Error('The KEY and VALUE are required on where() method.')
}

if (Array.isArray(value) || value instanceof Object)
if (Array.isArray(value) || value instanceof Object) {
throw new Error('The VALUE must be primitive on where() method.')
}

this.filters[key] = value
if (Array.isArray(key)) {
const [_key, _value] = this._nestedFilter(key, value)

this.filters[_key] = _value
} else {
this.filters[key] = value
}

return this
}

whereIn(key, array) {
if (!Array.isArray(array))
if (!Array.isArray(array)) {
throw new Error('The second argument on whereIn() method must be an array.')
}

if (Array.isArray(key)) {
const [_key, _value] = this._nestedFilter(key, array.join(','))

this.filters[key] = array.join(',')
this.filters[_key] = _value
} else {
this.filters[key] = array.join(',')
}

return this
}
Expand Down
11 changes: 10 additions & 1 deletion tests/builder.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ describe('Query builder', () => {
post = Post.where('id', 1).where('title', 'Cool')

expect(post._builder.filters).toEqual({ id: 1, title: 'Cool' })

post = Post.where(['user', 'status'], 'active')

expect(post._builder.filters).toEqual({ user: { status: 'active' } })
expect(post._builder.query()).toEqual('?filter[user][status]=active')
})

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

expect(post._builder.filters).toEqual({ status: 'ACTIVE,ARCHIVED' })

post = Post.whereIn(['user', 'status'], ['active', 'inactive'])

expect(post._builder.query()).toEqual('?filter[user][status]=active,inactive')
})

test('whereIn() throws a exception when second parameter is not a array', () => {
Expand Down Expand Up @@ -209,4 +218,4 @@ describe('Query builder', () => {
expect(post._builder.query()).toEqual(query)
expect(post._builder.query()).toEqual(query)
})
})
})