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;
|
return results;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
await transaction.rollback();
|
await transaction.rollback();
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
@ -93,6 +93,7 @@ export class BelongsToField extends RelationField {
|
|||||||
const association = collection.model.associations[this.name];
|
const association = collection.model.associations[this.name];
|
||||||
this.database.referenceMap.removeReference(this.reference(association));
|
this.database.referenceMap.removeReference(this.reference(association));
|
||||||
|
|
||||||
|
this.clearAccessors();
|
||||||
// 删掉 model 的关联字段
|
// 删掉 model 的关联字段
|
||||||
delete collection.model.associations[this.name];
|
delete collection.model.associations[this.name];
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -23,7 +23,9 @@ export class BelongsToManyField extends RelationField {
|
|||||||
|
|
||||||
bind() {
|
bind() {
|
||||||
const { database, collection } = this.context;
|
const { database, collection } = this.context;
|
||||||
|
|
||||||
const Target = this.TargetModel;
|
const Target = this.TargetModel;
|
||||||
|
|
||||||
if (!Target) {
|
if (!Target) {
|
||||||
database.addPendingField(this);
|
database.addPendingField(this);
|
||||||
return false;
|
return false;
|
||||||
@ -86,9 +88,12 @@ export class BelongsToManyField extends RelationField {
|
|||||||
unbind() {
|
unbind() {
|
||||||
const { database, collection } = this.context;
|
const { database, collection } = this.context;
|
||||||
const Through = database.getCollection(this.through);
|
const Through = database.getCollection(this.through);
|
||||||
|
|
||||||
// 如果关系字段还没建立就删除了,也同步删除待建立关联的关系字段
|
// 如果关系字段还没建立就删除了,也同步删除待建立关联的关系字段
|
||||||
database.removePendingField(this);
|
database.removePendingField(this);
|
||||||
// 删掉 model 的关联字段
|
// 删掉 model 的关联字段
|
||||||
|
|
||||||
|
this.clearAccessors();
|
||||||
delete collection.model.associations[this.name];
|
delete collection.model.associations[this.name];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
ModelIndexesOptions,
|
ModelIndexesOptions,
|
||||||
QueryInterfaceOptions,
|
QueryInterfaceOptions,
|
||||||
SyncOptions,
|
SyncOptions,
|
||||||
Transactionable
|
Transactionable,
|
||||||
} from 'sequelize';
|
} from 'sequelize';
|
||||||
import { Collection } from '../collection';
|
import { Collection } from '../collection';
|
||||||
import { Database } from '../database';
|
import { Database } from '../database';
|
||||||
|
@ -172,6 +172,7 @@ export class HasManyField extends RelationField {
|
|||||||
this.database.referenceMap.removeReference(this.reference(association));
|
this.database.referenceMap.removeReference(this.reference(association));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.clearAccessors();
|
||||||
// 删掉 model 的关联字段
|
// 删掉 model 的关联字段
|
||||||
delete collection.model.associations[this.name];
|
delete collection.model.associations[this.name];
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -168,6 +168,7 @@ export class HasOneField extends RelationField {
|
|||||||
const association = collection.model.associations[this.name];
|
const association = collection.model.associations[this.name];
|
||||||
this.database.referenceMap.removeReference(this.reference(association));
|
this.database.referenceMap.removeReference(this.reference(association));
|
||||||
|
|
||||||
|
this.clearAccessors();
|
||||||
// 删掉 model 的关联字段
|
// 删掉 model 的关联字段
|
||||||
delete collection.model.associations[this.name];
|
delete collection.model.associations[this.name];
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -34,4 +34,20 @@ export abstract class RelationField extends Field {
|
|||||||
get TargetModel() {
|
get TargetModel() {
|
||||||
return this.context.database.sequelize.models[this.target];
|
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');
|
const { hooks } = require('sequelize/lib/hooks');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export class ModelHook {
|
export class ModelHook {
|
||||||
database: Database;
|
database: Database;
|
||||||
|
|
||||||
|
@ -338,7 +338,6 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
|
|||||||
const guard = UpdateGuard.fromOptions(this.model, { ...options, action: 'create' });
|
const guard = UpdateGuard.fromOptions(this.model, { ...options, action: 'create' });
|
||||||
const values = guard.sanitize(options.values || {});
|
const values = guard.sanitize(options.values || {});
|
||||||
|
|
||||||
|
|
||||||
const instance = await this.model.create<any>(values, {
|
const instance = await this.model.create<any>(values, {
|
||||||
...options,
|
...options,
|
||||||
transaction,
|
transaction,
|
||||||
|
@ -6,7 +6,7 @@ import {
|
|||||||
HasOne,
|
HasOne,
|
||||||
Hookable,
|
Hookable,
|
||||||
ModelCtor,
|
ModelCtor,
|
||||||
Transactionable
|
Transactionable,
|
||||||
} from 'sequelize';
|
} from 'sequelize';
|
||||||
import { Model } from './model';
|
import { Model } from './model';
|
||||||
import { UpdateGuard } from './update-guard';
|
import { UpdateGuard } from './update-guard';
|
||||||
@ -375,6 +375,7 @@ export async function updateMultipleAssociation(
|
|||||||
const setAccessor = association.accessors.set;
|
const setAccessor = association.accessors.set;
|
||||||
|
|
||||||
const createAccessor = association.accessors.create;
|
const createAccessor = association.accessors.create;
|
||||||
|
|
||||||
if (isUndefinedOrNull(value)) {
|
if (isUndefinedOrNull(value)) {
|
||||||
await model[setAccessor](null, { transaction, context });
|
await model[setAccessor](null, { transaction, context });
|
||||||
model.setDataValue(key, null);
|
model.setDataValue(key, null);
|
||||||
|
@ -15,12 +15,12 @@ describe('belongsToMany', () => {
|
|||||||
Field = db.getCollection('fields');
|
Field = db.getCollection('fields');
|
||||||
await Collection.repository.create({
|
await Collection.repository.create({
|
||||||
values: {
|
values: {
|
||||||
name: 'tests',
|
name: 'posts',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
await Collection.repository.create({
|
await Collection.repository.create({
|
||||||
values: {
|
values: {
|
||||||
name: 'foos',
|
name: 'tags',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -29,7 +29,5 @@ describe('belongsToMany', () => {
|
|||||||
await app.destroy();
|
await app.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('a', () => {
|
it('should create belongsToMany field', async () => {});
|
||||||
expect(true).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -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 { transaction } = options || {};
|
||||||
const name = this.get('name');
|
const name = this.get('name');
|
||||||
const collection = this.db.getCollection(name);
|
const collection = this.db.getCollection(name);
|
||||||
|
|
||||||
if (!collection) {
|
if (!collection) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -72,6 +73,7 @@ export class CollectionModel extends MagicAttributeModel {
|
|||||||
await field.destroy({ transaction });
|
await field.destroy({ transaction });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await collection.removeFromDb({
|
await collection.removeFromDb({
|
||||||
transaction,
|
transaction,
|
||||||
});
|
});
|
||||||
|
@ -83,7 +83,7 @@ export class CollectionManagerPlugin extends Plugin {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// after migrate
|
// after migrate
|
||||||
this.app.db.on('fields.afterCreate', afterCreateForForeignKeyField(this.app.db));
|
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) {
|
if (context) {
|
||||||
await model.load({ transaction });
|
await model.load({ transaction });
|
||||||
}
|
}
|
||||||
@ -120,7 +120,7 @@ export class CollectionManagerPlugin extends Plugin {
|
|||||||
await model.remove(options);
|
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);
|
await model.remove(options);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user