From 78fe77e2cf4cc411918ca69060e2fb98d5ff07d3 Mon Sep 17 00:00:00 2001 From: ChengLei Shao Date: Tue, 29 Nov 2022 18:27:41 +0800 Subject: [PATCH] feat: belongs to many on delete (#1158) --- .../database/src/fields/belongs-to-field.ts | 14 +++-- .../src/fields/belongs-to-many-field.ts | 18 +++++++ .../reference-integerity-check.test.ts | 52 +++++++++++++++++++ packages/pro-plugins | 2 +- 4 files changed, 80 insertions(+), 6 deletions(-) diff --git a/packages/core/database/src/fields/belongs-to-field.ts b/packages/core/database/src/fields/belongs-to-field.ts index 7da6196ca..f28007118 100644 --- a/packages/core/database/src/fields/belongs-to-field.ts +++ b/packages/core/database/src/fields/belongs-to-field.ts @@ -1,5 +1,5 @@ import { omit } from 'lodash'; -import { BelongsToOptions as SequelizeBelongsToOptions, Utils } from 'sequelize'; +import { BelongsTo, BelongsToOptions as SequelizeBelongsToOptions, Utils } from 'sequelize'; import { Reference } from '../features/ReferencesMap'; import { checkIdentifier } from '../utils'; import { BaseRelationFieldOptions, RelationField } from './relation-field'; @@ -16,18 +16,22 @@ export class BelongsToField extends RelationField { return target || Utils.pluralize(name); } - reference(association): Reference { + static toReference(db, association, onDelete) { const targetKey = association.targetKey; return { - sourceCollectionName: this.database.modelCollection.get(association.source).name, + sourceCollectionName: db.modelCollection.get(association.source).name, sourceField: association.foreignKey, targetField: targetKey, - targetCollectionName: this.database.modelCollection.get(association.target).name, - onDelete: this.options.onDelete, + targetCollectionName: db.modelCollection.get(association.target).name, + onDelete: onDelete, }; } + reference(association): Reference { + return BelongsToField.toReference(this.database, association, this.options.onDelete); + } + bind() { const { database, collection } = this.context; const Target = this.TargetModel; diff --git a/packages/core/database/src/fields/belongs-to-many-field.ts b/packages/core/database/src/fields/belongs-to-many-field.ts index 34501ffc5..e7fed468c 100644 --- a/packages/core/database/src/fields/belongs-to-many-field.ts +++ b/packages/core/database/src/fields/belongs-to-many-field.ts @@ -1,7 +1,9 @@ import { omit } from 'lodash'; import { BelongsToManyOptions as SequelizeBelongsToManyOptions, Utils } from 'sequelize'; import { Collection } from '../collection'; +import { Reference } from '../features/ReferencesMap'; import { checkIdentifier } from '../utils'; +import { BelongsToField } from './belongs-to-field'; import { MultipleRelationFieldOptions, RelationField } from './relation-field'; export class BelongsToManyField extends RelationField { @@ -25,6 +27,15 @@ export class BelongsToManyField extends RelationField { return this.options.otherKey; } + references(association): Reference[] { + const db = this.context.database; + + return [ + BelongsToField.toReference(db, association.toSource, this.options.onDelete), + BelongsToField.toReference(db, association.toTarget, this.options.onDelete), + ]; + } + bind() { const { database, collection } = this.context; @@ -86,6 +97,8 @@ export class BelongsToManyField extends RelationField { Through.addIndex([this.options.foreignKey]); Through.addIndex([this.options.otherKey]); + + this.references(association).forEach((reference) => this.database.referenceMap.addReference(reference)); return true; } @@ -97,6 +110,11 @@ export class BelongsToManyField extends RelationField { database.removePendingField(this); // 删掉 model 的关联字段 + const association = collection.model.associations[this.name]; + if (association) { + this.references(association).forEach((reference) => this.database.referenceMap.removeReference(reference)); + } + this.clearAccessors(); delete collection.model.associations[this.name]; } diff --git a/packages/plugins/collection-manager/src/__tests__/associations/reference-integerity-check.test.ts b/packages/plugins/collection-manager/src/__tests__/associations/reference-integerity-check.test.ts index 865c8db6f..6bc843229 100644 --- a/packages/plugins/collection-manager/src/__tests__/associations/reference-integerity-check.test.ts +++ b/packages/plugins/collection-manager/src/__tests__/associations/reference-integerity-check.test.ts @@ -13,6 +13,58 @@ describe('reference integrity check', () => { await app.destroy(); }); + it('should delete cascade on belongs to many relation', async () => { + const posts = db.collection({ + name: 'posts', + fields: [ + { type: 'string', name: 'title' }, + { type: 'belongsToMany', name: 'tags', onDelete: 'CASCADE' }, + ], + }); + + const tags = db.collection({ + name: 'tags', + fields: [ + { + type: 'string', + name: 'name', + }, + { + type: 'belongsToMany', + name: 'posts', + onDelete: 'CASCADE', + }, + ], + }); + + await db.sync(); + + const post = await posts.repository.create({ + values: { + title: 'post', + tags: [{ name: 't1' }, { name: 't2' }], + }, + }); + + // @ts-ignore + const throughModel = posts.model.associations.tags.through.model; + + expect(await throughModel.count()).toEqual(2); + + await post.destroy(); + + expect(await throughModel.count()).toEqual(0); + + expect(db.referenceMap.getReferences('posts').length > 0).toBeTruthy(); + expect(db.referenceMap.getReferences('tags').length > 0).toBeTruthy(); + + tags.removeField('posts'); + tags.removeField('tags'); + + expect(db.referenceMap.getReferences('posts').length == 0).toBeTruthy(); + expect(db.referenceMap.getReferences('tags').length == 0).toBeTruthy(); + }); + it('should clean reference after collection destroy', async () => { const users = db.collection({ name: 'users', diff --git a/packages/pro-plugins b/packages/pro-plugins index 67f51c7fd..5a5fba41a 160000 --- a/packages/pro-plugins +++ b/packages/pro-plugins @@ -1 +1 @@ -Subproject commit 67f51c7fdd132ab12821f31f737b6f0cfb70d212 +Subproject commit 5a5fba41ae1cecaf3b073fcb610e9dc68dad0b85