Skip to content

Commit d1c3cc3

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 67295f6 commit d1c3cc3

File tree

2 files changed

+81
-18
lines changed

2 files changed

+81
-18
lines changed

src/GenericModel.js

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@ 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)
2424
}
25+
else if (Reflect.ownKeys(this).includes(propertyKey)) {
26+
return Reflect.get(this, propertyKey, this).bind(this, receiver)
27+
}
2528
return Reflect.get(target, propertyKey, receiver)
2629
},
2730
has: (target, propertyKey) => {
@@ -34,7 +37,7 @@ class GenericModel {
3437
})
3538
}
3639

37-
async #request ({ method, keepChanges = false, needsQuery = true }, signal, opts, ...data) {
40+
async #request (receiver, { method, keepChanges = false, needsQuery = true }, signal, opts, ...data) {
3841
await this.#options.route.$ready
3942
const { columns, ...options } = opts
4043

@@ -79,32 +82,32 @@ class GenericModel {
7982
// TODO: do we need to delete missing keys?
8083
if (keepChanges) {
8184
const diff = this.#proxy[$diff]
82-
Object.entries(body).forEach(([key, value]) => { this.#proxy[key] = value })
85+
Object.entries(body).forEach(([key, value]) => { receiver[key] = value })
8386
this.#proxy[$freeze]()
84-
Object.entries(diff).forEach(([key, value]) => { this.#proxy[key] = value })
87+
Object.entries(diff).forEach(([key, value]) => { receiver[key] = value })
8588
} else {
86-
Object.entries(body).forEach(([key, value]) => { this.#proxy[key] = value })
89+
Object.entries(body).forEach(([key, value]) => { receiver[key] = value })
8790
this.#proxy[$freeze]()
8891
}
8992
return body
9093
}
9194

92-
$get = new ObservableFunction(async (signal, opts = {}) => {
95+
$get = new ObservableFunction(async (receiver, signal, opts = {}) => {
9396
const { keepChanges, ...options } = opts
94-
return this.#request({ method: 'get', keepChanges }, signal, options)
97+
return this.#request(receiver, { method: 'get', keepChanges }, signal, options)
9598
})
9699

97-
$post = new ObservableFunction(async (signal, opts = {}) => {
100+
$post = new ObservableFunction(async (receiver, signal, opts = {}) => {
98101
const options = { return: 'representation', ...opts }
99-
return this.#request({ method: 'post', needsQuery: false }, signal, options, this.#proxy)
102+
return this.#request(receiver, { method: 'post', needsQuery: false }, signal, options, this.#proxy)
100103
})
101104

102-
$put = new ObservableFunction(async (signal, opts) => {
105+
$put = new ObservableFunction(async (receiver, signal, opts) => {
103106
const options = { return: 'representation', ...opts }
104-
return this.#request({ method: 'put' }, signal, options, this.#proxy)
107+
return this.#request(receiver, { method: 'put' }, signal, options, this.#proxy)
105108
})
106109

107-
$patch = new ObservableFunction(async (signal, opts, data = {}) => {
110+
$patch = new ObservableFunction(async (receiver, signal, opts, data = {}) => {
108111
const options = { return: 'representation', ...opts }
109112

110113
if (!data || typeof data !== 'object') {
@@ -121,11 +124,11 @@ class GenericModel {
121124
return this.#proxy
122125
}
123126

124-
return this.#request({ method: 'patch' }, signal, options, patchData)
127+
return this.#request(receiver, { method: 'patch' }, signal, options, patchData)
125128
})
126129

127-
$delete = new ObservableFunction(async (signal, options = {}) => {
128-
return this.#request({ method: 'delete' }, signal, options)
130+
$delete = new ObservableFunction(async (receiver, signal, options = {}) => {
131+
return this.#request(receiver, { method: 'delete' }, signal, options)
129132
})
130133
}
131134

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)