Skip to content

Commit

Permalink
feat: add persistOptions to handle relation persistence (#121)
Browse files Browse the repository at this point in the history
  • Loading branch information
cuebit authored May 5, 2020
1 parent 56e1f1a commit 4345c98
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 31 deletions.
26 changes: 24 additions & 2 deletions docs/guide/configurations.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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`
Expand Down
17 changes: 13 additions & 4 deletions docs/guide/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 1 addition & 2 deletions src/api/Request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ export class Request {
* The default config.
*/
config: Config = {
save: true,
persistBy: 'insertOrUpdate'
save: true
}

/**
Expand Down
50 changes: 27 additions & 23 deletions src/api/Response.ts
Original file line number Diff line number Diff line change
@@ -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 {
/**
Expand Down Expand Up @@ -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 ' +
Expand All @@ -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<Collections> {
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
}

/**
Expand Down Expand Up @@ -118,18 +104,36 @@ 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'
}

/**
* 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)
}
}
3 changes: 3 additions & 0 deletions src/contracts/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
95 changes: 95 additions & 0 deletions test/feature/Response_PersistOptions.spec.ts
Original file line number Diff line number Diff line change
@@ -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 }
}
})
})
})

0 comments on commit 4345c98

Please sign in to comment.