Skip to content

Commit 4345c98

Browse files
authored
feat: add persistOptions to handle relation persistence (#121)
1 parent 56e1f1a commit 4345c98

File tree

6 files changed

+163
-31
lines changed

6 files changed

+163
-31
lines changed

docs/guide/configurations.md

+24-2
Original file line numberDiff line numberDiff line change
@@ -137,11 +137,11 @@ In addition to [axios request options](https://github.com/axios/axios#request-co
137137

138138
### `persistBy`
139139

140+
> Since 0.9.3+
141+
140142
- **Type**: `string`
141143
- **Default**: `'insertOrUpdate'`
142144

143-
> Since 0.9.3+
144-
145145
This option determines which Vuex ORM persist method should be called when Vuex ORM Axios attempts to save the response data to the store.
146146

147147
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
151151
- `update`
152152
- `insertOrUpdate` (default)
153153

154+
### `persistOptions`
155+
156+
> Since 0.9.3+
157+
158+
- **Type**: `Object`
159+
160+
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.
161+
162+
It's particularly useful when used together with the [`persistBy`](#persistby) option:
163+
164+
```js
165+
User.api().get('/api/users', {
166+
persistBy: 'create',
167+
persistOptions: {
168+
insert: ['posts'],
169+
insertOrUpdate: ['roles']
170+
}
171+
})
172+
```
173+
174+
**See also**: [Vuex ORM - Insert Method for Relationships](https://vuex-orm.org/guide/data/inserting-and-updating.html#insert-method-for-relationships)
175+
154176
### `save`
155177

156178
- **Type**: `boolean`

docs/guide/usage.md

+13-4
Original file line numberDiff line numberDiff line change
@@ -94,19 +94,28 @@ Vuex ORM Axios will automatically save this data to the store, and the users ent
9494

9595
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.
9696

97+
If you do not want to persist response data automatically, you can defer persistence by configuring the request with the `{ save: false }` option.
98+
9799
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:
98100

99101
```js
100-
User.api().get('url', { persistBy: 'create' })
102+
User.api().get('/api/users', { persistBy: 'create' })
101103
```
102104

103-
If you do not want to persist response data automatically, you can defer persistence by configuring the request with the `{ save: false }` option.
105+
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.
106+
107+
```js
108+
User.api().get('/api/users', {
109+
persistOptions: {
110+
insert: ['posts']
111+
}
112+
})
113+
```
104114

105115
**See also**:
106116

107117
- [Deferring Persistence](#deferring-persistence)
108-
- [Configurations - persistBy](configurations.md#persistby)
109-
- [Vuex ORM - Data - Inserting & Updating](https://vuex-orm.org/guide/data/inserting-and-updating.html#insert-or-update)
118+
- [Vuex ORM - Inserting & Updating](https://vuex-orm.org/guide/data/inserting-and-updating.html#insert-or-update)
110119

111120
### Delete Requests
112121

src/api/Request.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ export class Request {
1313
* The default config.
1414
*/
1515
config: Config = {
16-
save: true,
17-
persistBy: 'insertOrUpdate'
16+
save: true
1817
}
1918

2019
/**

src/api/Response.ts

+27-23
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { AxiosResponse } from 'axios'
22
import { Model, Record, Collections } from '@vuex-orm/core'
3-
import { Config, PersistMethods } from '../contracts/Config'
3+
import { Config, PersistMethods, PersistOptions } from '../contracts/Config'
44

55
export class Response {
66
/**
@@ -54,9 +54,9 @@ export class Response {
5454
return
5555
}
5656

57-
let method = this.config.persistBy as PersistMethods
57+
let method: PersistMethods = this.config.persistBy || 'insertOrUpdate'
5858

59-
if (!this.validatePersistMethod(method)) {
59+
if (!this.validatePersistAction(method)) {
6060
console.warn(
6161
'[Vuex ORM Axios] The "persistBy" option configured is not a ' +
6262
'recognized value. Response data will be persisted by the ' +
@@ -66,25 +66,11 @@ export class Response {
6666
method = 'insertOrUpdate'
6767
}
6868

69-
this.entities = await this.persist(method, { data })
69+
const options = this.getPersistOptions()
7070

71-
this.isSaved = true
72-
}
71+
this.entities = await this.model[method as string]({ data, ...options })
7372

74-
/**
75-
* Determine the method to be used to persist the payload to the store.
76-
*/
77-
persist(method: PersistMethods, payload: any): Promise<Collections> {
78-
switch (method) {
79-
case 'create':
80-
return this.model.create(payload)
81-
case 'insert':
82-
return this.model.insert(payload)
83-
case 'update':
84-
return this.model.update(payload)
85-
case 'insertOrUpdate':
86-
return this.model.insertOrUpdate(payload)
87-
}
73+
this.isSaved = true
8874
}
8975

9076
/**
@@ -118,18 +104,36 @@ export class Response {
118104
return this.response.data
119105
}
120106

107+
/**
108+
* Get persist options if any set in config.
109+
*/
110+
protected getPersistOptions(): PersistOptions | undefined {
111+
const persistOptions = this.config.persistOptions
112+
113+
if (!persistOptions || typeof persistOptions !== 'object') {
114+
return
115+
}
116+
117+
return Object.keys(persistOptions)
118+
.filter(this.validatePersistAction) // Filter to avoid polluting the payload.
119+
.reduce((carry, key) => {
120+
carry[key] = persistOptions[key]
121+
return carry
122+
}, {})
123+
}
124+
121125
/**
122126
* Validate the given data to ensure the Vuex ORM persist methods accept it.
123127
*/
124-
private validateData(data: any): data is Record | Record[] {
128+
protected validateData(data: any): data is Record | Record[] {
125129
return data !== null && typeof data === 'object'
126130
}
127131

128132
/**
129133
* Validate the given string as to ensure it correlates with the available
130134
* Vuex ORM persist methods.
131135
*/
132-
private validatePersistMethod(method: string): method is PersistMethods {
133-
return ['create', 'insert', 'update', 'insertOrUpdate'].includes(method)
136+
protected validatePersistAction(action: string): action is PersistMethods {
137+
return ['create', 'insert', 'update', 'insertOrUpdate'].includes(action)
134138
}
135139
}

src/contracts/Config.ts

+3
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@ import { Model, Record } from '@vuex-orm/core'
33

44
export type PersistMethods = 'create' | 'insert' | 'update' | 'insertOrUpdate'
55

6+
export type PersistOptions = { [P in PersistMethods]?: string[] }
7+
68
export interface Config extends AxiosRequestConfig {
79
dataKey?: string
810
dataTransformer?: (response: AxiosResponse) => Record | Record[]
911
save?: boolean
1012
persistBy?: PersistMethods
13+
persistOptions?: PersistOptions
1114
delete?: string | number | ((model: Model) => boolean)
1215
actions?: {
1316
[name: string]: any
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import axios from 'axios'
2+
import MockAdapter from 'axios-mock-adapter'
3+
import { createStore, assertState, fillState } from 'test/support/Helpers'
4+
import { Model } from '@vuex-orm/core'
5+
6+
describe('Feature - Response - Persist Options', () => {
7+
let mock: MockAdapter
8+
9+
class Post extends Model {
10+
static entity = 'posts'
11+
12+
static fields() {
13+
return {
14+
id: this.attr(null),
15+
user: this.hasOne(User, 'post_id'),
16+
comments: this.hasMany(Comment, 'post_id')
17+
}
18+
}
19+
}
20+
21+
class User extends Model {
22+
static entity = 'users'
23+
24+
static fields() {
25+
return {
26+
id: this.attr(null),
27+
post_id: this.attr(null),
28+
name: this.string('')
29+
}
30+
}
31+
}
32+
33+
class Comment extends Model {
34+
static entity = 'comments'
35+
36+
static fields() {
37+
return {
38+
id: this.attr(null),
39+
post_id: this.attr(null)
40+
}
41+
}
42+
}
43+
44+
beforeEach(() => {
45+
mock = new MockAdapter(axios)
46+
})
47+
afterEach(() => {
48+
mock.reset()
49+
})
50+
51+
it('should support persist options for relations', async () => {
52+
mock.onGet('/api/posts').reply(200, {
53+
id: 1,
54+
user: {
55+
id: 1,
56+
name: 'Johnny Doe'
57+
},
58+
comments: [{ id: 2 }]
59+
})
60+
61+
const store = createStore([User, Post, Comment])
62+
63+
fillState(store, {
64+
posts: {
65+
1: { $id: '1', id: 1, user: null, comments: [] }
66+
},
67+
users: {
68+
1: { $id: '1', id: 1, post_id: 1, name: 'John Doe' }
69+
},
70+
comments: {
71+
1: { $id: '1', id: 1, post_id: 1 }
72+
}
73+
})
74+
75+
await Post.api().get('/api/posts', {
76+
persistOptions: {
77+
insert: ['comments'],
78+
update: ['users']
79+
}
80+
})
81+
82+
assertState(store, {
83+
posts: {
84+
1: { $id: '1', id: 1, user: null, comments: [] }
85+
},
86+
users: {
87+
1: { $id: '1', id: 1, post_id: 1, name: 'Johnny Doe' }
88+
},
89+
comments: {
90+
1: { $id: '1', id: 1, post_id: 1 },
91+
2: { $id: '2', id: 2, post_id: 1 }
92+
}
93+
})
94+
})
95+
})

0 commit comments

Comments
 (0)