chore(database): clean invalid associations in collection model when set field failed (#2720)
* chore(database): clean invalid associations in collection model when set field failed * fix: import * chore: test
This commit is contained in:
parent
a57c93d35b
commit
dbdd587b71
@ -13,6 +13,58 @@ describe('belongs to many field', () => {
|
||||
await db.close();
|
||||
});
|
||||
|
||||
it('should define belongs to many when change alias name', async () => {
|
||||
const Through = db.collection({
|
||||
name: 't1',
|
||||
fields: [
|
||||
{
|
||||
type: 'bigInt',
|
||||
name: 'id',
|
||||
primaryKey: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const A = db.collection({
|
||||
name: 'a',
|
||||
});
|
||||
|
||||
const B = db.collection({
|
||||
name: 'b',
|
||||
});
|
||||
|
||||
await db.sync();
|
||||
|
||||
let error;
|
||||
|
||||
expect(Object.keys(A.model.associations).length).toEqual(0);
|
||||
try {
|
||||
A.setField('t1', {
|
||||
type: 'belongsToMany',
|
||||
through: 't1',
|
||||
target: 't1',
|
||||
});
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error).toBeDefined();
|
||||
|
||||
expect(Object.keys(A.model.associations).length).toEqual(0);
|
||||
let error2;
|
||||
try {
|
||||
A.setField('xxx', {
|
||||
type: 'belongsToMany',
|
||||
through: 't1',
|
||||
target: 't1',
|
||||
});
|
||||
} catch (e) {
|
||||
error2 = e;
|
||||
}
|
||||
|
||||
expect(error2).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should define belongs to many relation through exists pivot collection', async () => {
|
||||
const PostTag = db.collection({
|
||||
name: 'postsTags',
|
||||
|
@ -22,6 +22,40 @@ export type CollectionSortable = string | boolean | { name?: string; scopeKey?:
|
||||
|
||||
type dumpable = 'required' | 'optional' | 'skip';
|
||||
|
||||
function EnsureAtomicity(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
const originalMethod = descriptor.value;
|
||||
|
||||
descriptor.value = function (...args: any[]) {
|
||||
const model = this.model;
|
||||
const beforeAssociationKeys = Object.keys(model.associations);
|
||||
const beforeRawAttributes = Object.keys(model.rawAttributes);
|
||||
|
||||
try {
|
||||
return originalMethod.apply(this, args);
|
||||
} catch (error) {
|
||||
// remove associations created in this method
|
||||
const afterAssociationKeys = Object.keys(model.associations);
|
||||
const createdAssociationKeys = lodash.difference(afterAssociationKeys, beforeAssociationKeys);
|
||||
for (const key of createdAssociationKeys) {
|
||||
delete this.model.associations[key];
|
||||
}
|
||||
|
||||
const afterRawAttributes = Object.keys(model.rawAttributes);
|
||||
const createdRawAttributes = lodash.difference(afterRawAttributes, beforeRawAttributes);
|
||||
console.log({
|
||||
beforeRawAttributes,
|
||||
afterRawAttributes,
|
||||
});
|
||||
for (const key of createdRawAttributes) {
|
||||
delete this.model.rawAttributes[key];
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
export interface CollectionOptions extends Omit<ModelOptions, 'name' | 'hooks'> {
|
||||
name: string;
|
||||
namespace?: string;
|
||||
@ -251,6 +285,7 @@ export class Collection<
|
||||
}
|
||||
}
|
||||
|
||||
@EnsureAtomicity
|
||||
setField(name: string, options: FieldOptions): Field {
|
||||
checkIdentifier(name);
|
||||
this.checkFieldType(name, options);
|
||||
|
@ -85,12 +85,14 @@ export class BelongsToManyField extends RelationField {
|
||||
this.options.onDelete = 'CASCADE';
|
||||
}
|
||||
|
||||
const association = collection.model.belongsToMany(Target, {
|
||||
const belongsToManyOptions = {
|
||||
constraints: false,
|
||||
...omit(this.options, ['name', 'type', 'target']),
|
||||
as: this.name,
|
||||
through: Through.model,
|
||||
});
|
||||
};
|
||||
|
||||
const association = collection.model.belongsToMany(Target, belongsToManyOptions);
|
||||
|
||||
// 建立关系之后从 pending 列表中删除
|
||||
database.removePendingField(this);
|
||||
|
@ -34,6 +34,78 @@ describe('belongsToMany', () => {
|
||||
await app.destroy();
|
||||
});
|
||||
|
||||
it('should define belongs to many when change alias name', async () => {
|
||||
await Collection.repository.create({
|
||||
values: {
|
||||
name: 'a',
|
||||
fields: [{ type: 'bigInt', name: 'id', primaryKey: true, autoIncrement: true }],
|
||||
},
|
||||
context: {},
|
||||
});
|
||||
|
||||
await Collection.repository.create({
|
||||
values: {
|
||||
name: 'b',
|
||||
fields: [{ type: 'bigInt', name: 'id', primaryKey: true, autoIncrement: true }],
|
||||
},
|
||||
context: {},
|
||||
});
|
||||
|
||||
await Collection.repository.create({
|
||||
values: {
|
||||
name: 't1',
|
||||
fields: [{ type: 'bigInt', name: 'id', primaryKey: true, autoIncrement: true }],
|
||||
},
|
||||
context: {},
|
||||
});
|
||||
|
||||
const Through = db.getCollection('t1');
|
||||
|
||||
let error;
|
||||
try {
|
||||
await Field.repository.create({
|
||||
values: {
|
||||
name: 't1',
|
||||
type: 'belongsToMany',
|
||||
target: 'b',
|
||||
through: 't1',
|
||||
collectionName: 'a',
|
||||
sourceKey: 'id',
|
||||
targetKey: 'id',
|
||||
foreignKey: 'a_id',
|
||||
otherKey: 'b_id',
|
||||
},
|
||||
context: {},
|
||||
});
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error).toBeDefined();
|
||||
|
||||
let error2;
|
||||
try {
|
||||
await Field.repository.create({
|
||||
values: {
|
||||
name: 'xxx',
|
||||
type: 'belongsToMany',
|
||||
target: 'b',
|
||||
through: 't1',
|
||||
collectionName: 'a',
|
||||
sourceKey: 'id',
|
||||
targetKey: 'id',
|
||||
foreignKey: 'a_id',
|
||||
otherKey: 'b_id',
|
||||
},
|
||||
context: {},
|
||||
});
|
||||
} catch (e) {
|
||||
error2 = e;
|
||||
}
|
||||
|
||||
expect(error2).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should create belongsToMany field', async () => {
|
||||
await Field.repository.create({
|
||||
values: {
|
||||
|
Loading…
Reference in New Issue
Block a user