fix: create collections with multiple records (#2753)
* fix: create collections with multiple items * chore: test * fix: sync collection when pending field resolved * fix: test
This commit is contained in:
parent
a1ee2afabd
commit
1defb5db51
@ -555,7 +555,7 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
|
|||||||
const instances = [];
|
const instances = [];
|
||||||
|
|
||||||
for (const values of records) {
|
for (const values of records) {
|
||||||
const instance = await this.create({ values, transaction });
|
const instance = await this.create({ ...options, values, transaction });
|
||||||
instances.push(instance);
|
instances.push(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,165 @@ describe('collections repository', () => {
|
|||||||
await app.destroy();
|
await app.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should create through table when pending fields', async () => {
|
||||||
|
await Collection.repository.create({
|
||||||
|
values: {
|
||||||
|
name: 'posts',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'title',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'belongsToMany',
|
||||||
|
target: 'tags',
|
||||||
|
name: 'tags',
|
||||||
|
through: 'posts_tags',
|
||||||
|
foreignKey: 'post_id',
|
||||||
|
otherKey: 'tag_id',
|
||||||
|
sourceKey: 'id',
|
||||||
|
targetKey: 'id',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
context: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const postsCollection = db.getCollection('posts');
|
||||||
|
expect(postsCollection).toBeTruthy();
|
||||||
|
|
||||||
|
await Collection.repository.create({
|
||||||
|
values: {
|
||||||
|
name: 'tags',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'name',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
context: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const throughCollection = db.getCollection('posts_tags');
|
||||||
|
expect(throughCollection).toBeTruthy();
|
||||||
|
|
||||||
|
expect(await throughCollection.existsInDb()).toBeTruthy();
|
||||||
|
const rawAttribute = throughCollection.model.rawAttributes['tag_id'].field;
|
||||||
|
const columns = await db.sequelize.getQueryInterface().describeTable(throughCollection.getTableNameWithSchema());
|
||||||
|
expect(columns[rawAttribute]).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create collections with array', async () => {
|
||||||
|
await Collection.repository.create({
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
name: 'users',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'username',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'hasMany',
|
||||||
|
target: 'posts',
|
||||||
|
name: 'posts',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'hasOne',
|
||||||
|
target: 'profiles',
|
||||||
|
name: 'profile',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'profiles',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'nickname',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'belongsTo',
|
||||||
|
target: 'users',
|
||||||
|
name: 'user',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'posts',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'title',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'belongsToMany',
|
||||||
|
target: 'tags',
|
||||||
|
name: 'tags',
|
||||||
|
through: 'posts_tags',
|
||||||
|
foreignKey: 'post_id',
|
||||||
|
otherKey: 'tag_id',
|
||||||
|
sourceKey: 'id',
|
||||||
|
targetKey: 'id',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tags',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'belongsToMany',
|
||||||
|
target: 'posts',
|
||||||
|
name: 'posts',
|
||||||
|
through: 'posts_tags',
|
||||||
|
foreignKey: 'tag_id',
|
||||||
|
otherKey: 'post_id',
|
||||||
|
sourceKey: 'id',
|
||||||
|
targetKey: 'id',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
context: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const postsCollection = db.getCollection('posts');
|
||||||
|
expect(postsCollection).toBeTruthy();
|
||||||
|
|
||||||
|
const tagsCollection = db.getCollection('tags');
|
||||||
|
expect(tagsCollection).toBeTruthy();
|
||||||
|
|
||||||
|
const usersCollection = db.getCollection('users');
|
||||||
|
expect(usersCollection).toBeTruthy();
|
||||||
|
|
||||||
|
const profilesCollection = db.getCollection('profiles');
|
||||||
|
expect(profilesCollection).toBeTruthy();
|
||||||
|
|
||||||
|
await usersCollection.repository.create({
|
||||||
|
values: {
|
||||||
|
username: 'admin',
|
||||||
|
profile: {
|
||||||
|
nickname: '管理员',
|
||||||
|
},
|
||||||
|
posts: [
|
||||||
|
{
|
||||||
|
title: 'test',
|
||||||
|
tags: [
|
||||||
|
{
|
||||||
|
name: 'test',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should create collection with description', async () => {
|
it('should create collection with description', async () => {
|
||||||
const description = 'this collection is for tests';
|
const description = 'this collection is for tests';
|
||||||
await Collection.repository.create({
|
await Collection.repository.create({
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import Database, { Collection, MagicAttributeModel, SyncOptions, Transactionable } from '@nocobase/database';
|
import Database, { Collection, MagicAttributeModel, SyncOptions, Transactionable } from '@nocobase/database';
|
||||||
import lodash from 'lodash';
|
import lodash from 'lodash';
|
||||||
import { FieldModel } from './field';
|
import { FieldModel } from './field';
|
||||||
|
import { async } from 'fast-glob';
|
||||||
|
|
||||||
interface LoadOptions extends Transactionable {
|
interface LoadOptions extends Transactionable {
|
||||||
// TODO
|
// TODO
|
||||||
@ -114,10 +115,26 @@ export class CollectionModel extends MagicAttributeModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async migrate(options?: SyncOptions & Transactionable) {
|
async migrate(options?: SyncOptions & Transactionable) {
|
||||||
|
const pendingFieldsTargetToThis = this.db.pendingFields.get(this.get('name')) || [];
|
||||||
|
const getPendingField = () =>
|
||||||
|
pendingFieldsTargetToThis.map((field) => {
|
||||||
|
return {
|
||||||
|
name: field.get('name'),
|
||||||
|
collectionName: field.get('collectionName'),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const beforePendingFields = getPendingField();
|
||||||
|
|
||||||
const collection = await this.load({
|
const collection = await this.load({
|
||||||
transaction: options?.transaction,
|
transaction: options?.transaction,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const afterPendingFields = getPendingField();
|
||||||
|
|
||||||
|
const resolvedPendingFields = lodash.differenceWith(beforePendingFields, afterPendingFields, lodash.isEqual);
|
||||||
|
const resolvedPendingFieldsCollections = lodash.uniq(resolvedPendingFields.map((field) => field.collectionName));
|
||||||
|
|
||||||
// postgres support zero column table, other database should not sync it to database
|
// postgres support zero column table, other database should not sync it to database
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (Object.keys(collection.model.tableAttributes).length == 0 && !this.db.inDialect('postgres')) {
|
if (Object.keys(collection.model.tableAttributes).length == 0 && !this.db.inDialect('postgres')) {
|
||||||
@ -125,13 +142,19 @@ export class CollectionModel extends MagicAttributeModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await collection.sync({
|
const syncOptions = {
|
||||||
force: false,
|
force: false,
|
||||||
alter: {
|
alter: {
|
||||||
drop: false,
|
drop: false,
|
||||||
},
|
},
|
||||||
...options,
|
...options,
|
||||||
});
|
};
|
||||||
|
|
||||||
|
await collection.sync(syncOptions);
|
||||||
|
|
||||||
|
for (const collectionName of resolvedPendingFieldsCollections) {
|
||||||
|
await this.db.getCollection(collectionName).sync(syncOptions);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
const name = this.get('name');
|
const name = this.get('name');
|
||||||
|
Loading…
Reference in New Issue
Block a user