feat: no recursive update associations (#1091)

* feat: update associations

* fix(collection-manager): should update uiSchema
This commit is contained in:
chenos 2022-11-15 20:37:26 +08:00 committed by GitHub
parent 2ea185a412
commit 688387413d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 372 additions and 6 deletions

View File

@ -0,0 +1,232 @@
import { Database } from '../database';
import { mockDatabase } from './';
describe('update associations', () => {
let db: Database;
beforeEach(async () => {
db = mockDatabase();
await db.clean({ drop: true });
});
afterEach(async () => {
await db.close();
});
it('hasOne', async () => {
db.collection({
name: 'a',
fields: [
{
type: 'string',
name: 'name',
},
{
type: 'hasOne',
name: 'b',
target: 'b',
},
],
});
db.collection({
name: 'b',
fields: [
{
type: 'string',
name: 'name',
},
{
type: 'hasOne',
name: 'c',
target: 'c',
},
],
});
db.collection({
name: 'c',
fields: [
{
type: 'string',
name: 'name',
},
{
type: 'hasOne',
name: 'd',
target: 'd',
},
],
});
db.collection({
name: 'd',
fields: [
{
type: 'string',
name: 'name',
},
],
});
await db.sync();
const b = await db.getRepository('b').create({
values: {},
});
const c = await db.getRepository('c').create({
values: {},
});
const d = await db.getRepository('d').create({
values: {},
});
await db.getRepository('a').create({
updateAssociationValues: ['b'],
values: {
name: 'a1',
b: {
id: b.id,
c: {
id: c.id,
d: {
id: d.id,
},
},
},
},
});
const d1 = await d.reload();
expect(d1.cId).toBe(c.id);
const b2 = await db.getRepository('b').create({
values: {},
});
const c2 = await db.getRepository('c').create({
values: {},
});
const d2 = await db.getRepository('d').create({
values: {},
});
await db.getRepository('a').create({
values: {
name: 'a1',
b: {
id: b2.id,
c: {
id: c2.id,
d: {
id: d2.id,
},
},
},
},
});
const c22 = await c2.reload();
expect(c22.bId).toBeNull();
const d22 = await d2.reload();
expect(d22.cId).toBeNull();
});
it('hasMany', async () => {
db.collection({
name: 'a',
fields: [
{
type: 'string',
name: 'name',
},
{
type: 'hasMany',
name: 'b',
target: 'b',
},
],
});
db.collection({
name: 'b',
fields: [
{
type: 'string',
name: 'name',
},
{
type: 'hasMany',
name: 'c',
target: 'c',
},
],
});
db.collection({
name: 'c',
fields: [
{
type: 'string',
name: 'name',
},
{
type: 'hasMany',
name: 'd',
target: 'd',
},
],
});
db.collection({
name: 'd',
fields: [
{
type: 'string',
name: 'name',
},
],
});
await db.sync();
const b = await db.getRepository('b').create({
values: {},
});
const c = await db.getRepository('c').create({
values: {},
});
const d = await db.getRepository('d').create({
values: {},
});
await db.getRepository('a').create({
updateAssociationValues: ['b'],
values: {
name: 'a1',
b: {
id: b.id,
c: {
id: c.id,
d: {
id: d.id,
},
},
},
},
});
const d1 = await d.reload();
expect(d1.cId).toBe(c.id);
const b2 = await db.getRepository('b').create({
values: {},
});
const c2 = await db.getRepository('c').create({
values: {},
});
const d2 = await db.getRepository('d').create({
values: {},
});
await db.getRepository('a').create({
values: {
name: 'a1',
b: {
id: b2.id,
c: {
id: c2.id,
d: {
id: d2.id,
},
},
},
},
});
const c22 = await c2.reload();
expect(c22.bId).toBeNull();
const d22 = await d2.reload();
expect(d22.cId).toBeNull();
});
});

View File

@ -83,7 +83,12 @@ describe('update associations', () => {
},
});
expect(post4.toJSON()).toMatchObject({
const p4 = await db.getRepository('posts').findOne({
filterByTk: post4.id,
appends: ['user'],
});
expect(p4?.toJSON()).toMatchObject({
id: 4,
name: 'post4',
userId: 1,

View File

@ -102,5 +102,6 @@ export interface BelongsToManyFieldOptions
extends MultipleRelationFieldOptions,
Omit<SequelizeBelongsToManyOptions, 'through'> {
type: 'belongsToMany';
target?: string;
through?: string;
}

View File

@ -64,6 +64,7 @@ interface UpdateAssociationOptions extends Transactionable, Hookable {
sourceModel?: Model;
context?: any;
associationContext?: any;
recursive?: boolean;
}
export async function updateModelByValues(instance: Model, values: UpdateValue, options?: UpdateOptions) {
@ -120,6 +121,10 @@ export async function updateAssociations(instance: Model, values: any, options:
return;
}
if (options?.updateAssociationValues) {
options.recursive = true;
}
let newTransaction = false;
let transaction = options.transaction;
@ -262,7 +267,7 @@ export async function updateSingleAssociation(
throw new Error(`The value of '${key}' cannot be in array format`);
}
const { context, updateAssociationValues = [], transaction } = options;
const { recursive, context, updateAssociationValues = [], transaction } = options;
const keys = getKeysByPrefix(updateAssociationValues, key);
try {
@ -313,6 +318,10 @@ export async function updateSingleAssociation(
if (instance) {
await model[setAccessor](instance, { context, transaction });
if (!recursive) {
return;
}
if (updateAssociationValues.includes(key)) {
await instance.update(value, { ...options, transaction });
}
@ -368,7 +377,7 @@ export async function updateMultipleAssociation(
return false;
}
const { context, updateAssociationValues = [], transaction } = options;
const { recursive, context, updateAssociationValues = [], transaction } = options;
const keys = getKeysByPrefix(updateAssociationValues, key);
try {
@ -449,6 +458,9 @@ export async function updateMultipleAssociation(
const addAccessor = association.accessors.add;
await model[addAccessor](item[pk], accessorOptions);
if (!recursive) {
continue;
}
if (updateAssociationValues.includes(key)) {
await instance.update(item, { ...options, transaction });
}

View File

@ -1,10 +1,10 @@
import Database, { Collection as DBCollection } from '@nocobase/database';
import Application from '@nocobase/server';
import { MockServer } from '@nocobase/test';
import { createApp } from '..';
describe('reverseField options', () => {
let db: Database;
let app: Application;
let app: MockServer;
let Collection: DBCollection;
let Field: DBCollection;
@ -144,6 +144,7 @@ describe('reverseField options', () => {
await Field.repository.update({
filterByTk: field.get('key') as string,
updateAssociationValues: ['reverseField'],
values: {
reverseField: {
key: reverseField.get('key'),
@ -172,4 +173,110 @@ describe('reverseField options', () => {
const uiSchema = reverseField.get('uiSchema');
expect(uiSchema['schema']).toEqual({ title: '123' });
});
it('should update uiSchema', async () => {
await app
.agent()
.resource('collections')
.create({
values: {
name: 'a',
},
});
const f = await app
.agent()
.resource('collections.fields', 'a')
.create({
values: {
name: 'f_i02fjvduwmv',
interface: 'input',
type: 'string',
uiSchema: { type: 'string', 'x-component': 'Input', title: 'A1' },
},
});
await app
.agent()
.resource('collections.fields', 'a')
.update({
filterByTk: 'f_i02fjvduwmv',
values: {
...f.body.data,
uiSchema: {
...f.body.data.uiSchema,
title: 'A2',
},
},
});
const f2 = await app
.agent()
.resource('collections.fields', 'a')
.get({
filterByTk: 'f_i02fjvduwmv',
appends: ['uiSchema'],
});
expect(f2.body.data.uiSchema.title).toBe('A2');
});
it('should create reverseField uiSchema', async () => {
await app
.agent()
.resource('collections')
.create({
values: {
name: 'a',
},
});
await app
.agent()
.resource('collections')
.create({
values: {
name: 'b',
},
});
await app
.agent()
.resource('collections.fields', 'a')
.create({
values: {
foreignKey: 'f_qnt8iaony6i',
onDelete: 'SET NULL',
reverseField: {
uiSchema: {
title: 'A',
'x-component': 'RecordPicker',
'x-component-props': { multiple: false, fieldNames: { label: 'id', value: 'id' } },
},
interface: 'obo',
type: 'belongsTo',
name: 'f_dctw6v5gsio',
},
name: 'f_d5ebrb4h85m',
type: 'hasOne',
uiSchema: {
'x-component': 'RecordPicker',
'x-component-props': { multiple: false, fieldNames: { label: 'id', value: 'id' } },
title: 'B',
},
interface: 'oho',
target: 'b',
},
});
const f1 = await app
.agent()
.resource('collections.fields', 'b')
.get({
filterByTk: 'f_dctw6v5gsio',
appends: ['uiSchema'],
});
expect(f1.body.data.uiSchema.title).toBe('A');
});
});

View File

@ -13,7 +13,7 @@ import {
beforeCreateForChildrenCollection,
beforeCreateForReverseField,
beforeDestroyForeignKey,
beforeInitOptions,
beforeInitOptions
} from './hooks';
import { CollectionModel, FieldModel } from './models';
@ -180,6 +180,15 @@ export class CollectionManagerPlugin extends Plugin {
return ctx.throw(400, ctx.t(`The value of ${Object.keys(err.fields)} field duplicated`));
},
);
this.app.resourcer.use(async (ctx, next) => {
if (ctx.action.resourceName === 'collections.fields' && ['create', 'update'].includes(ctx.action.actionName)) {
ctx.action.mergeParams({
updateAssociationValues: ['uiSchema', 'reverseField'],
});
}
await next();
});
}
}