diff --git a/packages/core/database/src/__tests__/collection.test.ts b/packages/core/database/src/__tests__/collection.test.ts index 33e694b05..0fd65394c 100644 --- a/packages/core/database/src/__tests__/collection.test.ts +++ b/packages/core/database/src/__tests__/collection.test.ts @@ -17,6 +17,25 @@ describe('collection', () => { await db.close(); }); + it('should remove sequelize model prototype methods after field remove', async () => { + db.collection({ + name: 'tags', + }); + + const UserCollection = db.collection({ + name: 'users', + fields: [{ type: 'belongsToMany', name: 'tags' }], + }); + + console.log(Object.getOwnPropertyNames(UserCollection.model.prototype)); + + await UserCollection.removeField('tags'); + + console.log(Object.getOwnPropertyNames(UserCollection.model.prototype)); + // @ts-ignore + expect(UserCollection.model.prototype.getTags).toBeUndefined(); + }); + it('should not throw error when create empty collection in sqlite and mysql', async () => { if (!db.inDialect('sqlite', 'mysql')) { return; diff --git a/packages/core/database/src/fields/field.ts b/packages/core/database/src/fields/field.ts index 8011a44bf..58a5fb677 100644 --- a/packages/core/database/src/fields/field.ts +++ b/packages/core/database/src/fields/field.ts @@ -217,6 +217,7 @@ export abstract class Field { bind() { const { model } = this.context.collection; model.rawAttributes[this.name] = this.toSequelize(); + // @ts-ignore model.refreshAttributes(); if (this.options.index) { @@ -226,6 +227,9 @@ export abstract class Field { unbind() { const { model } = this.context.collection; + + delete model.prototype[this.name]; + model.removeAttribute(this.name); if (this.options.index || this.options.unique) { this.context.collection.removeIndex([this.name]); diff --git a/packages/plugins/collection-manager/src/__tests__/fields.repository.test.ts b/packages/plugins/collection-manager/src/__tests__/fields.repository.test.ts index eb71958e3..c90578855 100644 --- a/packages/plugins/collection-manager/src/__tests__/fields.repository.test.ts +++ b/packages/plugins/collection-manager/src/__tests__/fields.repository.test.ts @@ -2,6 +2,95 @@ import Database, { Collection as DBCollection, StringFieldOptions } from '@nocob import Application from '@nocobase/server'; import { createApp } from '.'; +describe('recreate field', () => { + let db: Database; + let app: Application; + let Collection: DBCollection; + let Field: DBCollection; + + beforeEach(async () => { + app = await createApp(); + db = app.db; + Collection = db.getCollection('collections'); + Field = db.getCollection('fields'); + }); + + afterEach(async () => { + await app.destroy(); + }); + + it('should recreate field', async () => { + await Collection.repository.create({ + values: { + name: 'a1', + }, + context: {}, + }); + + await Collection.repository.create({ + values: { + name: 'a2', + }, + context: {}, + }); + + await Field.repository.create({ + values: { + name: 'a', + type: 'string', + collectionName: 'a1', + }, + context: {}, + }); + + await db.getRepository('a1').create({ + values: { + a: 'a', + }, + }); + + await Field.repository.destroy({ + filter: { + name: 'a', + collectionName: 'a1', + }, + context: {}, + }); + + await Field.repository.create({ + values: { + name: 'a', + type: 'hasOne', + collectionName: 'a1', + target: 'a2', + foreignKey: 'a_id', + }, + context: {}, + }); + + const a1Model = db.getRepository('a1').model; + const results = await a1Model.findAll({ + include: [ + { + association: 'a', + }, + ], + }); + + expect(Object.getOwnPropertyNames(db.getCollection('a1').model.prototype)).toContain('getA'); + + await Field.repository.destroy({ + filter: { + name: 'a', + collectionName: 'a1', + }, + }); + + expect(Object.getOwnPropertyNames(db.getCollection('a1').model.prototype)).not.toContain('getA'); + expect(Object.getOwnPropertyNames(db.getCollection('a1').model.prototype)).not.toContain('a'); + }); +}); + describe('collections repository', () => { let db: Database; let app: Application; diff --git a/packages/plugins/collection-manager/src/__tests__/http-api/recreate.test.ts b/packages/plugins/collection-manager/src/__tests__/http-api/recreate.test.ts new file mode 100644 index 000000000..750d5f74d --- /dev/null +++ b/packages/plugins/collection-manager/src/__tests__/http-api/recreate.test.ts @@ -0,0 +1,66 @@ +import { MockServer } from '@nocobase/test'; +import { createApp } from '../index'; + +describe('recreate field', () => { + let app: MockServer; + let agent; + + beforeEach(async () => { + app = await createApp(); + agent = app.agent(); + }); + + afterEach(async () => { + await app.destroy(); + }); + + it('should recreate field', async () => { + await agent.resource('collections').create({ + values: { + name: 'a1', + }, + }); + + await agent.resource('collections').create({ + values: { + name: 'a2', + }, + }); + + await agent.resource('fields').create({ + values: { + name: 'a', + type: 'string', + collectionName: 'a1', + }, + }); + + await agent.resource('a1').create({ + values: { + a: 'a-value', + }, + }); + + await agent.resource('fields').destroy({ + filter: { + name: 'a', + collectionName: 'a1', + }, + }); + + await agent.resource('fields').create({ + values: { + name: 'a', + type: 'belongsToMany', + collectionName: 'a1', + target: 'a2', + }, + }); + + const response = await agent.resource('a1').list({ + appends: ['a'], + }); + + expect(response.statusCode).toBe(200); + }); +});