From 90846686ac0adcfe846236b55546e375143f951e Mon Sep 17 00:00:00 2001 From: Chareice Date: Wed, 8 Dec 2021 13:00:09 +0800 Subject: [PATCH] fix: update association value --- .../database/src/__tests__/database.test.ts | 38 ----------- .../src/__tests__/option-parser.test.ts | 1 + .../belongs-to-many-repository.test.ts | 66 ++++++++++++++++++- .../database/src/__tests__/repository.test.ts | 6 +- .../src/__tests__/respsitory/find.test.ts | 1 + packages/database/src/update-associations.ts | 66 ++++++++++++++----- 6 files changed, 123 insertions(+), 55 deletions(-) diff --git a/packages/database/src/__tests__/database.test.ts b/packages/database/src/__tests__/database.test.ts index 93cd23b2a..452ceabab 100644 --- a/packages/database/src/__tests__/database.test.ts +++ b/packages/database/src/__tests__/database.test.ts @@ -35,44 +35,6 @@ describe('database', () => { expect(imageCollection.fields.has('url2')).toBeTruthy(); }); - test('hasMany with inverse belongsTo relation', async () => { - const db = mockDatabase(); - const UserCollection = db.collection({ - name: 'users', - fields: [ - { type: 'string', name: 'name' }, - { type: 'hasMany', name: 'posts' }, - ], - }); - - const PostCollection = db.collection({ - name: 'posts', - fields: [{ type: 'string', name: 'title' }], - }); - - expect(UserCollection.model.associations.posts).toBeDefined(); - expect(PostCollection.model.associations.user).toBeDefined(); - }); - - test('belongsTo with inverse hasMany relation', async () => { - const db = mockDatabase(); - const UserCollection = db.collection({ - name: 'users', - fields: [{ type: 'string', name: 'name' }], - }); - - const PostCollection = db.collection({ - name: 'posts', - fields: [ - { type: 'string', name: 'title' }, - { type: 'belongsTo', name: 'user' }, - ], - }); - - expect(PostCollection.model.associations.user).toBeDefined(); - expect(UserCollection.model.associations.posts).toBeDefined(); - }); - test('get collection', async () => { const db = mockDatabase(); expect(db.getCollection('test')).toBeUndefined(); diff --git a/packages/database/src/__tests__/option-parser.test.ts b/packages/database/src/__tests__/option-parser.test.ts index be2f1a10e..66a871565 100644 --- a/packages/database/src/__tests__/option-parser.test.ts +++ b/packages/database/src/__tests__/option-parser.test.ts @@ -17,6 +17,7 @@ describe('option parser', () => { fields: [ { type: 'string', name: 'name' }, { type: 'integer', name: 'age' }, + { type: 'hasMany', name: 'posts' }, ], }); diff --git a/packages/database/src/__tests__/relation-repository/belongs-to-many-repository.test.ts b/packages/database/src/__tests__/relation-repository/belongs-to-many-repository.test.ts index 397c46f4b..8d9566c4b 100644 --- a/packages/database/src/__tests__/relation-repository/belongs-to-many-repository.test.ts +++ b/packages/database/src/__tests__/relation-repository/belongs-to-many-repository.test.ts @@ -1,8 +1,10 @@ import { mockDatabase } from '../index'; import { BelongsToManyRepository } from '../../relation-repository/belongs-to-many-repository'; +import Database from '../../database'; describe('belongs to many', () => { - let db; + let db: Database; + let User; let Post; let Tag; let PostTag; @@ -14,11 +16,29 @@ describe('belongs to many', () => { fields: [{ type: 'string', name: 'tagged_at' }], }); + User = db.collection({ + name: 'users', + fields: [ + { + type: 'string', + name: 'name', + }, + { + type: 'hasMany', + name: 'posts', + }, + ], + }); + Post = db.collection({ name: 'posts', fields: [ { type: 'belongsToMany', name: 'tags', through: 'posts_tags' }, { type: 'string', name: 'title' }, + { + type: 'belongsTo', + name: 'user', + }, ], }); @@ -248,6 +268,50 @@ describe('belongs to many', () => { expect(p2Tag.posts_tags.tagged_at).toBeNull(); }); + test('update association values', async () => { + const u1 = await User.repository.create({ + values: { + name: 'u1', + }, + }); + + const p1 = await Post.repository.create({ + values: { + title: 'p1', + tags: [{ name: 't1' }, { name: 't2' }], + user: u1.id, + }, + }); + + const tag = await Tag.repository.findOne(); + const tagPostsRepository = new BelongsToManyRepository(Tag, 'posts', tag.id); + + await tagPostsRepository.update({ + values: { + user: { + id: u1.get('id'), + name: 'u0', + }, + }, + }); + + await u1.reload(); + expect(u1.get('name')).toEqual('u1'); + + await tagPostsRepository.update({ + values: { + user: { + id: u1.get('id'), + name: 'u0', + }, + }, + updateAssociationValues: ['user'], + }); + + await u1.reload(); + expect(u1.get('name')).toEqual('u0'); + }); + test('add', async () => { let t1 = await Tag.repository.create({ values: { name: 't1' }, diff --git a/packages/database/src/__tests__/repository.test.ts b/packages/database/src/__tests__/repository.test.ts index a5061e9d2..0b67078a6 100644 --- a/packages/database/src/__tests__/repository.test.ts +++ b/packages/database/src/__tests__/repository.test.ts @@ -21,12 +21,16 @@ describe('repository.find', () => { name: 'posts', fields: [ { type: 'string', name: 'name' }, + { type: 'belongsTo', name: 'user' }, { type: 'hasMany', name: 'comments' }, ], }); Comment = db.collection({ name: 'comments', - fields: [{ type: 'string', name: 'name' }], + fields: [ + { type: 'string', name: 'name' }, + { type: 'belongsTo', name: 'post' }, + ], }); await db.sync(); await User.repository.createMany({ diff --git a/packages/database/src/__tests__/respsitory/find.test.ts b/packages/database/src/__tests__/respsitory/find.test.ts index a6c7996b8..6d8d8c7ed 100644 --- a/packages/database/src/__tests__/respsitory/find.test.ts +++ b/packages/database/src/__tests__/respsitory/find.test.ts @@ -15,6 +15,7 @@ describe('repository find', () => { fields: [ { type: 'string', name: 'name' }, { type: 'integer', name: 'age' }, + { type: 'hasMany', name: 'posts' }, ], }); diff --git a/packages/database/src/update-associations.ts b/packages/database/src/update-associations.ts index ed29e2097..10e5f4b04 100644 --- a/packages/database/src/update-associations.ts +++ b/packages/database/src/update-associations.ts @@ -9,6 +9,8 @@ import { BelongsTo, BelongsToMany, HasMany, + Transactionable, + Hookable, } from 'sequelize'; import { UpdateGuard } from './update-guard'; import { TransactionAble } from './repository'; @@ -21,6 +23,10 @@ function isStringOrNumber(value: any) { return typeof value === 'string' || typeof value === 'number'; } +function getKeysByPrefix(keys: string[], prefix: string) { + return keys.filter((key) => key.startsWith(`${prefix}.`)).map((key) => key.substring(prefix.length + 1)); +} + export function modelAssociations(instance: Model) { return (instance.constructor).associations; } @@ -57,6 +63,11 @@ interface UpdateOptions extends TransactionAble { sourceModel?: Model; } +interface UpdateAssociationOptions extends Transactionable, Hookable { + updateAssociationValues?: string[]; + sourceModel?: Model; +} + export async function updateModelByValues(instance: Model, values: UpdateValue, options?: UpdateOptions) { if (!options?.sanitized) { const guard = new UpdateGuard(); @@ -105,7 +116,7 @@ export async function updateThroughTableValue( * @param values * @param options */ -export async function updateAssociations(instance: Model, values: any, options: any = {}) { +export async function updateAssociations(instance: Model, values: any, options: UpdateAssociationOptions = {}) { // if no values set, return if (!values) { return; @@ -119,8 +130,10 @@ export async function updateAssociations(instance: Model, values: any, options: transaction = await instance.sequelize.transaction(); } + const keys = Object.keys(values); + for (const key of Object.keys(modelAssociations(instance))) { - if (values[key]) { + if (keys.includes(key)) { await updateAssociation(instance, key, values[key], { ...options, transaction, @@ -159,7 +172,12 @@ export async function updateAssociations(instance: Model, values: any, options: * @param value * @param options */ -export async function updateAssociation(instance: Model, key: string, value: any, options: any = {}) { +export async function updateAssociation( + instance: Model, + key: string, + value: any, + options: UpdateAssociationOptions = {}, +) { const association = modelAssociationByKey(instance, key); if (!association) { @@ -183,7 +201,12 @@ export async function updateAssociation(instance: Model, key: string, value: any * @param value * @param options */ -export async function updateSingleAssociation(model: Model, key: string, value: any, options: any = {}) { +export async function updateSingleAssociation( + model: Model, + key: string, + value: any, + options: UpdateAssociationOptions = {}, +) { const association = modelAssociationByKey(model, key); if (!association) { @@ -194,7 +217,8 @@ export async function updateSingleAssociation(model: Model, key: string, value: return false; } - const { transaction = await model.sequelize.transaction() } = options; + const { updateAssociationValues = [], transaction = await model.sequelize.transaction() } = options; + const keys = getKeysByPrefix(updateAssociationValues, key); try { // set method of association @@ -220,6 +244,7 @@ export async function updateSingleAssociation(model: Model, key: string, value: } return true; } + if (value instanceof Model) { await model[setAccessor](value); model.setDataValue(key, value); @@ -248,9 +273,15 @@ export async function updateSingleAssociation(model: Model, key: string, value: }, transaction, }); + if (instance) { await model[setAccessor](instance); - await updateAssociations(instance, value, { transaction, ...options }); + + if (updateAssociationValues.includes(key)) { + await instance.update(value, { ...options, transaction }); + } + + await updateAssociations(instance, value, { ...options, transaction, updateAssociationValues: keys }); model.setDataValue(key, instance); if (!options.transaction) { await transaction.commit(); @@ -259,12 +290,8 @@ export async function updateSingleAssociation(model: Model, key: string, value: } } - if (value[dataKey] === null) { - return await removeAssociation(); - } - const instance = await model[createAccessor](value, { transaction }); - await updateAssociations(instance, value, { transaction, ...options }); + await updateAssociations(instance, value, { ...options, transaction, updateAssociationValues: keys }); model.setDataValue(key, instance); // @ts-ignore if (association.targetKey) { @@ -288,7 +315,12 @@ export async function updateSingleAssociation(model: Model, key: string, value: * @param value * @param options */ -export async function updateMultipleAssociation(model: Model, key: string, value: any, options: any = {}) { +export async function updateMultipleAssociation( + model: Model, + key: string, + value: any, + options: UpdateAssociationOptions = {}, +) { const association = modelAssociationByKey(model, key); if (!association) { @@ -299,7 +331,8 @@ export async function updateMultipleAssociation(model: Model, key: string, value return false; } - const { transaction = await model.sequelize.transaction() } = options; + const { updateAssociationValues = [], transaction = await model.sequelize.transaction() } = options; + const keys = getKeysByPrefix(updateAssociationValues, key); try { const setAccessor = association.accessors.set; @@ -359,7 +392,7 @@ export async function updateMultipleAssociation(model: Model, key: string, value if (isUndefinedOrNull(item[pk])) { // create new record const instance = await model[createAccessor](item, accessorOptions); - await updateAssociations(instance, item, { transaction, ...options }); + await updateAssociations(instance, item, { ...options, transaction, updateAssociationValues: keys }); list3.push(instance); } else { // set & update record @@ -369,7 +402,10 @@ export async function updateMultipleAssociation(model: Model, key: string, value const addAccessor = association.accessors.add; await model[addAccessor](item[pk], accessorOptions); - await updateAssociations(instance, item, { transaction, ...options }); + if (updateAssociationValues.includes(key)) { + await instance.update(item, { ...options, transaction }); + } + await updateAssociations(instance, item, { ...options, transaction, updateAssociationValues: keys }); list3.push(instance); } }