Skip to content

Commit efe2e8b

Browse files
authored
Merge pull request #84 from vuex-orm/response-save-method
Add the save method to the response object
2 parents 2f0750b + afe646f commit efe2e8b

File tree

9 files changed

+286
-36
lines changed

9 files changed

+286
-36
lines changed

docs/.vuepress/config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ const sidebars = {
2020
collapsable: false,
2121
children: [
2222
'/api/model',
23-
'/api/request'
23+
'/api/request',
24+
'/api/response'
2425
]
2526
}
2627
]

docs/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Vuex ORM is sponsored by awesome folks. Big love to all of them from whole Vuex
4040
- API
4141
- [Model](/api/model)
4242
- [Request](/api/request)
43+
- [Response](/api/response)
4344

4445
## Questions & Discussions
4546

docs/api/response.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
---
2+
sidebarDepth: 2
3+
---
4+
5+
# Response
6+
7+
The Response object is what gets returned when you make API call via Request object.
8+
9+
## Instance Properties
10+
11+
### response
12+
13+
- **`response: AxiosResponse`**
14+
15+
Please refer to the [Axios documentation](https://github.com/axios/axios#response-schema) for more details.
16+
17+
### entities
18+
19+
- **`entities: Collections | null`**
20+
21+
The result of Vuex ORM persistent method.
22+
23+
### isSaved
24+
25+
- **`isSaved: boolean`**
26+
27+
Whether the response data is persisted to the store or not.
28+
29+
### model
30+
31+
- **`model: typeof Model`**
32+
33+
The Model class that was attached to the Request instance when making an API call.
34+
35+
### config
36+
37+
- **`config: Config`**
38+
39+
The configuration which was passed to the Request instance.
40+
41+
## Instance Methods
42+
43+
### save
44+
45+
- **`save (): Promise<void>`**
46+
47+
Save response data to the store.
48+
49+
### delete
50+
51+
- **`delete (): Promise<void>`**
52+
53+
Delete store record depending on `delete` option. If the `delete` option is not specified at the config, it will throw an error.

docs/guide/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Vuex ORM is sponsored by awesome folks. Big love to all of them from whole Vuex
4040
- API
4141
- [Model](/api/model)
4242
- [Request](/api/request)
43+
- [Response](/api/response)
4344

4445
## Questions & Discussions
4546

docs/guide/usage.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,48 @@ result.response.status // <- 200
8686
// And Vuex ORM persisted entities like so.
8787
result.entities // <- { users: [{ ... }] }
8888
```
89+
90+
### Saving Data Afterwards
91+
92+
When setting the [save option](./configurations#available-options) to false, you can persist response data afterwards via `save` method on response object.
93+
94+
```js
95+
// Don't save response data when calling API.
96+
const result = await User.api().get('/api/users', {
97+
save: false
98+
})
99+
100+
// Save data afterwards.
101+
result.save()
102+
````
103+
104+
This can be useful when you want to check the response data before persisting it to the store. For example, you might check if the response contains any errors or not.
105+
106+
```js
107+
// Don't save response data when calling API.
108+
const result = await User.api().get('/api/users', {
109+
save: false
110+
})
111+
112+
// If the response data contains any error, don't persist in the store.
113+
if (result.response.data.error) {
114+
throw new Error('Something is wrong.')
115+
}
116+
117+
// Persist in the store otherwise.
118+
result.save()
119+
````
120+
121+
You can check to see if the response data is already stored in the store or not with `isSaved` property.
122+
123+
```js
124+
const result = await User.api().get('/api/users', {
125+
save: false
126+
})
127+
128+
result.isSaved // <- false
129+
130+
result.save()
131+
132+
result.isSaved // <- true
133+
```

src/api/Request.ts

Lines changed: 11 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { AxiosInstance, AxiosResponse } from 'axios'
2-
import { Model, Record, Collections } from '@vuex-orm/core'
2+
import { Model } from '@vuex-orm/core'
33
import Config from '../contracts/Config'
44
import Response from './Response'
55

@@ -117,11 +117,9 @@ export default class Request {
117117
async request (config: Config): Promise<Response> {
118118
const requestConfig = this.createConfig(config)
119119

120-
const response = await this.axios.request(requestConfig)
120+
const axiosResponse = await this.axios.request(requestConfig)
121121

122-
const entities = await this.persistResponseData(response, requestConfig)
123-
124-
return new Response(this.model, requestConfig, response, entities)
122+
return this.createResponse(axiosResponse, requestConfig)
125123
}
126124

127125
/**
@@ -138,38 +136,20 @@ export default class Request {
138136
}
139137

140138
/**
141-
* Persist the response data to the vuex store.
139+
* Create a new response instance by applying a few initialization processes.
140+
* For example, it saves response data if `save` option id set to `true`.
142141
*/
143-
private async persistResponseData (response: AxiosResponse, config: Config): Promise<Collections | null> {
144-
if (!config.save) {
145-
return null
146-
}
142+
private async createResponse (axiosResponse: AxiosResponse, config: Config): Promise<Response> {
143+
const response = new Response(this.model, config, axiosResponse)
147144

148145
if (config.delete !== undefined) {
149-
await this.model.delete(config.delete as any)
146+
await response.delete()
150147

151-
return null
148+
return response
152149
}
153150

154-
return this.model.insertOrUpdate({
155-
data: this.getDataFromResponse(response, config)
156-
})
157-
}
158-
159-
/**
160-
* Get data from the given response object. If the `dataTransformer` config is
161-
* provided, it tries to execute the method with the response as param. If the
162-
* `dataKey` config is provided, it tries to fetch the data at that key.
163-
*/
164-
private getDataFromResponse (response: AxiosResponse, config: Config): Record | Record[] {
165-
if (config.dataTransformer) {
166-
return config.dataTransformer(response)
167-
}
168-
169-
if (config.dataKey) {
170-
return response.data[config.dataKey]
171-
}
151+
config.save && response.save()
172152

173-
return response.data
153+
return response
174154
}
175155
}

src/api/Response.ts

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { AxiosResponse } from 'axios'
2-
import { Model, Collections } from '@vuex-orm/core'
2+
import { Model, Record, Collections } from '@vuex-orm/core'
33
import Config from '../contracts/Config'
44

55
export default class Response {
@@ -21,15 +21,58 @@ export default class Response {
2121
/**
2222
* Entities created by Vuex ORM.
2323
*/
24-
entities: Collections | null
24+
entities: Collections | null = null
25+
26+
/**
27+
* Whether if response data is saved to the store or not.
28+
*/
29+
isSaved: boolean = false
2530

2631
/**
2732
* Create a new response instance.
2833
*/
29-
constructor (model: typeof Model, config: Config, response: AxiosResponse, entities: Collections | null) {
34+
constructor (model: typeof Model, config: Config, response: AxiosResponse) {
3035
this.model = model
3136
this.config = config
3237
this.response = response
33-
this.entities = entities
38+
}
39+
40+
/**
41+
* Save response data to the store.
42+
*/
43+
async save (): Promise<void> {
44+
this.entities = await this.model.insertOrUpdate({
45+
data: this.getDataFromResponse()
46+
})
47+
48+
this.isSaved = true
49+
}
50+
51+
/**
52+
* Delete store record depending on `delete` option.
53+
*/
54+
async delete (): Promise<void> {
55+
if (this.config.delete === undefined) {
56+
throw new Error('[Vuex ORM Axios] Could not delete records because the `delete` option is not set.')
57+
}
58+
59+
await this.model.delete(this.config.delete as any)
60+
}
61+
62+
/**
63+
* Get data from the given response object. If the `dataTransformer` config is
64+
* provided, it tries to execute the method with the response as param. If the
65+
* `dataKey` config is provided, it tries to fetch the data at that key.
66+
*/
67+
private getDataFromResponse (): Record | Record[] {
68+
if (this.config.dataTransformer) {
69+
return this.config.dataTransformer(this.response)
70+
}
71+
72+
if (this.config.dataKey) {
73+
return this.response.data[this.config.dataKey]
74+
}
75+
76+
return this.response.data
3477
}
3578
}

test/feature/Response_Delete.spec.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import axios from 'axios'
2+
import MockAdapter from 'axios-mock-adapter'
3+
import { createStore, createState } from 'test/support/Helpers'
4+
import { Model, Fields } from '@vuex-orm/core'
5+
6+
describe('Feature - Response - Save', () => {
7+
let mock: MockAdapter
8+
9+
class User extends Model {
10+
static entity = 'users'
11+
12+
static fields (): Fields {
13+
return {
14+
id: this.attr(null),
15+
name: this.attr('')
16+
}
17+
}
18+
}
19+
20+
beforeEach(() => { mock = new MockAdapter(axios) })
21+
afterEach(() => { mock.reset() })
22+
23+
it('can save response data afterword', async () => {
24+
mock.onGet('/api/users').reply(200, { id: 1, name: 'John Doe' })
25+
26+
const store = createStore([User])
27+
28+
const response = await User.api().get('/api/users')
29+
30+
const expected1 = createState({
31+
users: {
32+
1: { $id: 1, id: 1, name: 'John Doe' }
33+
}
34+
})
35+
36+
expect(store.state.entities).toEqual(expected1)
37+
38+
response.config.delete = 1
39+
40+
await response.delete()
41+
42+
const expected2 = createState({
43+
users: {}
44+
})
45+
46+
expect(store.state.entities).toEqual(expected2)
47+
})
48+
49+
it('throws error if `delete` option is not set', async () => {
50+
mock.onGet('/api/users').reply(200, { id: 1, name: 'John Doe' })
51+
52+
createStore([User])
53+
54+
const response = await User.api().get('/api/users')
55+
56+
try {
57+
await response.delete()
58+
} catch (e) {
59+
expect(e.message).toBe('[Vuex ORM Axios] Could not delete records because the `delete` option is not set.')
60+
61+
return
62+
}
63+
64+
throw new Error('Error was not thrown')
65+
})
66+
})

test/feature/Response_Save.spec.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import axios from 'axios'
2+
import MockAdapter from 'axios-mock-adapter'
3+
import { createStore, createState } from 'test/support/Helpers'
4+
import { Model, Fields } from '@vuex-orm/core'
5+
6+
describe('Feature - Response - Save', () => {
7+
let mock: MockAdapter
8+
9+
class User extends Model {
10+
static entity = 'users'
11+
12+
static fields (): Fields {
13+
return {
14+
id: this.attr(null),
15+
name: this.attr('')
16+
}
17+
}
18+
}
19+
20+
beforeEach(() => { mock = new MockAdapter(axios) })
21+
afterEach(() => { mock.reset() })
22+
23+
it('can save response data afterword', async () => {
24+
mock.onGet('/api/users').reply(200, { id: 1, name: 'John Doe' })
25+
26+
const store = createStore([User])
27+
28+
const response = await User.api().get('/api/users', { save: false })
29+
30+
const expected1 = createState({
31+
users: {}
32+
})
33+
34+
expect(store.state.entities).toEqual(expected1)
35+
36+
await response.save()
37+
38+
const expected2 = createState({
39+
users: {
40+
1: { $id: 1, id: 1, name: 'John Doe' }
41+
}
42+
})
43+
44+
expect(store.state.entities).toEqual(expected2)
45+
})
46+
47+
it('sets `isSaved` flag', async () => {
48+
mock.onGet('/api/users').reply(200, { id: 1, name: 'John Doe' })
49+
50+
createStore([User])
51+
52+
const response = await User.api().get('/api/users', { save: false })
53+
54+
expect(response.isSaved).toEqual(false)
55+
56+
await response.save()
57+
58+
expect(response.isSaved).toEqual(true)
59+
})
60+
})

0 commit comments

Comments
 (0)