fix: allow set null with updateAssociations
This commit is contained in:
parent
752de2b58a
commit
eafdddcd4d
@ -4,7 +4,9 @@ import { Dialect } from 'sequelize';
|
|||||||
function getTestKey() {
|
function getTestKey() {
|
||||||
const { id } = require.main;
|
const { id } = require.main;
|
||||||
const key = id
|
const key = id
|
||||||
.replace(process.env.PWD, '')
|
// .replace(process.env.PWD, '')
|
||||||
|
.replace(`${process.env.PWD}/packages`, '')
|
||||||
|
.replace(/src\/__tests__/g, '')
|
||||||
.replace('.test.ts', '')
|
.replace('.test.ts', '')
|
||||||
.replace(/[^\w]/g, '_');
|
.replace(/[^\w]/g, '_');
|
||||||
return key
|
return key
|
||||||
@ -32,6 +34,9 @@ export function getDatabase() {
|
|||||||
...config,
|
...config,
|
||||||
sync: {
|
sync: {
|
||||||
force: true,
|
force: true,
|
||||||
|
alter: {
|
||||||
|
drop: true,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
hooks: {
|
hooks: {
|
||||||
beforeDefine(columns, model) {
|
beforeDefine(columns, model) {
|
||||||
|
107
packages/database/src/__tests__/model/updateAssociations.test.ts
Normal file
107
packages/database/src/__tests__/model/updateAssociations.test.ts
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import { getDatabase } from '..';
|
||||||
|
import Database from '../..';
|
||||||
|
|
||||||
|
let db: Database;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
db = getDatabase();
|
||||||
|
db.table({
|
||||||
|
name: 'users',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'hasMany',
|
||||||
|
name: 'posts',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'hasOne',
|
||||||
|
name: 'profile',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
||||||
|
db.table({
|
||||||
|
name: 'profiles',
|
||||||
|
});
|
||||||
|
db.table({
|
||||||
|
name: 'posts',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'hasMany',
|
||||||
|
name: 'comments',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'belongsTo',
|
||||||
|
name: 'user',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'belongsToMany',
|
||||||
|
name: 'tags',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
||||||
|
db.table({
|
||||||
|
name: 'tags',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'belongsToMany',
|
||||||
|
name: 'posts',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
db.table({
|
||||||
|
name: 'comments',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'belongsTo',
|
||||||
|
name: 'post',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
});
|
||||||
|
await db.sync();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await db.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('updateAssociations', () => {
|
||||||
|
describe('set null', () => {
|
||||||
|
it('belongsTo', async () => {
|
||||||
|
const [User, Post] = db.getModels(['users', 'posts']);
|
||||||
|
const user = await User.create();
|
||||||
|
const post = await Post.create();
|
||||||
|
await post.updateAssociations({
|
||||||
|
user,
|
||||||
|
});
|
||||||
|
await post.updateAssociations({});
|
||||||
|
const postUser = await post.getUser();
|
||||||
|
expect(postUser.id).toBe(user.id);
|
||||||
|
await post.updateAssociations({
|
||||||
|
user: null,
|
||||||
|
});
|
||||||
|
expect(await post.getUser()).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('hasMany', async () => {
|
||||||
|
const [User, Post] = db.getModels(['users', 'posts']);
|
||||||
|
const user = await User.create();
|
||||||
|
const post = await Post.create();
|
||||||
|
await user.updateAssociations({
|
||||||
|
posts: [post],
|
||||||
|
});
|
||||||
|
expect(await user.countPosts()).toBe(1);
|
||||||
|
await user.updateAssociations({
|
||||||
|
posts: [], // 空数组
|
||||||
|
});
|
||||||
|
expect(await user.countPosts()).toBe(0);
|
||||||
|
await user.updateAssociations({
|
||||||
|
posts: [post],
|
||||||
|
});
|
||||||
|
expect(await user.countPosts()).toBe(1);
|
||||||
|
await user.updateAssociations({
|
||||||
|
posts: null,
|
||||||
|
});
|
||||||
|
expect(await user.countPosts()).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -137,6 +137,10 @@ export const DEFAULT_OFFSET = 0;
|
|||||||
export const DEFAULT_LIMIT = 100;
|
export const DEFAULT_LIMIT = 100;
|
||||||
export const MAX_LIMIT = 500;
|
export const MAX_LIMIT = 500;
|
||||||
|
|
||||||
|
export interface UpdateAssociationOptions extends SaveOptions {
|
||||||
|
context?: any;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model 相关
|
* Model 相关
|
||||||
*
|
*
|
||||||
@ -281,7 +285,7 @@ export abstract class Model extends SequelizeModel {
|
|||||||
return where;
|
return where;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateSingleAssociation(key: string, data: any, options: SaveOptions<any> & { context?: any; } = {}) {
|
async updateSingleAssociation(key: string, data: any, options: UpdateAssociationOptions = {}) {
|
||||||
const {
|
const {
|
||||||
fields,
|
fields,
|
||||||
transaction = await this.sequelize.transaction(),
|
transaction = await this.sequelize.transaction(),
|
||||||
@ -293,6 +297,11 @@ export abstract class Model extends SequelizeModel {
|
|||||||
const association = table.getAssociations().get(key);
|
const association = table.getAssociations().get(key);
|
||||||
const accessors = association.getAccessors();
|
const accessors = association.getAccessors();
|
||||||
|
|
||||||
|
if (data == null) {
|
||||||
|
await this[accessors.set](null, opts);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof data === 'number' || typeof data === 'string' || data instanceof SequelizeModel) {
|
if (typeof data === 'number' || typeof data === 'string' || data instanceof SequelizeModel) {
|
||||||
await this[accessors.set](data, opts);
|
await this[accessors.set](data, opts);
|
||||||
} else if (typeof data === 'object') {
|
} else if (typeof data === 'object') {
|
||||||
@ -323,11 +332,8 @@ export abstract class Model extends SequelizeModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateMultipleAssociation(associationName: string, data: any, options: SaveOptions<any> & { context?: any; } = {}) {
|
async updateMultipleAssociation(associationName: string, data: any, options: UpdateAssociationOptions = {}) {
|
||||||
const items = Array.isArray(data) ? data : [data];
|
const items = Array.isArray(data) ? data : data == null ? [] : [data];
|
||||||
if (!items.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
fields,
|
fields,
|
||||||
@ -339,6 +345,12 @@ export abstract class Model extends SequelizeModel {
|
|||||||
const table = this.database.getTable(this.constructor.name);
|
const table = this.database.getTable(this.constructor.name);
|
||||||
const association = table.getAssociations().get(associationName);
|
const association = table.getAssociations().get(associationName);
|
||||||
const accessors = association.getAccessors();
|
const accessors = association.getAccessors();
|
||||||
|
|
||||||
|
if (!items.length) {
|
||||||
|
await this[accessors.set](null, opts);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const Target = association.getTargetModel();
|
const Target = association.getTargetModel();
|
||||||
// 当前表关联 target 表的外键(大部分情况与 target 表主键相同,但可以设置为不同的,要考虑)
|
// 当前表关联 target 表的外键(大部分情况与 target 表主键相同,但可以设置为不同的,要考虑)
|
||||||
const { targetKey = Target.primaryKeyAttribute } = association.options;
|
const { targetKey = Target.primaryKeyAttribute } = association.options;
|
||||||
@ -481,7 +493,7 @@ export abstract class Model extends SequelizeModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateAssociation(key: string, data: any, options: SaveOptions<any> & { context?: any; }) {
|
async updateAssociation(key: string, data: any, options: UpdateAssociationOptions = {}) {
|
||||||
const table = this.database.getTable(this.constructor.name);
|
const table = this.database.getTable(this.constructor.name);
|
||||||
const association = table.getAssociations().get(key);
|
const association = table.getAssociations().get(key);
|
||||||
switch (true) {
|
switch (true) {
|
||||||
@ -497,15 +509,14 @@ export abstract class Model extends SequelizeModel {
|
|||||||
/**
|
/**
|
||||||
* 关联数据的更新
|
* 关联数据的更新
|
||||||
*
|
*
|
||||||
* TODO: 暂不支持除主键以外关联字段的更新
|
|
||||||
*
|
|
||||||
* @param data
|
* @param data
|
||||||
*/
|
*/
|
||||||
async updateAssociations(data: any, options: SaveOptions & { context?: any } = {}) {
|
async updateAssociations(data: any, options: UpdateAssociationOptions = {}) {
|
||||||
const { transaction = await this.sequelize.transaction() } = options;
|
const { transaction = await this.sequelize.transaction() } = options;
|
||||||
const table = this.database.getTable(this.constructor.name);
|
const table = this.database.getTable(this.constructor.name);
|
||||||
for (const key of table.getAssociations().keys()) {
|
for (const key of table.getAssociations().keys()) {
|
||||||
if (!data[key]) {
|
// 如果 key 不存在才跳过
|
||||||
|
if (!Object.keys(data).includes(key)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
await this.updateAssociation(key, data[key], {
|
await this.updateAssociation(key, data[key], {
|
||||||
|
Loading…
Reference in New Issue
Block a user