Skip to content

Commit 9cc1ae6

Browse files
committed
Call setupInstance before storing data.
This update is for scenarios when using Model classes. Any incoming data will be converted to Model instances before it is added to the store. This also makes it so that the model data is setup properly when updates are received from the API server.
1 parent 8ff57fb commit 9cc1ae6

File tree

4 files changed

+106
-106
lines changed

4 files changed

+106
-106
lines changed

src/service-module/make-model.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,15 @@ export default function makeModel(options: FeathersVuexOptions) {
9494

9595
data = data || {}
9696

97+
const existingItem = hasValidId && !options.clone
98+
? getFromStore.call(this.constructor, id)
99+
: null
100+
97101
// If it already exists, update the original and return
98-
if (hasValidId && !options.clone) {
99-
const existingItem = getFromStore.call(this.constructor, id)
100-
if (existingItem) {
101-
_commit.call(this.constructor, 'updateItem', data)
102-
return existingItem
103-
}
102+
if (existingItem) {
103+
data = setupInstance.call(this, data, { models, store }) || data
104+
_commit.call(this.constructor, 'mergeInstance', data)
105+
return existingItem
104106
}
105107

106108
// Mark as a clone
@@ -111,7 +113,7 @@ export default function makeModel(options: FeathersVuexOptions) {
111113
})
112114
}
113115

114-
// Setup instanceDefaults, separate out accessors
116+
// Setup instanceDefaults
115117
if (instanceDefaults && typeof instanceDefaults === 'function') {
116118
const defaults = instanceDefaults.call(this, data, { models, store }) || data
117119
mergeWithAccessors(this, defaults)

src/service-module/service-module.actions.ts

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { getId } from '../utils'
88

99
export default function makeServiceActions(service) {
1010
const serviceActions = {
11-
find({ commit, dispatch }, params) {
11+
async find({ commit, dispatch }, params) {
1212
params = params || {}
1313
params = fastCopy(params)
1414

@@ -23,7 +23,7 @@ export default function makeServiceActions(service) {
2323
// Two query syntaxes are supported, since actions only receive one argument.
2424
// 1. Just pass the id: `get(1)`
2525
// 2. Pass arguments as an array: `get([null, params])`
26-
get({ state, getters, commit, dispatch }, args) {
26+
async get({ state, getters, commit, dispatch }, args) {
2727
let id
2828
let params
2929
let skipRequestIfExists
@@ -49,8 +49,8 @@ export default function makeServiceActions(service) {
4949
commit('setPending', 'get')
5050
return service
5151
.get(id, params)
52-
.then(item => {
53-
dispatch('addOrUpdate', item)
52+
.then(async item => {
53+
await dispatch('addOrUpdate', item)
5454
commit('unsetPending', 'get')
5555
return state.keyedById[id]
5656
})
@@ -62,15 +62,14 @@ export default function makeServiceActions(service) {
6262
}
6363

6464
// If the records is already in store, return it
65-
const existedItem = getters.get(id, params)
66-
if (existedItem) {
67-
if (!skipRequestIfExists) getFromRemote()
68-
return Promise.resolve(existedItem)
65+
const existingItem = getters.get(id, params)
66+
if (existingItem && skipRequestIfExists) {
67+
return Promise.resolve(existingItem)
6968
}
7069
return getFromRemote()
7170
},
7271

73-
create({ commit, dispatch, state }, dataOrArray) {
72+
async create({ commit, dispatch, state }, dataOrArray) {
7473
const { idField, tempIdField } = state
7574
let data
7675
let params
@@ -97,9 +96,9 @@ export default function makeServiceActions(service) {
9796

9897
return service
9998
.create(data, params)
100-
.then(response => {
99+
.then(async response => {
101100
if (Array.isArray(response)) {
102-
dispatch('addOrUpdateList', response)
101+
await dispatch('addOrUpdateList', response)
103102
response = response.map(item => {
104103
const id = getId(item, idField)
105104

@@ -108,7 +107,7 @@ export default function makeServiceActions(service) {
108107
} else {
109108
const id = getId(response, idField)
110109

111-
dispatch('addOrUpdate', response)
110+
await dispatch('addOrUpdate', response)
112111

113112
response = state.keyedById[id]
114113
}
@@ -123,16 +122,16 @@ export default function makeServiceActions(service) {
123122
})
124123
},
125124

126-
update({ commit, dispatch, state }, [id, data, params]) {
125+
async update({ commit, dispatch, state }, [id, data, params]) {
127126

128127
commit('setPending', 'update')
129128

130129
params = fastCopy(params)
131130

132131
return service
133132
.update(id, data, params)
134-
.then(item => {
135-
dispatch('addOrUpdate', item)
133+
.then(async item => {
134+
await dispatch('addOrUpdate', item)
136135
commit('unsetPending', 'update')
137136
return state.keyedById[id]
138137
})
@@ -143,7 +142,7 @@ export default function makeServiceActions(service) {
143142
})
144143
},
145144

146-
patch({ commit, dispatch, state }, [id, data, params]) {
145+
async patch({ commit, dispatch, state }, [id, data, params]) {
147146
commit('setPending', 'patch')
148147

149148
params = fastCopy(params)
@@ -154,8 +153,8 @@ export default function makeServiceActions(service) {
154153

155154
return service
156155
.patch(id, data, params)
157-
.then(item => {
158-
dispatch('addOrUpdate', item)
156+
.then(async item => {
157+
await dispatch('addOrUpdate', item)
159158
commit('unsetPending', 'patch')
160159
return state.keyedById[id]
161160
})
@@ -166,7 +165,7 @@ export default function makeServiceActions(service) {
166165
})
167166
},
168167

169-
remove({ commit }, idOrArray) {
168+
async remove({ commit }, idOrArray) {
170169
let id
171170
let params
172171

@@ -206,11 +205,11 @@ export default function makeServiceActions(service) {
206205
* Feathers client. The client modifies the params object.
207206
* @param response
208207
*/
209-
handleFindResponse({ state, commit, dispatch }, { params, response }) {
208+
async handleFindResponse({ state, commit, dispatch }, { params, response }) {
210209
const { qid = 'default', query } = params
211210
const { idField } = state
212211

213-
dispatch('addOrUpdateList', response)
212+
await dispatch('addOrUpdateList', response)
214213
commit('unsetPending', 'find')
215214

216215
const mapItemFromState = item => {
@@ -232,17 +231,22 @@ export default function makeServiceActions(service) {
232231
: (response = mappedFromState)
233232
}
234233

235-
dispatch('afterFind', response)
234+
response = await await dispatch('afterFind', response)
236235

237236
return response
238237
},
239-
handleFindError({ commit }, { params, error }) {
238+
239+
async handleFindError({ commit }, { params, error }) {
240240
commit('setError', { method: 'find', params, error })
241241
commit('unsetPending', 'find')
242242
return Promise.reject(error)
243243
},
244-
afterFind() {},
245-
addOrUpdateList({ state, commit }, response) {
244+
245+
async afterFind({}, response) {
246+
return response
247+
},
248+
249+
async addOrUpdateList({ state, commit }, response) {
246250
const list = response.data || response
247251
const isPaginated = response.hasOwnProperty('total')
248252
const toAdd = []
@@ -282,8 +286,11 @@ export default function makeServiceActions(service) {
282286

283287
commit('addItems', toAdd)
284288
commit('updateItems', toUpdate)
289+
290+
return response
285291
},
286-
addOrUpdate({ state, commit }, item) {
292+
293+
async addOrUpdate({ state, commit }, item) {
287294
const { idField } = state
288295
let id = getId(item, idField)
289296
let existingItem = state.keyedById[id]
@@ -300,8 +307,12 @@ export default function makeServiceActions(service) {
300307
if (isIdOk) {
301308
existingItem ? commit('updateItem', item) : commit('addItem', item)
302309
}
310+
return item
303311
}
304312
}
313+
/**
314+
* Only add a method to the store if the service actually has that same method.
315+
*/
305316
Object.keys(serviceActions).map(method => {
306317
if (service[method] && typeof service[method] === 'function') {
307318
actions[method] = serviceActions[method]

src/service-module/service-module.mutations.ts

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export default function makeServiceMutations() {
5959
function updateItems(state, items) {
6060
const { idField, replaceItems, addOnUpsert, serverAlias, modelName } = state
6161
const Model = _get(models, `[${serverAlias}][${modelName}]`)
62-
// const BaseModel = _get(models, `[${state.serverAlias}].BaseModel`)
62+
const BaseModel = _get(models, `[${state.serverAlias}].BaseModel`)
6363

6464
for (let item of items) {
6565
const id = getId(item, idField)
@@ -71,18 +71,6 @@ export default function makeServiceMutations() {
7171

7272
// Update the record
7373
if (id !== null && id !== undefined) {
74-
75-
76-
77-
78-
// if (Model && !(item instanceof BaseModel) && !(item instanceof Model)) {
79-
// item = new Model(item)
80-
// }
81-
82-
83-
84-
85-
8674
if (state.ids.includes(id)) {
8775
// Completely replace the item
8876
if (replaceItems) {
@@ -92,6 +80,16 @@ export default function makeServiceMutations() {
9280
Vue.set(state.keyedById, id, item)
9381
// Merge in changes
9482
} else {
83+
/**
84+
* If we have a Model class, calling new Model(incomingData) will call update
85+
* the original record with the accessors and setupInstance data.
86+
* This means that date objects and relationships will be preserved.
87+
*
88+
* If there's no Model class, just call updateOriginal on the incoming data.
89+
*/
90+
if (Model && !(item instanceof BaseModel) && !(item instanceof Model)) {
91+
item = new Model(item)
92+
}
9593
const original = state.keyedById[id]
9694
updateOriginal(original, item)
9795
}
@@ -106,7 +104,17 @@ export default function makeServiceMutations() {
106104
}
107105
}
108106

107+
function mergeInstance(state, item) {
108+
const { serverAlias, idField, tempIdField, modelName } = state
109+
const id = getId(item, idField)
110+
const existingItem = state.keyedById[id]
111+
if (existingItem) {
112+
mergeWithAccessors(existingItem, item)
113+
}
114+
}
115+
109116
return {
117+
mergeInstance,
110118
addItem(state, item) {
111119
addItems(state, [item])
112120
},

test/service-module/service-module.actions.test.ts

Lines changed: 39 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -552,7 +552,7 @@ describe('Service Module - Actions', () => {
552552
})
553553

554554
describe('Get', function () {
555-
it('updates store list state on service success', done => {
555+
it('updates store list state on service success', async () => {
556556
const { makeServicePlugin, Todo } = makeContext()
557557
const store = new Vuex.Store<RootState>({
558558
plugins: [
@@ -571,68 +571,47 @@ describe('Service Module - Actions', () => {
571571
assert(todoState.isGetPending === false)
572572
assert(todoState.idField === 'id')
573573

574-
actions.get.call({ $store: store }, 0).then(() => {
575-
assert(todoState.ids.length === 1, 'only one item is in the store')
576-
assert(todoState.errorOnGet === null, 'there was no errorOnGet')
577-
assert(todoState.isGetPending === false, 'isGetPending is set to false')
578-
579-
let expectedKeyedById: NumberedList = {
580-
0: { id: 0, description: 'Do the first' }
581-
}
582-
assert.deepEqual(
583-
JSON.parse(JSON.stringify(todoState.keyedById)),
584-
expectedKeyedById
585-
)
586-
587-
// Make a request with the array syntax that allows passing params
588-
actions.get.call({ $store: store }, [1, {}]).then(response2 => {
589-
expectedKeyedById = {
590-
0: { id: 0, description: 'Do the first' },
591-
1: { id: 1, description: 'Do the second' }
592-
}
593-
assert(response2.description === 'Do the second')
594-
assert.deepEqual(
595-
JSON.parse(JSON.stringify(todoState.keyedById)),
596-
expectedKeyedById
597-
)
574+
const todo1 = await actions.get.call({ $store: store }, 0)
575+
assert(todoState.ids.length === 1, 'only one item is in the store')
576+
assert(todoState.errorOnGet === null, 'there was no errorOnGet')
577+
assert(todoState.isGetPending === false, 'isGetPending is set to false')
598578

599-
// Make a request to an existing record and return the existing data first, then update `keyedById`
600-
todoState.keyedById = {
601-
0: { id: 0, description: 'Do the FIRST' }, // twist the data to see difference
602-
1: { id: 1, description: 'Do the second' }
603-
}
604-
actions.get.call({ $store: store }, [0, {}]).then(response3 => {
605-
expectedKeyedById = {
606-
0: { id: 0, description: 'Do the FIRST' },
607-
1: { id: 1, description: 'Do the second' }
608-
}
609-
assert(response3.description === 'Do the FIRST')
610-
assert.deepEqual(
611-
JSON.parse(JSON.stringify(todoState.keyedById)),
612-
expectedKeyedById
613-
)
579+
let expectedKeyedById: NumberedList = {
580+
0: { id: 0, description: 'Do the first' }
581+
}
582+
assert.deepEqual(
583+
JSON.parse(JSON.stringify(todoState.keyedById)),
584+
expectedKeyedById
585+
)
614586

615-
// Wait for the remote data to arriive
616-
setTimeout(() => {
617-
expectedKeyedById = {
618-
0: { id: 0, description: 'Do the first' },
619-
1: { id: 1, description: 'Do the second' }
620-
}
621-
assert.deepEqual(
622-
JSON.parse(JSON.stringify(todoState.keyedById)),
623-
expectedKeyedById
624-
)
625-
done()
626-
}, 100)
627-
})
628-
})
629-
})
587+
// Make a request with the array syntax that allows passing params
588+
const response2 = await actions.get.call({ $store: store }, [1, {}])
589+
expectedKeyedById = {
590+
0: { id: 0, description: 'Do the first' },
591+
1: { id: 1, description: 'Do the second' }
592+
}
593+
assert(response2.description === 'Do the second')
594+
assert.deepEqual(
595+
JSON.parse(JSON.stringify(todoState.keyedById)),
596+
expectedKeyedById
597+
)
630598

631-
// Make sure proper state changes occurred before response
632-
assert(todoState.ids.length === 0)
633-
assert(todoState.errorOnCreate === null)
634-
assert(todoState.isGetPending === true)
635-
assert.deepEqual(todoState.keyedById, {})
599+
// Edit the first record in the store so the data is different.
600+
// Make a request for the first record again, and it should be updated.
601+
const clone1 = todo1.clone()
602+
clone1.description = 'MODIFIED IN THE VUEX STORE'
603+
clone1.commit()
604+
605+
assert.strictEqual(todoState.keyedById[0].description, clone1.description, 'the store instance was updated')
606+
607+
const response3 = await actions.get.call({ $store: store }, [0, {}])
608+
const todo0 = Todo.getFromStore(0)
609+
assert(response3.description === 'Do the first')
610+
assert.deepEqual(
611+
JSON.parse(JSON.stringify(todoState.keyedById)),
612+
expectedKeyedById,
613+
'The data is back as it was on the API server'
614+
)
636615
})
637616

638617
it('does not make remote call when skipRequestIfExists=true', done => {

0 commit comments

Comments
 (0)