fix: association accessors rebind (#1027)
* chore: test * chore: test * fix: association accessors rebind * fix: test
This commit is contained in:
parent
f15c67afd5
commit
b3f3883435
@ -43,6 +43,7 @@ export function transactionWrapperBuilder(transactionGenerator) {
|
||||
|
||||
return results;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
await transaction.rollback();
|
||||
throw err;
|
||||
}
|
||||
|
@ -93,6 +93,7 @@ export class BelongsToField extends RelationField {
|
||||
const association = collection.model.associations[this.name];
|
||||
this.database.referenceMap.removeReference(this.reference(association));
|
||||
|
||||
this.clearAccessors();
|
||||
// 删掉 model 的关联字段
|
||||
delete collection.model.associations[this.name];
|
||||
// @ts-ignore
|
||||
|
@ -23,7 +23,9 @@ export class BelongsToManyField extends RelationField {
|
||||
|
||||
bind() {
|
||||
const { database, collection } = this.context;
|
||||
|
||||
const Target = this.TargetModel;
|
||||
|
||||
if (!Target) {
|
||||
database.addPendingField(this);
|
||||
return false;
|
||||
@ -86,9 +88,12 @@ export class BelongsToManyField extends RelationField {
|
||||
unbind() {
|
||||
const { database, collection } = this.context;
|
||||
const Through = database.getCollection(this.through);
|
||||
|
||||
// 如果关系字段还没建立就删除了,也同步删除待建立关联的关系字段
|
||||
database.removePendingField(this);
|
||||
// 删掉 model 的关联字段
|
||||
|
||||
this.clearAccessors();
|
||||
delete collection.model.associations[this.name];
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
ModelIndexesOptions,
|
||||
QueryInterfaceOptions,
|
||||
SyncOptions,
|
||||
Transactionable
|
||||
Transactionable,
|
||||
} from 'sequelize';
|
||||
import { Collection } from '../collection';
|
||||
import { Database } from '../database';
|
||||
|
@ -172,6 +172,7 @@ export class HasManyField extends RelationField {
|
||||
this.database.referenceMap.removeReference(this.reference(association));
|
||||
}
|
||||
|
||||
this.clearAccessors();
|
||||
// 删掉 model 的关联字段
|
||||
delete collection.model.associations[this.name];
|
||||
// @ts-ignore
|
||||
|
@ -168,6 +168,7 @@ export class HasOneField extends RelationField {
|
||||
const association = collection.model.associations[this.name];
|
||||
this.database.referenceMap.removeReference(this.reference(association));
|
||||
|
||||
this.clearAccessors();
|
||||
// 删掉 model 的关联字段
|
||||
delete collection.model.associations[this.name];
|
||||
// @ts-ignore
|
||||
|
@ -34,4 +34,20 @@ export abstract class RelationField extends Field {
|
||||
get TargetModel() {
|
||||
return this.context.database.sequelize.models[this.target];
|
||||
}
|
||||
|
||||
protected clearAccessors() {
|
||||
const { collection } = this.context;
|
||||
const association = collection.model.associations[this.name];
|
||||
if (!association) {
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const accessors = Object.values(association.accessors);
|
||||
|
||||
accessors.forEach((accessor) => {
|
||||
// @ts-ignore
|
||||
delete collection.model.prototype[accessor];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,6 @@ import { Model } from './model';
|
||||
|
||||
const { hooks } = require('sequelize/lib/hooks');
|
||||
|
||||
|
||||
|
||||
export class ModelHook {
|
||||
database: Database;
|
||||
|
||||
|
@ -338,7 +338,6 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
|
||||
const guard = UpdateGuard.fromOptions(this.model, { ...options, action: 'create' });
|
||||
const values = guard.sanitize(options.values || {});
|
||||
|
||||
|
||||
const instance = await this.model.create<any>(values, {
|
||||
...options,
|
||||
transaction,
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
HasOne,
|
||||
Hookable,
|
||||
ModelCtor,
|
||||
Transactionable
|
||||
Transactionable,
|
||||
} from 'sequelize';
|
||||
import { Model } from './model';
|
||||
import { UpdateGuard } from './update-guard';
|
||||
@ -375,6 +375,7 @@ export async function updateMultipleAssociation(
|
||||
const setAccessor = association.accessors.set;
|
||||
|
||||
const createAccessor = association.accessors.create;
|
||||
|
||||
if (isUndefinedOrNull(value)) {
|
||||
await model[setAccessor](null, { transaction, context });
|
||||
model.setDataValue(key, null);
|
||||
|
@ -15,12 +15,12 @@ describe('belongsToMany', () => {
|
||||
Field = db.getCollection('fields');
|
||||
await Collection.repository.create({
|
||||
values: {
|
||||
name: 'tests',
|
||||
name: 'posts',
|
||||
},
|
||||
});
|
||||
await Collection.repository.create({
|
||||
values: {
|
||||
name: 'foos',
|
||||
name: 'tags',
|
||||
},
|
||||
});
|
||||
});
|
||||
@ -29,7 +29,5 @@ describe('belongsToMany', () => {
|
||||
await app.destroy();
|
||||
});
|
||||
|
||||
it('a', () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
it('should create belongsToMany field', async () => {});
|
||||
});
|
||||
|
@ -0,0 +1,140 @@
|
||||
import { MockServer } from '@nocobase/test';
|
||||
import { createApp } from '..';
|
||||
|
||||
describe('collections repository', () => {
|
||||
let app: MockServer;
|
||||
let agent;
|
||||
let db;
|
||||
|
||||
beforeEach(async () => {
|
||||
app = await createApp();
|
||||
db = app.db;
|
||||
|
||||
agent = app.agent();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await app.destroy();
|
||||
});
|
||||
|
||||
it('should create belongs to many fields', async () => {
|
||||
await agent.resource('collections').create({
|
||||
values: {
|
||||
name: 'posts',
|
||||
},
|
||||
});
|
||||
|
||||
await agent.resource('collections').create({
|
||||
values: {
|
||||
name: 'tags',
|
||||
},
|
||||
});
|
||||
|
||||
await agent.resource('collections').create({
|
||||
values: {
|
||||
name: 'post_tag',
|
||||
createdBy: true,
|
||||
updatedBy: true,
|
||||
sortable: true,
|
||||
logging: true,
|
||||
fields: [
|
||||
{
|
||||
name: 'id',
|
||||
type: 'integer',
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
allowNull: false,
|
||||
uiSchema: {
|
||||
type: 'number',
|
||||
title: '{{t("ID")}}',
|
||||
'x-component': 'InputNumber',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
interface: 'id',
|
||||
},
|
||||
],
|
||||
title: 'post_tag',
|
||||
},
|
||||
});
|
||||
|
||||
await agent.resource('tags').create({
|
||||
values: {},
|
||||
});
|
||||
|
||||
const response0 = await agent.resource('collections.fields', 'posts').create({
|
||||
values: {
|
||||
foreignKey: 'post_id',
|
||||
otherKey: 'tag_id',
|
||||
name: 'tags',
|
||||
type: 'belongsToMany',
|
||||
uiSchema: {
|
||||
'x-component': 'RecordPicker',
|
||||
'x-component-props': {
|
||||
multiple: true,
|
||||
fieldNames: {
|
||||
label: 'id',
|
||||
value: 'id',
|
||||
},
|
||||
},
|
||||
title: 'tags',
|
||||
},
|
||||
interface: 'm2m',
|
||||
through: 'post_tag',
|
||||
target: 'tags',
|
||||
},
|
||||
});
|
||||
|
||||
expect(response0.status).toBe(200);
|
||||
|
||||
const TagRepository = db.getCollection('tags').repository;
|
||||
const PostRepository = db.getCollection('posts').repository;
|
||||
|
||||
const tag = await TagRepository.findOne();
|
||||
|
||||
expect(!!tag).toBeTruthy();
|
||||
|
||||
const response = await agent.resource('posts').create({
|
||||
values: {
|
||||
tags: [tag.id],
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const post = await PostRepository.findOne({
|
||||
appends: ['tags'],
|
||||
});
|
||||
|
||||
expect(post.tags.length).toBe(1);
|
||||
|
||||
expect(PostRepository.model.associations['tags']).toBeDefined();
|
||||
|
||||
// destroy through table
|
||||
await agent.resource('collections').destroy({
|
||||
filterByTk: 'post_tag',
|
||||
});
|
||||
|
||||
// association deleted
|
||||
expect(PostRepository.model.associations['tags']).not.toBeDefined();
|
||||
|
||||
// create association again
|
||||
await agent.resource('collections.fields', 'posts').create({
|
||||
values: {
|
||||
name: 'tags',
|
||||
type: 'belongsToMany',
|
||||
target: 'tags',
|
||||
through: 'random_2', //difference name
|
||||
foreignKey: 'post_id',
|
||||
otherKey: 'tag_id',
|
||||
},
|
||||
});
|
||||
|
||||
const response2 = await agent.resource('posts').create({
|
||||
values: {
|
||||
tags: [tag.id],
|
||||
},
|
||||
});
|
||||
|
||||
expect(response2.status).toBe(200);
|
||||
});
|
||||
});
|
@ -54,6 +54,7 @@ export class CollectionModel extends MagicAttributeModel {
|
||||
const { transaction } = options || {};
|
||||
const name = this.get('name');
|
||||
const collection = this.db.getCollection(name);
|
||||
|
||||
if (!collection) {
|
||||
return;
|
||||
}
|
||||
@ -72,6 +73,7 @@ export class CollectionModel extends MagicAttributeModel {
|
||||
await field.destroy({ transaction });
|
||||
}
|
||||
}
|
||||
|
||||
await collection.removeFromDb({
|
||||
transaction,
|
||||
});
|
||||
|
@ -83,7 +83,7 @@ export class CollectionManagerPlugin extends Plugin {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// after migrate
|
||||
this.app.db.on('fields.afterCreate', afterCreateForForeignKeyField(this.app.db));
|
||||
|
||||
@ -108,7 +108,7 @@ export class CollectionManagerPlugin extends Plugin {
|
||||
}
|
||||
});
|
||||
|
||||
this.app.db.on('fields.afterSaveWithAssociations', async (model, { context, transaction }) => {
|
||||
this.app.db.on('fields.afterSaveWithAssociations', async (model: FieldModel, { context, transaction }) => {
|
||||
if (context) {
|
||||
await model.load({ transaction });
|
||||
}
|
||||
@ -120,7 +120,7 @@ export class CollectionManagerPlugin extends Plugin {
|
||||
await model.remove(options);
|
||||
});
|
||||
|
||||
this.app.db.on('collections.beforeDestroy', async (model, options) => {
|
||||
this.app.db.on('collections.beforeDestroy', async (model: CollectionModel, options) => {
|
||||
await model.remove(options);
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user