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();
|
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 () => {
|
it('should define belongs to many relation through exists pivot collection', async () => {
|
||||||
const PostTag = db.collection({
|
const PostTag = db.collection({
|
||||||
name: 'postsTags',
|
name: 'postsTags',
|
||||||
|
@ -22,6 +22,40 @@ export type CollectionSortable = string | boolean | { name?: string; scopeKey?:
|
|||||||
|
|
||||||
type dumpable = 'required' | 'optional' | 'skip';
|
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'> {
|
export interface CollectionOptions extends Omit<ModelOptions, 'name' | 'hooks'> {
|
||||||
name: string;
|
name: string;
|
||||||
namespace?: string;
|
namespace?: string;
|
||||||
@ -251,6 +285,7 @@ export class Collection<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@EnsureAtomicity
|
||||||
setField(name: string, options: FieldOptions): Field {
|
setField(name: string, options: FieldOptions): Field {
|
||||||
checkIdentifier(name);
|
checkIdentifier(name);
|
||||||
this.checkFieldType(name, options);
|
this.checkFieldType(name, options);
|
||||||
|
@ -85,12 +85,14 @@ export class BelongsToManyField extends RelationField {
|
|||||||
this.options.onDelete = 'CASCADE';
|
this.options.onDelete = 'CASCADE';
|
||||||
}
|
}
|
||||||
|
|
||||||
const association = collection.model.belongsToMany(Target, {
|
const belongsToManyOptions = {
|
||||||
constraints: false,
|
constraints: false,
|
||||||
...omit(this.options, ['name', 'type', 'target']),
|
...omit(this.options, ['name', 'type', 'target']),
|
||||||
as: this.name,
|
as: this.name,
|
||||||
through: Through.model,
|
through: Through.model,
|
||||||
});
|
};
|
||||||
|
|
||||||
|
const association = collection.model.belongsToMany(Target, belongsToManyOptions);
|
||||||
|
|
||||||
// 建立关系之后从 pending 列表中删除
|
// 建立关系之后从 pending 列表中删除
|
||||||
database.removePendingField(this);
|
database.removePendingField(this);
|
||||||
|
@ -34,6 +34,78 @@ describe('belongsToMany', () => {
|
|||||||
await app.destroy();
|
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 () => {
|
it('should create belongsToMany field', async () => {
|
||||||
await Field.repository.create({
|
await Field.repository.create({
|
||||||
values: {
|
values: {
|
||||||
|
Loading…
Reference in New Issue
Block a user