Skip to content

Commit 164d898

Browse files
refactor: refactor belongsToUpdater to class (#595)
1 parent ad76837 commit 164d898

File tree

3 files changed

+208
-34
lines changed

3 files changed

+208
-34
lines changed

src/services/belongs-to-updater.js

Lines changed: 41 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,47 @@
11
const _ = require('lodash');
22
const orm = require('../utils/orm');
3+
const associationRecord = require('../utils/association-record');
34

4-
function BelongsToUpdater(model, assoc, opts, params, data) {
5-
this.perform = function perform() {
6-
return orm.findRecord(model, params.recordId)
7-
.then((record) => {
8-
// WORKAROUND: Make the hasOne associations update work while waiting
9-
// for the Sequelize 4 release with the fix of the following
10-
// issue: https://github.com/sequelize/sequelize/issues/6069
11-
// TODO: Once Sequelize 4 is mainstream, use the following code instead:
12-
// return record['set' + _.upperFirst(params.associationName)](
13-
// data.data ? data.data.id : null);
14-
let isHasOne = false;
15-
let modelAssociation;
16-
17-
_.each(model.associations, (association) => {
18-
if (association.associationAccessor === params.associationName) {
19-
isHasOne = association.associationType === 'HasOne';
20-
modelAssociation = association.target;
21-
}
22-
});
23-
24-
const setterName = `set${_.upperFirst(params.associationName)}`;
25-
26-
// NOTICE: Enable model hooks to change fields values during an association update.
27-
const options = { fields: null };
28-
29-
if (isHasOne && data.data) {
30-
return orm.findRecord(modelAssociation, data.data.id)
31-
.then((recordAssociated) => {
32-
record[setterName](recordAssociated, options);
33-
});
34-
}
35-
return record[setterName](data.data ? data.data.id : null, options);
36-
});
37-
};
5+
// WORKAROUND: Make the hasOne associations update work while waiting
6+
// for the Sequelize 4 release with the fix of the following
7+
// issue: https://github.com/sequelize/sequelize/issues/6069
8+
const getTargetKey = async (pk, association) => {
9+
let targetKey = pk;
10+
11+
if (association.associationType === 'HasOne') {
12+
targetKey = await associationRecord.get(association.target, pk);
13+
}
14+
15+
return targetKey;
16+
};
17+
18+
class BelongsToUpdater {
19+
constructor(model, assoc, opts, params, data) {
20+
this.model = model;
21+
this.assoc = assoc;
22+
this.opts = opts;
23+
this.params = params;
24+
this.data = data;
25+
}
26+
27+
async perform() {
28+
const { associationName, recordId } = this.params;
29+
const record = await orm.findRecord(this.model, recordId);
30+
const association = Object.values(this.model.associations)
31+
.find((a) => a.associationAccessor === associationName);
32+
33+
if (association && this.data.data) {
34+
const pk = this.data.data.id;
35+
const targetKey = await getTargetKey(pk, association);
36+
37+
// NOTICE: Enable model hooks to change fields values during an association update.
38+
const options = { fields: null };
39+
const setterName = `set${_.upperFirst(associationName)}`;
40+
return record[setterName](targetKey, options);
41+
}
42+
43+
return null;
44+
}
3845
}
3946

4047
module.exports = BelongsToUpdater;

src/utils/association-record.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import orm from './orm';
2+
3+
async function get(model, pk) {
4+
const record = await orm.findRecord(model, pk);
5+
if (!record) {
6+
throw new Error(`related ${model.name} with pk ${pk} does not exist.`);
7+
}
8+
return record;
9+
}
10+
11+
exports.get = get;

test/databases.test.js

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const LineStatGetter = require('../src/services/line-stat-getter');
99
const ResourcesGetter = require('../src/services/resources-getter');
1010
const ResourceGetter = require('../src/services/resource-getter');
1111
const ResourceCreator = require('../src/services/resource-creator');
12+
const BelongsToUpdater = require('../src/services/belongs-to-updater');
1213
const ResourceRemover = require('../src/services/resource-remover');
1314
const HasManyGetter = require('../src/services/has-many-getter');
1415
const HasManyDissociator = require('../src/services/has-many-dissociator');
@@ -45,6 +46,20 @@ const HasManyDissociator = require('../src/services/has-many-dissociator');
4546
age: { type: Sequelize.INTEGER },
4647
});
4748

49+
models.member = sequelize.define('member', {
50+
name: { type: Sequelize.STRING },
51+
});
52+
53+
models.membership = sequelize.define('membership', {
54+
type: { type: Sequelize.STRING },
55+
memberId: { type: Sequelize.INTEGER },
56+
});
57+
58+
models.friend = sequelize.define('friend', {
59+
name: { type: Sequelize.STRING },
60+
memberId: { type: Sequelize.INTEGER },
61+
});
62+
4863
models.bike = sequelize.define('bike', {
4964
id: {
5065
type: Sequelize.UUID,
@@ -104,6 +119,10 @@ const HasManyDissociator = require('../src/services/has-many-dissociator');
104119
models.user.hasMany(models.address);
105120
models.team.belongsToMany(models.user, { through: 'userTeam' });
106121
models.user.belongsToMany(models.team, { through: 'userTeam' });
122+
models.membership.belongsTo(models.member);
123+
models.member.hasOne(models.membership);
124+
models.member.hasMany(models.friend);
125+
models.friend.belongsTo(models.member);
107126

108127
Interface.Schemas = {
109128
schemas: {
@@ -219,6 +238,38 @@ const HasManyDissociator = require('../src/services/has-many-dissociator');
219238
{ field: 'team', type: 'Number', reference: 'team.id' },
220239
],
221240
},
241+
member: {
242+
name: 'member',
243+
idField: 'id',
244+
primaryKeys: ['id'],
245+
isCompositePrimary: false,
246+
fields: [
247+
{ field: 'id', type: 'Number' },
248+
{ field: 'name', type: 'String' },
249+
],
250+
},
251+
membership: {
252+
name: 'membership',
253+
idField: 'id',
254+
primaryKeys: ['id'],
255+
isCompositePrimary: false,
256+
fields: [
257+
{ field: 'id', type: 'Number' },
258+
{ field: 'type', type: 'String' },
259+
{ field: 'member', type: 'Number', reference: 'member.id' },
260+
],
261+
},
262+
friend: {
263+
name: 'friend',
264+
idField: 'id',
265+
primaryKeys: ['id'],
266+
isCompositePrimary: false,
267+
fields: [
268+
{ field: 'id', type: 'Number' },
269+
{ field: 'name', type: 'String' },
270+
{ field: 'member', type: 'Number', reference: 'member.id' },
271+
],
272+
},
222273
},
223274
};
224275

@@ -504,6 +555,111 @@ const HasManyDissociator = require('../src/services/has-many-dissociator');
504555
});
505556
});
506557

558+
describe('resources > resources updater', () => {
559+
describe('update a record on a collection', () => {
560+
it('should update a record', async () => {
561+
expect.assertions(2);
562+
const { models } = initializeSequelize();
563+
try {
564+
await new ResourceCreator(models.member, {
565+
id: 1,
566+
name: 'foo',
567+
}).perform();
568+
await new ResourceCreator(models.member, {
569+
id: 2,
570+
name: 'bar',
571+
}).perform();
572+
await new ResourceCreator(models.friend, {
573+
id: 1,
574+
name: 'foo',
575+
memberId: 1,
576+
}).perform();
577+
const result = await new BelongsToUpdater(models.friend, null, null, {
578+
recordId: '1',
579+
associationName: 'member',
580+
}, {
581+
data: {
582+
id: '2',
583+
type: 'member',
584+
},
585+
}).perform();
586+
587+
expect(result.id).toStrictEqual(1);
588+
expect(result.memberId).toStrictEqual('2');
589+
} finally {
590+
connectionManager.closeConnection();
591+
}
592+
});
593+
594+
it('should update a record with hasOne association', async () => {
595+
expect.assertions(2);
596+
const { models } = initializeSequelize();
597+
try {
598+
await new ResourceCreator(models.membership, {
599+
id: 1,
600+
type: 'basic',
601+
memberId: 1,
602+
}).perform();
603+
await new ResourceCreator(models.membership, {
604+
id: 2,
605+
type: 'premium',
606+
memberId: 2,
607+
}).perform();
608+
await new BelongsToUpdater(models.member, null, null, {
609+
recordId: '1',
610+
associationName: 'membership',
611+
}, {
612+
data: {
613+
id: '2',
614+
type: 'membership',
615+
},
616+
}).perform();
617+
618+
const member = await models.member.findOne({
619+
where: { id: 1 },
620+
include: { model: models.membership },
621+
});
622+
expect(member).not.toBeNull();
623+
expect(member.membership.id).toStrictEqual(2);
624+
} finally {
625+
connectionManager.closeConnection();
626+
}
627+
});
628+
629+
it('should not update a record', async () => {
630+
expect.assertions(1);
631+
const { models } = initializeSequelize();
632+
try {
633+
await expect(new BelongsToUpdater(models.member, null, null, {
634+
recordId: '1',
635+
associationName: 'membership',
636+
}, {
637+
data: {
638+
id: '999',
639+
type: 'membership',
640+
},
641+
}).perform()).rejects.toThrow(Error('related membership with pk 999 does not exist.'));
642+
} finally {
643+
connectionManager.closeConnection();
644+
}
645+
});
646+
647+
it('should not update a record if no data', async () => {
648+
expect.assertions(1);
649+
const { models } = initializeSequelize();
650+
try {
651+
const result = await new BelongsToUpdater(models.member, null, null, {
652+
recordId: '1',
653+
associationName: 'membership',
654+
}, {}).perform();
655+
expect(result).toBeNull();
656+
} finally {
657+
connectionManager.closeConnection();
658+
}
659+
});
660+
});
661+
});
662+
507663
describe('resources > resources getter', () => {
508664
describe('request on the resources getter without page size', () => {
509665
it('should generate a valid SQL query', async () => {

0 commit comments

Comments
 (0)