Skip to content

Commit 813e52e

Browse files
DutchGermanwolfgangwalther
authored andcommitted
fix: Make sure mutations target the reacetive proxy observed by Vue
GenericModel's methods like $get where mutating the internal #proxy object which meant Vue could not observe these changes. By binding GenericModel's methods to the receiver instance and mutating these directly Vue can pick up on these changes. This reflects the behavior of GenericCollection. Resolves #727
1 parent fb0d28d commit 813e52e

File tree

2 files changed

+80
-18
lines changed

2 files changed

+80
-18
lines changed

src/GenericModel.js

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@ class GenericModel {
1919
return Reflect.deleteProperty(target, propertyKey)
2020
},
2121
get: (target, propertyKey, receiver) => {
22-
if (propertyKey === 'constructor' || Reflect.ownKeys(this).includes(propertyKey)) {
22+
if (propertyKey === 'constructor') {
2323
return Reflect.get(this, propertyKey, this)
24+
} else if (Reflect.ownKeys(this).includes(propertyKey)) {
25+
return Reflect.get(this, propertyKey, this).bind(this, receiver)
2426
}
2527
return Reflect.get(target, propertyKey, receiver)
2628
},
@@ -34,7 +36,7 @@ class GenericModel {
3436
})
3537
}
3638

37-
async #request ({ method, keepChanges = false, needsQuery = true }, signal, opts, ...data) {
39+
async #request (receiver, { method, keepChanges = false, needsQuery = true }, signal, opts, ...data) {
3840
await this.#options.route.$ready
3941
const { columns, ...options } = opts
4042

@@ -79,37 +81,37 @@ class GenericModel {
7981
// TODO: do we need to delete missing keys?
8082
if (keepChanges) {
8183
const diff = this.#proxy[$diff]
82-
Object.entries(body).forEach(([key, value]) => { this.#proxy[key] = value })
84+
Object.entries(body).forEach(([key, value]) => { receiver[key] = value })
8385
this.#proxy[$freeze]()
84-
Object.entries(diff).forEach(([key, value]) => { this.#proxy[key] = value })
86+
Object.entries(diff).forEach(([key, value]) => { receiver[key] = value })
8587
} else {
86-
Object.entries(body).forEach(([key, value]) => { this.#proxy[key] = value })
88+
Object.entries(body).forEach(([key, value]) => { receiver[key] = value })
8789
this.#proxy[$freeze]()
8890
}
8991
return body
9092
}
9193

92-
$get = new ObservableFunction(async (signal, opts = {}) => {
94+
$get = new ObservableFunction(async (receiver, signal, opts = {}) => {
9395
const { keepChanges, ...options } = opts
94-
return this.#request({ method: 'get', keepChanges }, signal, options)
96+
return this.#request(receiver, { method: 'get', keepChanges }, signal, options)
9597
})
9698

97-
$post = new ObservableFunction(async (signal, opts = {}) => {
99+
$post = new ObservableFunction(async (receiver, signal, opts = {}) => {
98100
const options = { return: 'representation', ...opts }
99-
const body = await this.#request({ method: 'post', needsQuery: false }, signal, options, this.#proxy)
101+
const body = await this.#request(receiver, { method: 'post', needsQuery: false }, signal, options, this.#proxy)
100102
if (body) {
101103
// we need to make sure the query is updated with the primary key
102104
this.#options.query = createPKQuery(this.#options.route.pks, mapAliasesFromSelect(this.#options.query?.select, body))
103105
}
104106
return body
105107
})
106108

107-
$put = new ObservableFunction(async (signal, opts) => {
109+
$put = new ObservableFunction(async (receiver, signal, opts) => {
108110
const options = { return: 'representation', ...opts }
109-
return this.#request({ method: 'put' }, signal, options, this.#proxy)
111+
return this.#request(receiver, { method: 'put' }, signal, options, this.#proxy)
110112
})
111113

112-
$patch = new ObservableFunction(async (signal, opts, data = {}) => {
114+
$patch = new ObservableFunction(async (receiver, signal, opts, data = {}) => {
113115
const options = { return: 'representation', ...opts }
114116

115117
if (!data || typeof data !== 'object') {
@@ -126,11 +128,11 @@ class GenericModel {
126128
return this.#proxy
127129
}
128130

129-
return this.#request({ method: 'patch' }, signal, options, patchData)
131+
return this.#request(receiver, { method: 'patch' }, signal, options, patchData)
130132
})
131133

132-
$delete = new ObservableFunction(async (signal, options = {}) => {
133-
return this.#request({ method: 'delete' }, signal, options)
134+
$delete = new ObservableFunction(async (receiver, signal, options = {}) => {
135+
return this.#request(receiver, { method: 'delete' }, signal, options)
134136
})
135137
}
136138

tests/unit/mixin.spec.js

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -296,20 +296,80 @@ describe('Mixin', () => {
296296
})
297297

298298
describe('reactivity', () => {
299-
beforeEach(() => {
299+
it('keeps pg when single is changed to same semantics', async () => {
300300
wrapper = shallowMount(Component, {
301301
global: {
302302
plugins: [[Postgrest, { apiRoot: '/api' }]]
303303
}
304304
})
305-
})
306305

307-
it('keeps pg when single is changed to same semantics', async () => {
308306
await flushPromises()
309307
const pgBefore = wrapper.vm.pg
310308
wrapper.vm.pgConfig.single = undefined
311309
await flushPromises()
312310
expect(wrapper.vm.pg).toBe(pgBefore)
313311
})
312+
313+
it('creates a reactive pg GenericCollection', async () => {
314+
wrapper = shallowMount(
315+
{
316+
template: '<pre>{{ pg }}</pre>',
317+
mixins: [pg],
318+
data () {
319+
return { pgConfig: { route: 'clients', query: { select: ['*'], single: false } } }
320+
}
321+
},
322+
{ global: { plugins: [[Postgrest, { apiRoot: '/api' }]] } }
323+
)
324+
325+
expect(wrapper.vm.pg).toEqual([])
326+
expect(wrapper.html()).toBe('<pre>[]</pre>')
327+
328+
await flushPromises()
329+
330+
expect(wrapper.vm.pg).toEqual([{ id: 1, name: 'Test Client 1' }, { id: 2, name: 'Test Client 2' }, { id: 3, name: 'Test Client 3' }])
331+
expect(wrapper.html()).toMatchInlineSnapshot(`
332+
"<pre>[
333+
{
334+
"id": 1,
335+
"name": "Test Client 1"
336+
},
337+
{
338+
"id": 2,
339+
"name": "Test Client 2"
340+
},
341+
{
342+
"id": 3,
343+
"name": "Test Client 3"
344+
}
345+
]</pre>"
346+
`)
347+
})
348+
349+
it('creates a reactive pg GenericModel', async () => {
350+
wrapper = shallowMount(
351+
{
352+
template: '<pre>{{ pg }}</pre>',
353+
mixins: [pg],
354+
data () {
355+
return { pgConfig: { route: 'clients', query: { select: ['*'], id: 'eq.1' }, single: true } }
356+
}
357+
},
358+
{ global: { plugins: [[Postgrest, { apiRoot: '/api' }]] } }
359+
)
360+
361+
expect(wrapper.vm.pg).toEqual({})
362+
expect(wrapper.html()).toBe('<pre>{}</pre>')
363+
364+
await flushPromises()
365+
366+
expect(wrapper.vm.pg).toEqual({ id: 1, name: 'Test Client 1' })
367+
expect(wrapper.html()).toMatchInlineSnapshot(`
368+
"<pre>{
369+
"id": 1,
370+
"name": "Test Client 1"
371+
}</pre>"
372+
`)
373+
})
314374
})
315375
})

0 commit comments

Comments
 (0)