tachybase_todo/packages/database-next/src/update-associations.ts

223 lines
6.0 KiB
TypeScript
Raw Normal View History

2021-09-23 00:16:04 +08:00
import {
Sequelize,
ModelCtor,
Model,
DataTypes,
Utils,
Association,
} from 'sequelize';
function isUndefinedOrNull(value: any) {
return typeof value === 'undefined' || value === null;
}
function isStringOrNumber(value: any) {
return typeof value === 'string' || typeof value === 'number';
}
export async function updateAssociations(
2021-09-27 15:28:32 +08:00
instance: Model,
2021-09-23 00:16:04 +08:00
values: any,
options: any = {},
) {
2021-09-27 15:28:32 +08:00
const { transaction = await instance.sequelize.transaction() } = options;
2021-09-23 00:16:04 +08:00
// @ts-ignore
2021-09-27 15:28:32 +08:00
for (const key of Object.keys(instance.constructor.associations)) {
2021-09-23 00:16:04 +08:00
// 如果 key 不存在才跳过
2021-09-27 15:28:32 +08:00
if (!Object.keys(values||{}).includes(key)) {
2021-09-23 00:16:04 +08:00
continue;
}
2021-09-27 15:28:32 +08:00
await updateAssociation(instance, key, values[key], {
2021-09-23 00:16:04 +08:00
...options,
transaction,
});
}
if (!options.transaction) {
await transaction.commit();
}
}
export async function updateAssociation(
2021-09-27 15:28:32 +08:00
instance: Model,
2021-09-23 00:16:04 +08:00
key: string,
value: any,
options: any = {},
) {
// @ts-ignore
2021-09-27 15:28:32 +08:00
const association = instance.constructor.associations[key] as Association;
2021-09-23 00:16:04 +08:00
if (!association) {
return false;
}
switch (association.associationType) {
case 'HasOne':
case 'BelongsTo':
2021-09-27 15:28:32 +08:00
return updateSingleAssociation(instance, key, value, options);
2021-09-23 00:16:04 +08:00
case 'HasMany':
case 'BelongsToMany':
2021-09-27 15:28:32 +08:00
return updateMultipleAssociation(instance, key, value, options);
2021-09-23 00:16:04 +08:00
}
}
export async function updateSingleAssociation(
model: Model,
key: string,
value: any,
options: any = {},
) {
// @ts-ignore
const association = model.constructor.associations[key] as Association;
if (!association) {
return false;
}
if (!['undefined', 'string', 'number', 'object'].includes(typeof value)) {
return false;
}
const { transaction = await model.sequelize.transaction() } = options;
try {
// @ts-ignore
const setAccessor = association.accessors.set;
if (isUndefinedOrNull(value)) {
2021-09-27 15:28:32 +08:00
await model[setAccessor](null, { transaction });
model.setDataValue(key, null);
if (!options.transaction) {
await transaction.commit();
}
return true;
2021-09-23 00:16:04 +08:00
}
if (isStringOrNumber(value)) {
2021-09-27 15:28:32 +08:00
await model[setAccessor](value, { transaction });
if (!options.transaction) {
await transaction.commit();
}
return true;
}
if (value instanceof Model) {
await model[setAccessor](value);
model.setDataValue(key, value);
if (!options.transaction) {
await transaction.commit();
}
return true;
2021-09-23 00:16:04 +08:00
}
// @ts-ignore
const createAccessor = association.accessors.create;
2021-09-27 15:28:32 +08:00
let dataKey: string;
2021-09-23 00:16:04 +08:00
let M: ModelCtor<Model>;
if (association.associationType === 'BelongsTo') {
M = association.target;
// @ts-ignore
2021-09-27 15:28:32 +08:00
dataKey = association.targetKey;
} else {
2021-09-23 00:16:04 +08:00
M = association.source;
2021-09-27 15:28:32 +08:00
dataKey = M.primaryKeyAttribute;
2021-09-23 00:16:04 +08:00
}
2021-09-27 15:28:32 +08:00
if (isStringOrNumber(value[dataKey])) {
2021-09-23 00:16:04 +08:00
let instance: any = await M.findOne({
where: {
2021-09-27 15:28:32 +08:00
[dataKey]: value[dataKey],
2021-09-23 00:16:04 +08:00
},
transaction,
});
2021-09-27 15:28:32 +08:00
if (instance) {
await model[setAccessor](instance);
await updateAssociations(instance, value, { transaction, ...options });
model.setDataValue(key, instance);
if (!options.transaction) {
await transaction.commit();
}
return true;
2021-09-23 00:16:04 +08:00
}
2021-09-27 15:28:32 +08:00
}
const instance = await model[createAccessor](value, { transaction });
await updateAssociations(instance, value, { transaction, ...options });
model.setDataValue(key, instance);
// @ts-ignore
if (association.targetKey) {
model.setDataValue(association.foreignKey, instance[dataKey]);
2021-09-23 00:16:04 +08:00
}
if (!options.transaction) {
await transaction.commit();
}
} catch (error) {
if (!options.transaction) {
await transaction.rollback();
}
throw error;
}
}
export async function updateMultipleAssociation(
model: Model,
key: string,
value: any,
options: any = {},
) {
// @ts-ignore
const association = model.constructor.associations[key] as Association;
if (!association) {
return false;
}
if (!['undefined', 'string', 'number', 'object'].includes(typeof value)) {
return false;
}
const { transaction = await model.sequelize.transaction() } = options;
try {
// @ts-ignore
const setAccessor = association.accessors.set;
// @ts-ignore
const createAccessor = association.accessors.create;
if (isUndefinedOrNull(value)) {
2021-09-27 15:28:32 +08:00
await model[setAccessor](null, { transaction });
model.setDataValue(key, null);
return;
2021-09-23 00:16:04 +08:00
}
if (isStringOrNumber(value)) {
2021-09-27 15:28:32 +08:00
await model[setAccessor](value, { transaction });
return;
2021-09-23 00:16:04 +08:00
}
if (!Array.isArray(value)) {
value = [value];
}
const list1 = []; // to be setted
const list2 = []; // to be added
for (const item of value) {
if (isUndefinedOrNull(item)) {
continue;
}
if (isStringOrNumber(item)) {
list1.push(item);
} else if (item instanceof Model) {
list1.push(item);
} else if (item.sequelize) {
list1.push(item);
} else if (typeof item === 'object') {
list2.push(item);
}
}
await model[setAccessor](list1, { transaction });
2021-09-27 15:28:32 +08:00
const list3 = [];
2021-09-23 00:16:04 +08:00
for (const item of list2) {
const pk = association.target.primaryKeyAttribute;
if (isUndefinedOrNull(item[pk])) {
const instance = await model[createAccessor](item, { transaction });
await updateAssociations(instance, item, { transaction, ...options });
2021-09-27 15:28:32 +08:00
list3.push(instance);
2021-09-23 00:16:04 +08:00
} else {
const instance = await association.target.findByPk(item[pk], { transaction });
// @ts-ignore
const addAccessor = association.accessors.add;
await model[addAccessor](item[pk], { transaction });
await updateAssociations(instance, item, { transaction, ...options });
2021-09-27 15:28:32 +08:00
list3.push(instance);
2021-09-23 00:16:04 +08:00
}
}
2021-09-27 15:28:32 +08:00
model.setDataValue(key, list1.concat(list3));
2021-09-23 00:16:04 +08:00
if (!options.transaction) {
await transaction.commit();
}
} catch (error) {
await transaction.rollback();
throw error;
}
}