feat: no recursive update associations (#1091)
* feat: update associations * fix(collection-manager): should update uiSchema
This commit is contained in:
parent
2ea185a412
commit
688387413d
@ -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();
|
||||
});
|
||||
});
|
@ -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,
|
||||
|
@ -102,5 +102,6 @@ export interface BelongsToManyFieldOptions
|
||||
extends MultipleRelationFieldOptions,
|
||||
Omit<SequelizeBelongsToManyOptions, 'through'> {
|
||||
type: 'belongsToMany';
|
||||
target?: string;
|
||||
through?: string;
|
||||
}
|
||||
|
@ -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 });
|
||||
}
|
||||
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user