Skip to content

Commit

Permalink
Merge pull request #84 from vuex-orm/response-save-method
Browse files Browse the repository at this point in the history
Add the save method to the response object
  • Loading branch information
kiaking authored Nov 5, 2019
2 parents 2f0750b + afe646f commit efe2e8b
Show file tree
Hide file tree
Showing 9 changed files with 286 additions and 36 deletions.
3 changes: 2 additions & 1 deletion docs/.vuepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ const sidebars = {
collapsable: false,
children: [
'/api/model',
'/api/request'
'/api/request',
'/api/response'
]
}
]
Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Vuex ORM is sponsored by awesome folks. Big love to all of them from whole Vuex
- API
- [Model](/api/model)
- [Request](/api/request)
- [Response](/api/response)

## Questions & Discussions

Expand Down
53 changes: 53 additions & 0 deletions docs/api/response.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
sidebarDepth: 2
---

# Response

The Response object is what gets returned when you make API call via Request object.

## Instance Properties

### response

- **`response: AxiosResponse`**

Please refer to the [Axios documentation](https://github.com/axios/axios#response-schema) for more details.

### entities

- **`entities: Collections | null`**

The result of Vuex ORM persistent method.

### isSaved

- **`isSaved: boolean`**

Whether the response data is persisted to the store or not.

### model

- **`model: typeof Model`**

The Model class that was attached to the Request instance when making an API call.

### config

- **`config: Config`**

The configuration which was passed to the Request instance.

## Instance Methods

### save

- **`save (): Promise<void>`**

Save response data to the store.

### delete

- **`delete (): Promise<void>`**

Delete store record depending on `delete` option. If the `delete` option is not specified at the config, it will throw an error.
1 change: 1 addition & 0 deletions docs/guide/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Vuex ORM is sponsored by awesome folks. Big love to all of them from whole Vuex
- API
- [Model](/api/model)
- [Request](/api/request)
- [Response](/api/response)

## Questions & Discussions

Expand Down
45 changes: 45 additions & 0 deletions docs/guide/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,48 @@ result.response.status // <- 200
// And Vuex ORM persisted entities like so.
result.entities // <- { users: [{ ... }] }
```

### Saving Data Afterwards

When setting the [save option](./configurations#available-options) to false, you can persist response data afterwards via `save` method on response object.

```js
// Don't save response data when calling API.
const result = await User.api().get('/api/users', {
save: false
})

// Save data afterwards.
result.save()
````

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.

```js
// Don't save response data when calling API.
const result = await User.api().get('/api/users', {
save: false
})
// If the response data contains any error, don't persist in the store.
if (result.response.data.error) {
throw new Error('Something is wrong.')
}
// Persist in the store otherwise.
result.save()
````
You can check to see if the response data is already stored in the store or not with `isSaved` property.
```js
const result = await User.api().get('/api/users', {
save: false
})

result.isSaved // <- false

result.save()

result.isSaved // <- true
```
42 changes: 11 additions & 31 deletions src/api/Request.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AxiosInstance, AxiosResponse } from 'axios'
import { Model, Record, Collections } from '@vuex-orm/core'
import { Model } from '@vuex-orm/core'
import Config from '../contracts/Config'
import Response from './Response'

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

const response = await this.axios.request(requestConfig)
const axiosResponse = await this.axios.request(requestConfig)

const entities = await this.persistResponseData(response, requestConfig)

return new Response(this.model, requestConfig, response, entities)
return this.createResponse(axiosResponse, requestConfig)
}

/**
Expand All @@ -138,38 +136,20 @@ export default class Request {
}

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

if (config.delete !== undefined) {
await this.model.delete(config.delete as any)
await response.delete()

return null
return response
}

return this.model.insertOrUpdate({
data: this.getDataFromResponse(response, config)
})
}

/**
* Get data from the given response object. If the `dataTransformer` config is
* provided, it tries to execute the method with the response as param. If the
* `dataKey` config is provided, it tries to fetch the data at that key.
*/
private getDataFromResponse (response: AxiosResponse, config: Config): Record | Record[] {
if (config.dataTransformer) {
return config.dataTransformer(response)
}

if (config.dataKey) {
return response.data[config.dataKey]
}
config.save && response.save()

return response.data
return response
}
}
51 changes: 47 additions & 4 deletions src/api/Response.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AxiosResponse } from 'axios'
import { Model, Collections } from '@vuex-orm/core'
import { Model, Record, Collections } from '@vuex-orm/core'
import Config from '../contracts/Config'

export default class Response {
Expand All @@ -21,15 +21,58 @@ export default class Response {
/**
* Entities created by Vuex ORM.
*/
entities: Collections | null
entities: Collections | null = null

/**
* Whether if response data is saved to the store or not.
*/
isSaved: boolean = false

/**
* Create a new response instance.
*/
constructor (model: typeof Model, config: Config, response: AxiosResponse, entities: Collections | null) {
constructor (model: typeof Model, config: Config, response: AxiosResponse) {
this.model = model
this.config = config
this.response = response
this.entities = entities
}

/**
* Save response data to the store.
*/
async save (): Promise<void> {
this.entities = await this.model.insertOrUpdate({
data: this.getDataFromResponse()
})

this.isSaved = true
}

/**
* Delete store record depending on `delete` option.
*/
async delete (): Promise<void> {
if (this.config.delete === undefined) {
throw new Error('[Vuex ORM Axios] Could not delete records because the `delete` option is not set.')
}

await this.model.delete(this.config.delete as any)
}

/**
* Get data from the given response object. If the `dataTransformer` config is
* provided, it tries to execute the method with the response as param. If the
* `dataKey` config is provided, it tries to fetch the data at that key.
*/
private getDataFromResponse (): Record | Record[] {
if (this.config.dataTransformer) {
return this.config.dataTransformer(this.response)
}

if (this.config.dataKey) {
return this.response.data[this.config.dataKey]
}

return this.response.data
}
}
66 changes: 66 additions & 0 deletions test/feature/Response_Delete.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import axios from 'axios'
import MockAdapter from 'axios-mock-adapter'
import { createStore, createState } from 'test/support/Helpers'
import { Model, Fields } from '@vuex-orm/core'

describe('Feature - Response - Save', () => {
let mock: MockAdapter

class User extends Model {
static entity = 'users'

static fields (): Fields {
return {
id: this.attr(null),
name: this.attr('')
}
}
}

beforeEach(() => { mock = new MockAdapter(axios) })
afterEach(() => { mock.reset() })

it('can save response data afterword', async () => {
mock.onGet('/api/users').reply(200, { id: 1, name: 'John Doe' })

const store = createStore([User])

const response = await User.api().get('/api/users')

const expected1 = createState({
users: {
1: { $id: 1, id: 1, name: 'John Doe' }
}
})

expect(store.state.entities).toEqual(expected1)

response.config.delete = 1

await response.delete()

const expected2 = createState({
users: {}
})

expect(store.state.entities).toEqual(expected2)
})

it('throws error if `delete` option is not set', async () => {
mock.onGet('/api/users').reply(200, { id: 1, name: 'John Doe' })

createStore([User])

const response = await User.api().get('/api/users')

try {
await response.delete()
} catch (e) {
expect(e.message).toBe('[Vuex ORM Axios] Could not delete records because the `delete` option is not set.')

return
}

throw new Error('Error was not thrown')
})
})
60 changes: 60 additions & 0 deletions test/feature/Response_Save.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import axios from 'axios'
import MockAdapter from 'axios-mock-adapter'
import { createStore, createState } from 'test/support/Helpers'
import { Model, Fields } from '@vuex-orm/core'

describe('Feature - Response - Save', () => {
let mock: MockAdapter

class User extends Model {
static entity = 'users'

static fields (): Fields {
return {
id: this.attr(null),
name: this.attr('')
}
}
}

beforeEach(() => { mock = new MockAdapter(axios) })
afterEach(() => { mock.reset() })

it('can save response data afterword', async () => {
mock.onGet('/api/users').reply(200, { id: 1, name: 'John Doe' })

const store = createStore([User])

const response = await User.api().get('/api/users', { save: false })

const expected1 = createState({
users: {}
})

expect(store.state.entities).toEqual(expected1)

await response.save()

const expected2 = createState({
users: {
1: { $id: 1, id: 1, name: 'John Doe' }
}
})

expect(store.state.entities).toEqual(expected2)
})

it('sets `isSaved` flag', async () => {
mock.onGet('/api/users').reply(200, { id: 1, name: 'John Doe' })

createStore([User])

const response = await User.api().get('/api/users', { save: false })

expect(response.isSaved).toEqual(false)

await response.save()

expect(response.isSaved).toEqual(true)
})
})

0 comments on commit efe2e8b

Please sign in to comment.