From 1c89fe4b2b708c9dcb54af8c19a260c3c0902cd0 Mon Sep 17 00:00:00 2001 From: Kia Ishii Date: Fri, 1 Nov 2019 19:11:13 +0900 Subject: [PATCH 1/3] Add `save` and `delete` method to Request --- src/api/Request.ts | 42 +++++------------- src/api/Response.ts | 51 +++++++++++++++++++-- test/feature/Response_Delete.spec.ts | 66 ++++++++++++++++++++++++++++ test/feature/Response_Save.spec.ts | 60 +++++++++++++++++++++++++ 4 files changed, 184 insertions(+), 35 deletions(-) create mode 100644 test/feature/Response_Delete.spec.ts create mode 100644 test/feature/Response_Save.spec.ts diff --git a/src/api/Request.ts b/src/api/Request.ts index 06b3a16..4d6e74d 100644 --- a/src/api/Request.ts +++ b/src/api/Request.ts @@ -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' @@ -117,11 +117,9 @@ export default class Request { async request (config: Config): Promise { 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) } /** @@ -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 { - if (!config.save) { - return null - } + private async createResponse (axiosResponse: AxiosResponse, config: Config): Promise { + 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 } } diff --git a/src/api/Response.ts b/src/api/Response.ts index e93cf8f..9b9283c 100644 --- a/src/api/Response.ts +++ b/src/api/Response.ts @@ -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 { @@ -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 { + this.entities = await this.model.insertOrUpdate({ + data: this.getDataFromResponse() + }) + + this.isSaved = true + } + + /** + * Delete store record depending on `delete` option. + */ + async delete (): Promise { + 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 } } diff --git a/test/feature/Response_Delete.spec.ts b/test/feature/Response_Delete.spec.ts new file mode 100644 index 0000000..b213ae8 --- /dev/null +++ b/test/feature/Response_Delete.spec.ts @@ -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') + }) +}) diff --git a/test/feature/Response_Save.spec.ts b/test/feature/Response_Save.spec.ts new file mode 100644 index 0000000..a6b5dbd --- /dev/null +++ b/test/feature/Response_Save.spec.ts @@ -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) + }) +}) From 94528b24fe9d5dc4318c4b3bdc5d30bceecd38b4 Mon Sep 17 00:00:00 2001 From: Kia Ishii Date: Tue, 5 Nov 2019 20:41:09 +0900 Subject: [PATCH 2/3] Add documentations --- docs/.vuepress/config.js | 3 ++- docs/README.md | 1 + docs/api/response.md | 47 ++++++++++++++++++++++++++++++++++++++++ docs/guide/README.md | 1 + docs/guide/usage.md | 31 ++++++++++++++++++++++++++ 5 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 docs/api/response.md diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 472d172..b1782bc 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -20,7 +20,8 @@ const sidebars = { collapsable: false, children: [ '/api/model', - '/api/request' + '/api/request', + '/api/response' ] } ] diff --git a/docs/README.md b/docs/README.md index e0143bb..0cf7e26 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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 diff --git a/docs/api/response.md b/docs/api/response.md new file mode 100644 index 0000000..441cebe --- /dev/null +++ b/docs/api/response.md @@ -0,0 +1,47 @@ +--- +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. + +### 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`** + + Save response data to the store. + +### delete + +- **`delete (): Promise`** + + Delete store record depending on `delete` option. If the `delete` option is not specified at the config, it will throw an error. diff --git a/docs/guide/README.md b/docs/guide/README.md index e0143bb..0cf7e26 100644 --- a/docs/guide/README.md +++ b/docs/guide/README.md @@ -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 diff --git a/docs/guide/usage.md b/docs/guide/usage.md index 8829023..ccc0970 100644 --- a/docs/guide/usage.md +++ b/docs/guide/usage.md @@ -86,3 +86,34 @@ 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() +```` From afe646f99e8cdec3e68e874179ec8a3f358a0e3b Mon Sep 17 00:00:00 2001 From: Kia Ishii Date: Tue, 5 Nov 2019 20:47:23 +0900 Subject: [PATCH 3/3] Add missing documentation for `isSaved` property --- docs/api/response.md | 6 ++++++ docs/guide/usage.md | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/docs/api/response.md b/docs/api/response.md index 441cebe..a8a50af 100644 --- a/docs/api/response.md +++ b/docs/api/response.md @@ -20,6 +20,12 @@ The Response object is what gets returned when you make API call via Request obj 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`** diff --git a/docs/guide/usage.md b/docs/guide/usage.md index ccc0970..77bec83 100644 --- a/docs/guide/usage.md +++ b/docs/guide/usage.md @@ -117,3 +117,17 @@ if (result.response.data.error) { // 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 +```