diff --git a/docs/guide/configurations.md b/docs/guide/configurations.md index ca2361d..5032355 100644 --- a/docs/guide/configurations.md +++ b/docs/guide/configurations.md @@ -137,11 +137,11 @@ In addition to [axios request options](https://github.com/axios/axios#request-co ### `persistBy` + > Since 0.9.3+ + - **Type**: `string` - **Default**: `'insertOrUpdate'` - > Since 0.9.3+ - This option determines which Vuex ORM persist method should be called when Vuex ORM Axios attempts to save the response data to the store. You can set this option to any one of the following string values: @@ -151,6 +151,28 @@ In addition to [axios request options](https://github.com/axios/axios#request-co - `update` - `insertOrUpdate` (default) +### `persistOptions` + + > Since 0.9.3+ + +- **Type**: `Object` + + This option can be configured to control the persistence of relational data. Persist options are passed on to the persist method in the same manner Vuex ORM handles these options. + + It's particularly useful when used together with the [`persistBy`](#persistby) option: + + ```js + User.api().get('/api/users', { + persistBy: 'create', + persistOptions: { + insert: ['posts'], + insertOrUpdate: ['roles'] + } + }) + ``` + + **See also**: [Vuex ORM - Insert Method for Relationships](https://vuex-orm.org/guide/data/inserting-and-updating.html#insert-method-for-relationships) + ### `save` - **Type**: `boolean` diff --git a/docs/guide/usage.md b/docs/guide/usage.md index eee9691..f2952d8 100644 --- a/docs/guide/usage.md +++ b/docs/guide/usage.md @@ -94,19 +94,28 @@ Vuex ORM Axios will automatically save this data to the store, and the users ent Under the hood, the plugin will persist data to the store by determining which records require inserting and which require updating. To accomplish this, the plugin passes data to the Vuex ORM `insertOrUpdate` model method. Therefore, only valid model attributes will be persisted to the store. +If you do not want to persist response data automatically, you can defer persistence by configuring the request with the `{ save: false }` option. + As of 0.9.3+ you may configure Vuex ORM Axios to persist data using an alternative Vuex ORM persist method other than the default `insertOrUpdate`. For example, you can refresh entities by passing the `persistBy` option as `'create'` which will persist data using the model's `create` method: ```js -User.api().get('url', { persistBy: 'create' }) +User.api().get('/api/users', { persistBy: 'create' }) ``` -If you do not want to persist response data automatically, you can defer persistence by configuring the request with the `{ save: false }` option. +In addition, you can control how relations are persisted by passing the `persistOptions` option. Learn more about [Insert Method for Relationships](https://vuex-orm.org/guide/data/inserting-and-updating.html#insert-method-for-relationships) in the Vuex ORM documentation. + +```js +User.api().get('/api/users', { + persistOptions: { + insert: ['posts'] + } +}) +``` **See also**: - [Deferring Persistence](#deferring-persistence) -- [Configurations - persistBy](configurations.md#persistby) -- [Vuex ORM - Data - Inserting & Updating](https://vuex-orm.org/guide/data/inserting-and-updating.html#insert-or-update) +- [Vuex ORM - Inserting & Updating](https://vuex-orm.org/guide/data/inserting-and-updating.html#insert-or-update) ### Delete Requests diff --git a/src/api/Request.ts b/src/api/Request.ts index 2af9e7d..499c647 100644 --- a/src/api/Request.ts +++ b/src/api/Request.ts @@ -13,8 +13,7 @@ export class Request { * The default config. */ config: Config = { - save: true, - persistBy: 'insertOrUpdate' + save: true } /** diff --git a/src/api/Response.ts b/src/api/Response.ts index d3a7fc8..6a94654 100644 --- a/src/api/Response.ts +++ b/src/api/Response.ts @@ -1,6 +1,6 @@ import { AxiosResponse } from 'axios' import { Model, Record, Collections } from '@vuex-orm/core' -import { Config, PersistMethods } from '../contracts/Config' +import { Config, PersistMethods, PersistOptions } from '../contracts/Config' export class Response { /** @@ -54,9 +54,9 @@ export class Response { return } - let method = this.config.persistBy as PersistMethods + let method: PersistMethods = this.config.persistBy || 'insertOrUpdate' - if (!this.validatePersistMethod(method)) { + if (!this.validatePersistAction(method)) { console.warn( '[Vuex ORM Axios] The "persistBy" option configured is not a ' + 'recognized value. Response data will be persisted by the ' + @@ -66,25 +66,11 @@ export class Response { method = 'insertOrUpdate' } - this.entities = await this.persist(method, { data }) + const options = this.getPersistOptions() - this.isSaved = true - } + this.entities = await this.model[method as string]({ data, ...options }) - /** - * Determine the method to be used to persist the payload to the store. - */ - persist(method: PersistMethods, payload: any): Promise { - switch (method) { - case 'create': - return this.model.create(payload) - case 'insert': - return this.model.insert(payload) - case 'update': - return this.model.update(payload) - case 'insertOrUpdate': - return this.model.insertOrUpdate(payload) - } + this.isSaved = true } /** @@ -118,10 +104,28 @@ export class Response { return this.response.data } + /** + * Get persist options if any set in config. + */ + protected getPersistOptions(): PersistOptions | undefined { + const persistOptions = this.config.persistOptions + + if (!persistOptions || typeof persistOptions !== 'object') { + return + } + + return Object.keys(persistOptions) + .filter(this.validatePersistAction) // Filter to avoid polluting the payload. + .reduce((carry, key) => { + carry[key] = persistOptions[key] + return carry + }, {}) + } + /** * Validate the given data to ensure the Vuex ORM persist methods accept it. */ - private validateData(data: any): data is Record | Record[] { + protected validateData(data: any): data is Record | Record[] { return data !== null && typeof data === 'object' } @@ -129,7 +133,7 @@ export class Response { * Validate the given string as to ensure it correlates with the available * Vuex ORM persist methods. */ - private validatePersistMethod(method: string): method is PersistMethods { - return ['create', 'insert', 'update', 'insertOrUpdate'].includes(method) + protected validatePersistAction(action: string): action is PersistMethods { + return ['create', 'insert', 'update', 'insertOrUpdate'].includes(action) } } diff --git a/src/contracts/Config.ts b/src/contracts/Config.ts index 78f2ab9..49cdf52 100644 --- a/src/contracts/Config.ts +++ b/src/contracts/Config.ts @@ -3,11 +3,14 @@ import { Model, Record } from '@vuex-orm/core' export type PersistMethods = 'create' | 'insert' | 'update' | 'insertOrUpdate' +export type PersistOptions = { [P in PersistMethods]?: string[] } + export interface Config extends AxiosRequestConfig { dataKey?: string dataTransformer?: (response: AxiosResponse) => Record | Record[] save?: boolean persistBy?: PersistMethods + persistOptions?: PersistOptions delete?: string | number | ((model: Model) => boolean) actions?: { [name: string]: any diff --git a/test/feature/Response_PersistOptions.spec.ts b/test/feature/Response_PersistOptions.spec.ts new file mode 100644 index 0000000..4968b5f --- /dev/null +++ b/test/feature/Response_PersistOptions.spec.ts @@ -0,0 +1,95 @@ +import axios from 'axios' +import MockAdapter from 'axios-mock-adapter' +import { createStore, assertState, fillState } from 'test/support/Helpers' +import { Model } from '@vuex-orm/core' + +describe('Feature - Response - Persist Options', () => { + let mock: MockAdapter + + class Post extends Model { + static entity = 'posts' + + static fields() { + return { + id: this.attr(null), + user: this.hasOne(User, 'post_id'), + comments: this.hasMany(Comment, 'post_id') + } + } + } + + class User extends Model { + static entity = 'users' + + static fields() { + return { + id: this.attr(null), + post_id: this.attr(null), + name: this.string('') + } + } + } + + class Comment extends Model { + static entity = 'comments' + + static fields() { + return { + id: this.attr(null), + post_id: this.attr(null) + } + } + } + + beforeEach(() => { + mock = new MockAdapter(axios) + }) + afterEach(() => { + mock.reset() + }) + + it('should support persist options for relations', async () => { + mock.onGet('/api/posts').reply(200, { + id: 1, + user: { + id: 1, + name: 'Johnny Doe' + }, + comments: [{ id: 2 }] + }) + + const store = createStore([User, Post, Comment]) + + fillState(store, { + posts: { + 1: { $id: '1', id: 1, user: null, comments: [] } + }, + users: { + 1: { $id: '1', id: 1, post_id: 1, name: 'John Doe' } + }, + comments: { + 1: { $id: '1', id: 1, post_id: 1 } + } + }) + + await Post.api().get('/api/posts', { + persistOptions: { + insert: ['comments'], + update: ['users'] + } + }) + + assertState(store, { + posts: { + 1: { $id: '1', id: 1, user: null, comments: [] } + }, + users: { + 1: { $id: '1', id: 1, post_id: 1, name: 'Johnny Doe' } + }, + comments: { + 1: { $id: '1', id: 1, post_id: 1 }, + 2: { $id: '2', id: 2, post_id: 1 } + } + }) + }) +})