Skip to content

Commit 03cbd69

Browse files
feat(model): add method stringifyOptions (#254)
* chore(deps): add `@types/qs` * feat(model): add method `stringifyOptions` * docs(configurations): add method `stringifyOptions`
1 parent 808d8c3 commit 03cbd69

File tree

9 files changed

+155
-3
lines changed

9 files changed

+155
-3
lines changed

docs/content/en/api/model-options.md

+17
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,23 @@ formData() {
111111
}
112112
```
113113

114+
### `stringifyOptions`
115+
- Default: `{ encode: false, arrayFormat: 'comma' }`
116+
- Returns: `object`
117+
118+
This method can be overridden in the model to configure `qs`.
119+
120+
See [qs](https://github.com/ljharb/qs#stringifying)
121+
122+
```js
123+
stringifyOptions() {
124+
return {
125+
encode: false,
126+
arrayFormat: 'comma',
127+
}
128+
}
129+
```
130+
114131
## Model Options
115132

116133
These are model-related options.

docs/content/en/configuration.md

+53
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,52 @@ export default class Model extends BaseModel {
296296
}
297297
```
298298

299+
## Configuring Query Parameters Parser
300+
301+
See the [API reference](/api/model-options#stringifyOptions) and [qs](https://github.com/ljharb/qs#stringifying)
302+
303+
We may also need to configure the parser to match our needs. By default, it is configured to match
304+
`spatie/laravel-query-builder`, which uses `comma` array format.
305+
306+
If we want, for example, to change this behaviour to `indices`, we can configure the stringify options of `qs`
307+
by overriding the `stringifyOptions` method.
308+
309+
We can globally configure this in the [Base Model](/configuration#creating-a-base-model):
310+
311+
```js{}[~/models/Model.js]
312+
import { Model as BaseModel } from 'vue-api-query'
313+
314+
export default class Model extends BaseModel {
315+
316+
// Define a base url for a REST API
317+
baseURL() {
318+
return 'http://my-api.com'
319+
}
320+
321+
// Implement a default request method
322+
request(config) {
323+
return this.$http.request(config)
324+
}
325+
326+
// Override default query parameter names
327+
parameterNames() {
328+
const defaultParams = super.parameterNames()
329+
const customParams = {
330+
include: 'include_custom'
331+
}
332+
333+
return { ...defaultParams, ...customParams }
334+
}
335+
336+
// Configure qs
337+
stringifyOptions() {
338+
return {
339+
arrayFormat: 'indices'
340+
}
341+
}
342+
}
343+
```
344+
299345
## Configuring FormData
300346

301347
See the [API reference](/api/model-options#formdata) and [object-to-formdata](https://github.com/therealparmesh/object-to-formdata#usage)
@@ -331,6 +377,13 @@ export default class Model extends BaseModel {
331377
return { ...defaultParams, ...customParams }
332378
}
333379
380+
// Configure qs
381+
stringifyOptions() {
382+
return {
383+
arrayFormat: 'indices'
384+
}
385+
}
386+
334387
// Configure object-to-formadata
335388
formData() {
336389
return {

index.d.ts

+10
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { IStringifyOptions } from 'qs'
2+
13
type Method =
24
| 'get'
35
| 'GET'
@@ -443,6 +445,14 @@ export class Model extends StaticModel {
443445
limit: string
444446
}
445447

448+
/**
449+
* This method can be overridden in the model to configure `qs`.
450+
*
451+
* @see {@link https://robsontenorio.github.io/vue-api-query/api/model-options#stringifyOptions|API Reference}
452+
* @see {@link https://github.com/ljharb/qs#stringifying|qs}
453+
*/
454+
protected stringifyOptions(): IStringifyOptions
455+
446456
/**
447457
* Query
448458
*/

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
"semantic-release": "^19.0.3"
7070
},
7171
"dependencies": {
72+
"@types/qs": "^6.9.7",
7273
"defu": "^6.0.0",
7374
"dotprop": "^1.2.0",
7475
"dset": "^3.1.2",

src/Model.js

+6
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,12 @@ export default class Model extends StaticModel {
188188
}
189189
}
190190

191+
stringifyOptions() {
192+
return {
193+
arrayFormat: 'comma'
194+
}
195+
}
196+
191197
/**
192198
* Query
193199
*/

src/Parser.js

+9-3
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,10 @@ export default class Parser {
106106
let fields = { [this.parameterNames().fields]: this.builder.fields }
107107
this.uri +=
108108
this.prepend() +
109-
qs.stringify(fields, { encode: false, arrayFormat: 'comma' })
109+
qs.stringify(fields, {
110+
encode: false,
111+
...this.builder.model.stringifyOptions()
112+
})
110113
}
111114

112115
filters() {
@@ -117,7 +120,10 @@ export default class Parser {
117120
let filters = { [this.parameterNames().filter]: this.builder.filters }
118121
this.uri +=
119122
this.prepend() +
120-
qs.stringify(filters, { encode: false, arrayFormat: 'comma' })
123+
qs.stringify(filters, {
124+
encode: false,
125+
...this.builder.model.stringifyOptions()
126+
})
121127
}
122128

123129
sorts() {
@@ -159,7 +165,7 @@ export default class Parser {
159165
this.prepend() +
160166
qs.stringify(this.builder.payload, {
161167
encode: false,
162-
arrayFormat: 'comma'
168+
...this.builder.model.stringifyOptions()
163169
})
164170
}
165171
}

tests/builder.test.js

+31
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import MockAdapter from 'axios-mock-adapter'
44
import { Model } from '../src'
55
import ModelWithParamNames from './dummy/models/ModelWithParamNames'
66
import Post from './dummy/models/Post'
7+
import PostWithOptions from './dummy/models/PostWithOptions'
78

89
describe('Query builder', () => {
910
let errorModel = {}
@@ -56,6 +57,36 @@ describe('Query builder', () => {
5657
expect(post._builder.query()).toEqual(query)
5758
})
5859

60+
test('it can change default array format option', () => {
61+
let post = PostWithOptions.include('user').whereIn('status', [
62+
'published',
63+
'archived'
64+
])
65+
66+
expect(post._builder.query()).toEqual(
67+
'?include=user&filter[status][0]=published&filter[status][1]=archived'
68+
)
69+
70+
expect(post._builder.filters).toEqual({
71+
status: ['published', 'archived']
72+
})
73+
74+
post = PostWithOptions.include('user').whereIn(
75+
['user', 'status'],
76+
['active', 'inactive']
77+
)
78+
79+
expect(post._builder.query()).toEqual(
80+
'?include=user&filter[user][status][0]=active&filter[user][status][1]=inactive'
81+
)
82+
83+
expect(post._builder.filters).toEqual({
84+
user: {
85+
status: ['active', 'inactive']
86+
}
87+
})
88+
})
89+
5990
test('include() sets properly the builder', () => {
6091
let post = Post.include('user')
6192

tests/dummy/models/PostWithOptions.js

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import BaseModel from './BaseModel'
2+
import Comment from './Comment'
3+
import Tag from './Tag'
4+
import User from './User'
5+
6+
export default class PostWithOptions extends BaseModel {
7+
comments() {
8+
return this.hasMany(Comment)
9+
}
10+
11+
relations() {
12+
return {
13+
user: User,
14+
'relationships.tags': Tag
15+
}
16+
}
17+
18+
stringifyOptions() {
19+
return {
20+
arrayFormat: 'indices'
21+
}
22+
}
23+
}

yarn.lock

+5
Original file line numberDiff line numberDiff line change
@@ -1448,6 +1448,11 @@
14481448
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
14491449
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
14501450

1451+
"@types/qs@^6.9.7":
1452+
version "6.9.7"
1453+
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb"
1454+
integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==
1455+
14511456
"@types/retry@^0.12.0":
14521457
version "0.12.0"
14531458
resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d"

0 commit comments

Comments
 (0)