From a61c5b93e84e295f5611e7e301e5196641364e7a Mon Sep 17 00:00:00 2001 From: Chen Yangjian <252317+cyjake@users.noreply.github.com> Date: Fri, 13 Sep 2024 19:51:04 +0800 Subject: [PATCH] fix: reload attribute if instance updated with jsonMerge --- Readme.md | 2 +- docs/_config.yml | 1 + src/bone.js | 43 +++++++++++++++++++++++------ test/integration/suite/json.test.js | 5 +--- 4 files changed, 38 insertions(+), 13 deletions(-) diff --git a/Readme.md b/Readme.md index 1a20e939..48b32171 100644 --- a/Readme.md +++ b/Readme.md @@ -6,7 +6,7 @@ [![Build Status](https://github.com/cyjake/leoric/actions/workflows/nodejs.yml/badge.svg)](https://github.com/cyjake/leoric/actions/workflows/nodejs.yml) [![codecov](https://codecov.io/gh/cyjake/leoric/branch/master/graph/badge.svg?token=OZZWTZTDS1)](https://codecov.io/gh/cyjake/leoric) -Leoric is an object-relational mapping for Node.js, which is heavily influenced by Active Record of Ruby on Rails. See the [documentation](https://leoric.js.org) for detail. +Leoric is an object-relational mapping library for Node.js, which is heavily influenced by Active Record of Ruby on Rails. See the [documentation](https://leoric.js.org) for detail. ## Usage diff --git a/docs/_config.yml b/docs/_config.yml index 1e141685..6df291fc 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -18,6 +18,7 @@ description: > # this means to ignore newlines until "baseurl:" An object-relational mapping for Node.js which is heavily influenced by Active Record. # Build settings +permalink: pretty kramdown: toc_levels: 2..3 theme: jekyll-theme-primer diff --git a/src/bone.js b/src/bone.js index 886199ea..e88fda29 100644 --- a/src/bone.js +++ b/src/bone.js @@ -682,15 +682,26 @@ class Bone { * @memberof Bone */ async jsonMerge(name, jsonValue, options = {}) { - const raw = new Raw(`JSON_MERGE_PATCH(${name}, '${JSON.stringify(jsonValue)}')`); - const affectedRows = await this.update({ [name]: raw }, options); + const Model = this.constructor; + const { primaryKey, shardingKey } = Model; + if (this[primaryKey] == null) throw new Error(`unset primary key ${primaryKey}`); + + const { preserve, ...restOptions } = options; + const where = { [primaryKey]: this[primaryKey] }; + if (shardingKey) where[shardingKey] = this[shardingKey]; + + const affectedRows = await Model.jsonMerge(where, { [name]: jsonValue }, options); + // reload only the updated attribute, incase overwriting others + const spell = Model._find(where, restOptions).$select(name).$get(0); + spell.scopes = []; + const instance = await spell; + if (instance) this.attribute(name, instance.attribute(name)); + return affectedRows; } async jsonMergePreserve(name, jsonValue, options = {}) { - const raw = new Raw(`JSON_MERGE_PRESERVE(${name}, '${JSON.stringify(jsonValue)}')`); - const affectedRows = await this.update({ [name]: raw }, options); - return affectedRows; + return await this.jsonMerge(name, jsonValue, { ...options, preserve: true }); } /** @@ -707,9 +718,10 @@ class Bone { const { fields = [] } = options; if (typeof values === 'object') { for (const name in values) { - if (values[name] !== undefined && this.hasAttribute(name) && (!fields.length || fields.includes(name))) { + const value = values[name]; + if (value !== undefined && !(value instanceof Raw) && this.hasAttribute(name) && (!fields.length || fields.includes(name))) { // exec custom setters in case it exist - this[name] = values[name]; + this[name] = value; changes[name] = this.attribute(name); } } @@ -767,7 +779,8 @@ class Bone { return await spell.later(result => { // sync changes (changes has been formatted by custom setters, use this.attribute(name, value) directly) for (const key in changes) { - this.attribute(key, changes[key]); + const value = changes[key]; + if (!(value instanceof Raw)) this.attribute(key, value); } this.syncRaw(); return result.affectedRows; @@ -1560,6 +1573,20 @@ class Bone { }); } + static jsonMerge(conditions, values = {}, options = {}) { + const { preserve, ...restOptions } = options; + const method = preserve ? 'JSON_MERGE_PRESERVE' : 'JSON_MERGE_PATCH'; + const data = { ...values }; + for (const [name, value] of Object.entries(values)) { + data[name] = new Raw(`${method}(${name}, '${JSON.stringify(value)}')`); + } + return this.update(conditions, data, restOptions); + } + + static jsonMergePreserve(conditions, values = {}, options = {}) { + return this.jsonMerge(conditions, values, { ...options, preserve: true }); + } + /** * Remove any record that matches `conditions`. * - If `forceDelete` is true, `DELETE` records from database permanently. diff --git a/test/integration/suite/json.test.js b/test/integration/suite/json.test.js index bc9127ce..b114f00c 100644 --- a/test/integration/suite/json.test.js +++ b/test/integration/suite/json.test.js @@ -35,14 +35,12 @@ describe('=> Basic', () => { await gen.update({ extra: { a: 1 } }); assert.equal(gen.extra.a, 1); await gen.jsonMerge('extra', { b: 2, a: 3 }); - await gen.reload(); assert.equal(gen.extra.a, 3); assert.equal(gen.extra.b, 2); const gen2 = await Gen.create({ name: 'gen2', extra: { test: 1 }}); assert.equal(gen2.extra.test, 1); await gen2.jsonMerge('extra', { url: 'https://www.wanxiang.art/?foo=' }); - await gen2.reload(); assert.equal(gen2.extra.url, 'https://www.wanxiang.art/?foo='); }); @@ -53,6 +51,7 @@ describe('=> Basic', () => { const sql = new Raw(`JSON_MERGE_PATCH(extra, '${JSON.stringify({ url: 'https://www.taobao.com/?id=1' })}')`); await gen.update({extra: sql}); + assert.ok(!(gen.extra instanceof Raw)); await gen.reload(); assert.equal(gen.extra.url, 'https://www.taobao.com/?id=1'); }); @@ -63,12 +62,10 @@ describe('=> Basic', () => { await gen.update({ extra: { a: 1 } }); assert.equal(gen.extra.a, 1); await gen.jsonMergePreserve('extra', { b: 2, a: 3 }); - await gen.reload(); assert.deepEqual(gen.extra.a, [1, 3]); await gen.jsonMerge('extra', { url: 'https://wanxiang.art/?foo=' }); await gen.jsonMergePreserve('extra', { url: 'https://www.wanxiang.art/?foo=' }); - await gen.reload(); assert.deepEqual(gen.extra.url, ['https://wanxiang.art/?foo=', 'https://www.wanxiang.art/?foo=']); }); });