fix: update association value

This commit is contained in:
Chareice 2021-12-08 13:00:09 +08:00
parent f09991d932
commit 90846686ac
6 changed files with 123 additions and 55 deletions

View File

@ -35,44 +35,6 @@ describe('database', () => {
expect(imageCollection.fields.has('url2')).toBeTruthy(); expect(imageCollection.fields.has('url2')).toBeTruthy();
}); });
test('hasMany with inverse belongsTo relation', async () => {
const db = mockDatabase();
const UserCollection = db.collection({
name: 'users',
fields: [
{ type: 'string', name: 'name' },
{ type: 'hasMany', name: 'posts' },
],
});
const PostCollection = db.collection({
name: 'posts',
fields: [{ type: 'string', name: 'title' }],
});
expect(UserCollection.model.associations.posts).toBeDefined();
expect(PostCollection.model.associations.user).toBeDefined();
});
test('belongsTo with inverse hasMany relation', async () => {
const db = mockDatabase();
const UserCollection = db.collection({
name: 'users',
fields: [{ type: 'string', name: 'name' }],
});
const PostCollection = db.collection({
name: 'posts',
fields: [
{ type: 'string', name: 'title' },
{ type: 'belongsTo', name: 'user' },
],
});
expect(PostCollection.model.associations.user).toBeDefined();
expect(UserCollection.model.associations.posts).toBeDefined();
});
test('get collection', async () => { test('get collection', async () => {
const db = mockDatabase(); const db = mockDatabase();
expect(db.getCollection('test')).toBeUndefined(); expect(db.getCollection('test')).toBeUndefined();

View File

@ -17,6 +17,7 @@ describe('option parser', () => {
fields: [ fields: [
{ type: 'string', name: 'name' }, { type: 'string', name: 'name' },
{ type: 'integer', name: 'age' }, { type: 'integer', name: 'age' },
{ type: 'hasMany', name: 'posts' },
], ],
}); });

View File

@ -1,8 +1,10 @@
import { mockDatabase } from '../index'; import { mockDatabase } from '../index';
import { BelongsToManyRepository } from '../../relation-repository/belongs-to-many-repository'; import { BelongsToManyRepository } from '../../relation-repository/belongs-to-many-repository';
import Database from '../../database';
describe('belongs to many', () => { describe('belongs to many', () => {
let db; let db: Database;
let User;
let Post; let Post;
let Tag; let Tag;
let PostTag; let PostTag;
@ -14,11 +16,29 @@ describe('belongs to many', () => {
fields: [{ type: 'string', name: 'tagged_at' }], fields: [{ type: 'string', name: 'tagged_at' }],
}); });
User = db.collection({
name: 'users',
fields: [
{
type: 'string',
name: 'name',
},
{
type: 'hasMany',
name: 'posts',
},
],
});
Post = db.collection({ Post = db.collection({
name: 'posts', name: 'posts',
fields: [ fields: [
{ type: 'belongsToMany', name: 'tags', through: 'posts_tags' }, { type: 'belongsToMany', name: 'tags', through: 'posts_tags' },
{ type: 'string', name: 'title' }, { type: 'string', name: 'title' },
{
type: 'belongsTo',
name: 'user',
},
], ],
}); });
@ -248,6 +268,50 @@ describe('belongs to many', () => {
expect(p2Tag.posts_tags.tagged_at).toBeNull(); expect(p2Tag.posts_tags.tagged_at).toBeNull();
}); });
test('update association values', async () => {
const u1 = await User.repository.create({
values: {
name: 'u1',
},
});
const p1 = await Post.repository.create({
values: {
title: 'p1',
tags: [{ name: 't1' }, { name: 't2' }],
user: u1.id,
},
});
const tag = await Tag.repository.findOne();
const tagPostsRepository = new BelongsToManyRepository(Tag, 'posts', tag.id);
await tagPostsRepository.update({
values: {
user: {
id: u1.get('id'),
name: 'u0',
},
},
});
await u1.reload();
expect(u1.get('name')).toEqual('u1');
await tagPostsRepository.update({
values: {
user: {
id: u1.get('id'),
name: 'u0',
},
},
updateAssociationValues: ['user'],
});
await u1.reload();
expect(u1.get('name')).toEqual('u0');
});
test('add', async () => { test('add', async () => {
let t1 = await Tag.repository.create({ let t1 = await Tag.repository.create({
values: { name: 't1' }, values: { name: 't1' },

View File

@ -21,12 +21,16 @@ describe('repository.find', () => {
name: 'posts', name: 'posts',
fields: [ fields: [
{ type: 'string', name: 'name' }, { type: 'string', name: 'name' },
{ type: 'belongsTo', name: 'user' },
{ type: 'hasMany', name: 'comments' }, { type: 'hasMany', name: 'comments' },
], ],
}); });
Comment = db.collection({ Comment = db.collection({
name: 'comments', name: 'comments',
fields: [{ type: 'string', name: 'name' }], fields: [
{ type: 'string', name: 'name' },
{ type: 'belongsTo', name: 'post' },
],
}); });
await db.sync(); await db.sync();
await User.repository.createMany({ await User.repository.createMany({

View File

@ -15,6 +15,7 @@ describe('repository find', () => {
fields: [ fields: [
{ type: 'string', name: 'name' }, { type: 'string', name: 'name' },
{ type: 'integer', name: 'age' }, { type: 'integer', name: 'age' },
{ type: 'hasMany', name: 'posts' },
], ],
}); });

View File

@ -9,6 +9,8 @@ import {
BelongsTo, BelongsTo,
BelongsToMany, BelongsToMany,
HasMany, HasMany,
Transactionable,
Hookable,
} from 'sequelize'; } from 'sequelize';
import { UpdateGuard } from './update-guard'; import { UpdateGuard } from './update-guard';
import { TransactionAble } from './repository'; import { TransactionAble } from './repository';
@ -21,6 +23,10 @@ function isStringOrNumber(value: any) {
return typeof value === 'string' || typeof value === 'number'; return typeof value === 'string' || typeof value === 'number';
} }
function getKeysByPrefix(keys: string[], prefix: string) {
return keys.filter((key) => key.startsWith(`${prefix}.`)).map((key) => key.substring(prefix.length + 1));
}
export function modelAssociations(instance: Model) { export function modelAssociations(instance: Model) {
return (<typeof Model>instance.constructor).associations; return (<typeof Model>instance.constructor).associations;
} }
@ -57,6 +63,11 @@ interface UpdateOptions extends TransactionAble {
sourceModel?: Model; sourceModel?: Model;
} }
interface UpdateAssociationOptions extends Transactionable, Hookable {
updateAssociationValues?: string[];
sourceModel?: Model;
}
export async function updateModelByValues(instance: Model, values: UpdateValue, options?: UpdateOptions) { export async function updateModelByValues(instance: Model, values: UpdateValue, options?: UpdateOptions) {
if (!options?.sanitized) { if (!options?.sanitized) {
const guard = new UpdateGuard(); const guard = new UpdateGuard();
@ -105,7 +116,7 @@ export async function updateThroughTableValue(
* @param values * @param values
* @param options * @param options
*/ */
export async function updateAssociations(instance: Model, values: any, options: any = {}) { export async function updateAssociations(instance: Model, values: any, options: UpdateAssociationOptions = {}) {
// if no values set, return // if no values set, return
if (!values) { if (!values) {
return; return;
@ -119,8 +130,10 @@ export async function updateAssociations(instance: Model, values: any, options:
transaction = await instance.sequelize.transaction(); transaction = await instance.sequelize.transaction();
} }
const keys = Object.keys(values);
for (const key of Object.keys(modelAssociations(instance))) { for (const key of Object.keys(modelAssociations(instance))) {
if (values[key]) { if (keys.includes(key)) {
await updateAssociation(instance, key, values[key], { await updateAssociation(instance, key, values[key], {
...options, ...options,
transaction, transaction,
@ -159,7 +172,12 @@ export async function updateAssociations(instance: Model, values: any, options:
* @param value * @param value
* @param options * @param options
*/ */
export async function updateAssociation(instance: Model, key: string, value: any, options: any = {}) { export async function updateAssociation(
instance: Model,
key: string,
value: any,
options: UpdateAssociationOptions = {},
) {
const association = modelAssociationByKey(instance, key); const association = modelAssociationByKey(instance, key);
if (!association) { if (!association) {
@ -183,7 +201,12 @@ export async function updateAssociation(instance: Model, key: string, value: any
* @param value * @param value
* @param options * @param options
*/ */
export async function updateSingleAssociation(model: Model, key: string, value: any, options: any = {}) { export async function updateSingleAssociation(
model: Model,
key: string,
value: any,
options: UpdateAssociationOptions = {},
) {
const association = <HasOne | BelongsTo>modelAssociationByKey(model, key); const association = <HasOne | BelongsTo>modelAssociationByKey(model, key);
if (!association) { if (!association) {
@ -194,7 +217,8 @@ export async function updateSingleAssociation(model: Model, key: string, value:
return false; return false;
} }
const { transaction = await model.sequelize.transaction() } = options; const { updateAssociationValues = [], transaction = await model.sequelize.transaction() } = options;
const keys = getKeysByPrefix(updateAssociationValues, key);
try { try {
// set method of association // set method of association
@ -220,6 +244,7 @@ export async function updateSingleAssociation(model: Model, key: string, value:
} }
return true; return true;
} }
if (value instanceof Model) { if (value instanceof Model) {
await model[setAccessor](value); await model[setAccessor](value);
model.setDataValue(key, value); model.setDataValue(key, value);
@ -248,9 +273,15 @@ export async function updateSingleAssociation(model: Model, key: string, value:
}, },
transaction, transaction,
}); });
if (instance) { if (instance) {
await model[setAccessor](instance); await model[setAccessor](instance);
await updateAssociations(instance, value, { transaction, ...options });
if (updateAssociationValues.includes(key)) {
await instance.update(value, { ...options, transaction });
}
await updateAssociations(instance, value, { ...options, transaction, updateAssociationValues: keys });
model.setDataValue(key, instance); model.setDataValue(key, instance);
if (!options.transaction) { if (!options.transaction) {
await transaction.commit(); await transaction.commit();
@ -259,12 +290,8 @@ export async function updateSingleAssociation(model: Model, key: string, value:
} }
} }
if (value[dataKey] === null) {
return await removeAssociation();
}
const instance = await model[createAccessor](value, { transaction }); const instance = await model[createAccessor](value, { transaction });
await updateAssociations(instance, value, { transaction, ...options }); await updateAssociations(instance, value, { ...options, transaction, updateAssociationValues: keys });
model.setDataValue(key, instance); model.setDataValue(key, instance);
// @ts-ignore // @ts-ignore
if (association.targetKey) { if (association.targetKey) {
@ -288,7 +315,12 @@ export async function updateSingleAssociation(model: Model, key: string, value:
* @param value * @param value
* @param options * @param options
*/ */
export async function updateMultipleAssociation(model: Model, key: string, value: any, options: any = {}) { export async function updateMultipleAssociation(
model: Model,
key: string,
value: any,
options: UpdateAssociationOptions = {},
) {
const association = <BelongsToMany | HasMany>modelAssociationByKey(model, key); const association = <BelongsToMany | HasMany>modelAssociationByKey(model, key);
if (!association) { if (!association) {
@ -299,7 +331,8 @@ export async function updateMultipleAssociation(model: Model, key: string, value
return false; return false;
} }
const { transaction = await model.sequelize.transaction() } = options; const { updateAssociationValues = [], transaction = await model.sequelize.transaction() } = options;
const keys = getKeysByPrefix(updateAssociationValues, key);
try { try {
const setAccessor = association.accessors.set; const setAccessor = association.accessors.set;
@ -359,7 +392,7 @@ export async function updateMultipleAssociation(model: Model, key: string, value
if (isUndefinedOrNull(item[pk])) { if (isUndefinedOrNull(item[pk])) {
// create new record // create new record
const instance = await model[createAccessor](item, accessorOptions); const instance = await model[createAccessor](item, accessorOptions);
await updateAssociations(instance, item, { transaction, ...options }); await updateAssociations(instance, item, { ...options, transaction, updateAssociationValues: keys });
list3.push(instance); list3.push(instance);
} else { } else {
// set & update record // set & update record
@ -369,7 +402,10 @@ export async function updateMultipleAssociation(model: Model, key: string, value
const addAccessor = association.accessors.add; const addAccessor = association.accessors.add;
await model[addAccessor](item[pk], accessorOptions); await model[addAccessor](item[pk], accessorOptions);
await updateAssociations(instance, item, { transaction, ...options }); if (updateAssociationValues.includes(key)) {
await instance.update(item, { ...options, transaction });
}
await updateAssociations(instance, item, { ...options, transaction, updateAssociationValues: keys });
list3.push(instance); list3.push(instance);
} }
} }