Skip to content

Commit

Permalink
feat(builder): add support to nested filters (robsontenorio#141)
Browse files Browse the repository at this point in the history
Add nested filters support to `where` and `whereIn`.
  • Loading branch information
JoaoPedroAS51 authored Nov 9, 2020
1 parent 0d71cf4 commit faf3d1e
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 7 deletions.
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)
})
})
})

0 comments on commit faf3d1e

Please sign in to comment.