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,
|
id: 4,
|
||||||
name: 'post4',
|
name: 'post4',
|
||||||
userId: 1,
|
userId: 1,
|
||||||
|
@ -102,5 +102,6 @@ export interface BelongsToManyFieldOptions
|
|||||||
extends MultipleRelationFieldOptions,
|
extends MultipleRelationFieldOptions,
|
||||||
Omit<SequelizeBelongsToManyOptions, 'through'> {
|
Omit<SequelizeBelongsToManyOptions, 'through'> {
|
||||||
type: 'belongsToMany';
|
type: 'belongsToMany';
|
||||||
|
target?: string;
|
||||||
through?: string;
|
through?: string;
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,7 @@ interface UpdateAssociationOptions extends Transactionable, Hookable {
|
|||||||
sourceModel?: Model;
|
sourceModel?: Model;
|
||||||
context?: any;
|
context?: any;
|
||||||
associationContext?: any;
|
associationContext?: any;
|
||||||
|
recursive?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateModelByValues(instance: Model, values: UpdateValue, options?: UpdateOptions) {
|
export async function updateModelByValues(instance: Model, values: UpdateValue, options?: UpdateOptions) {
|
||||||
@ -120,6 +121,10 @@ export async function updateAssociations(instance: Model, values: any, options:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options?.updateAssociationValues) {
|
||||||
|
options.recursive = true;
|
||||||
|
}
|
||||||
|
|
||||||
let newTransaction = false;
|
let newTransaction = false;
|
||||||
let transaction = options.transaction;
|
let transaction = options.transaction;
|
||||||
|
|
||||||
@ -262,7 +267,7 @@ export async function updateSingleAssociation(
|
|||||||
throw new Error(`The value of '${key}' cannot be in array format`);
|
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);
|
const keys = getKeysByPrefix(updateAssociationValues, key);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -313,6 +318,10 @@ export async function updateSingleAssociation(
|
|||||||
if (instance) {
|
if (instance) {
|
||||||
await model[setAccessor](instance, { context, transaction });
|
await model[setAccessor](instance, { context, transaction });
|
||||||
|
|
||||||
|
if (!recursive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (updateAssociationValues.includes(key)) {
|
if (updateAssociationValues.includes(key)) {
|
||||||
await instance.update(value, { ...options, transaction });
|
await instance.update(value, { ...options, transaction });
|
||||||
}
|
}
|
||||||
@ -368,7 +377,7 @@ export async function updateMultipleAssociation(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { context, updateAssociationValues = [], transaction } = options;
|
const { recursive, context, updateAssociationValues = [], transaction } = options;
|
||||||
const keys = getKeysByPrefix(updateAssociationValues, key);
|
const keys = getKeysByPrefix(updateAssociationValues, key);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -449,6 +458,9 @@ export async function updateMultipleAssociation(
|
|||||||
const addAccessor = association.accessors.add;
|
const addAccessor = association.accessors.add;
|
||||||
|
|
||||||
await model[addAccessor](item[pk], accessorOptions);
|
await model[addAccessor](item[pk], accessorOptions);
|
||||||
|
if (!recursive) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (updateAssociationValues.includes(key)) {
|
if (updateAssociationValues.includes(key)) {
|
||||||
await instance.update(item, { ...options, transaction });
|
await instance.update(item, { ...options, transaction });
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import Database, { Collection as DBCollection } from '@nocobase/database';
|
import Database, { Collection as DBCollection } from '@nocobase/database';
|
||||||
import Application from '@nocobase/server';
|
import { MockServer } from '@nocobase/test';
|
||||||
import { createApp } from '..';
|
import { createApp } from '..';
|
||||||
|
|
||||||
describe('reverseField options', () => {
|
describe('reverseField options', () => {
|
||||||
let db: Database;
|
let db: Database;
|
||||||
let app: Application;
|
let app: MockServer;
|
||||||
let Collection: DBCollection;
|
let Collection: DBCollection;
|
||||||
let Field: DBCollection;
|
let Field: DBCollection;
|
||||||
|
|
||||||
@ -144,6 +144,7 @@ describe('reverseField options', () => {
|
|||||||
|
|
||||||
await Field.repository.update({
|
await Field.repository.update({
|
||||||
filterByTk: field.get('key') as string,
|
filterByTk: field.get('key') as string,
|
||||||
|
updateAssociationValues: ['reverseField'],
|
||||||
values: {
|
values: {
|
||||||
reverseField: {
|
reverseField: {
|
||||||
key: reverseField.get('key'),
|
key: reverseField.get('key'),
|
||||||
@ -172,4 +173,110 @@ describe('reverseField options', () => {
|
|||||||
const uiSchema = reverseField.get('uiSchema');
|
const uiSchema = reverseField.get('uiSchema');
|
||||||
expect(uiSchema['schema']).toEqual({ title: '123' });
|
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,
|
beforeCreateForChildrenCollection,
|
||||||
beforeCreateForReverseField,
|
beforeCreateForReverseField,
|
||||||
beforeDestroyForeignKey,
|
beforeDestroyForeignKey,
|
||||||
beforeInitOptions,
|
beforeInitOptions
|
||||||
} from './hooks';
|
} from './hooks';
|
||||||
|
|
||||||
import { CollectionModel, FieldModel } from './models';
|
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`));
|
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