Skip to content

Commit d3779b7

Browse files
fix: fix record creation with unconventional pk field acting as a fk (#598)
1 parent ff0acca commit d3779b7

File tree

5 files changed

+135
-1
lines changed

5 files changed

+135
-1
lines changed

src/adapters/sequelize.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const P = require('bluebird');
33
const Interface = require('forest-express');
44
const ApimapFieldBuilder = require('../services/apimap-field-builder');
55
const ApimapFieldTypeDetector = require('../services/apimap-field-type-detector');
6+
const primaryKeyIsForeignKey = require('../utils/primaryKey-is-ForeignKey');
67

78
module.exports = (model, opts) => {
89
const fields = [];
@@ -82,8 +83,16 @@ module.exports = (model, opts) => {
8283
idField = 'forestCompositePrimary';
8384
}
8485

86+
Object.entries(model.associations).forEach(([, association]) => {
87+
const pkIsFk = primaryKeyIsForeignKey(association);
88+
if (pkIsFk) {
89+
const fk = fields.find((field) => field.reference === `${association.associationAccessor}.${association.foreignKey}`);
90+
fk.foreignAndPrimaryKey = true;
91+
}
92+
});
93+
8594
_.remove(fields, (field) =>
86-
_.includes(fieldNamesToExclude, field.columnName) && !field.primaryKey);
95+
_.includes(fieldNamesToExclude, field.columnName));
8796

8897
return {
8998
name: model.name,

src/services/resource-creator.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const { ErrorHTTP422 } = require('./errors');
55
const ResourceGetter = require('./resource-getter');
66
const CompositeKeysManager = require('./composite-keys-manager');
77
const associationRecord = require('../utils/association-record');
8+
const primaryKeyIsForeignKey = require('../utils/primaryKey-is-ForeignKey');
89

910
class ResourceCreator {
1011
constructor(model, params) {
@@ -28,6 +29,10 @@ class ResourceCreator {
2829
if (association.associationType === 'BelongsTo') {
2930
const setterName = `set${_.upperFirst(name)}`;
3031
const targetKey = await this._getTargetKey(name, association);
32+
const pkIsFk = primaryKeyIsForeignKey(association);
33+
if (pkIsFk) {
34+
record[association.source.primaryKeyAttribute] = this.params[name];
35+
}
3136
return record[setterName](targetKey, { save: false });
3237
}
3338
return null;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = (association) =>
2+
Object.values(association.source.rawAttributes).filter((attr) =>
3+
attr.field === association.source.primaryKeyField).length > 1;

test/adapters/sequelize.test.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,36 @@ function getField(schema, name) {
2525
},
2626
});
2727

28+
models.customer = sequelize.define('customer', {
29+
name: { type: Sequelize.STRING },
30+
});
31+
32+
models.picture = sequelize.define('picture', {
33+
name: { type: Sequelize.STRING },
34+
customerId: {
35+
type: Sequelize.INTEGER,
36+
primaryKey: true,
37+
allowNull: false,
38+
},
39+
}, {
40+
underscored: true,
41+
});
42+
43+
models.customer.hasOne(models.picture, {
44+
foreignKey: {
45+
name: 'customerIdKey',
46+
field: 'customer_id',
47+
},
48+
as: 'picture',
49+
});
50+
models.picture.belongsTo(models.customer, {
51+
foreignKey: {
52+
name: 'customerIdKey',
53+
field: 'customer_id',
54+
},
55+
as: 'customer',
56+
});
57+
2858
describe(`with dialect ${connectionManager.getDialect()} (port: ${connectionManager.getPort()})`, () => {
2959
describe('with model `users`', () => {
3060
it('should set name correctly', async () => {
@@ -120,5 +150,14 @@ function getField(schema, name) {
120150
});
121151
});
122152
});
153+
154+
describe('with association', () => {
155+
it('should set foreignAndPrimaryKey to true', async () => {
156+
expect.assertions(1);
157+
158+
const schema = await getSchema(models.picture, sequelizeOptions);
159+
expect(schema.fields.find((x) => x.field === 'customer').foreignAndPrimaryKey).toBeTrue();
160+
});
161+
});
123162
});
124163
});

test/databases.test.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,21 @@ const HasManyDissociator = require('../src/services/has-many-dissociator');
128128
clicks: { type: Sequelize.BIGINT },
129129
});
130130

131+
models.customer = sequelize.define('customer', {
132+
name: { type: Sequelize.STRING },
133+
});
134+
135+
models.picture = sequelize.define('picture', {
136+
name: { type: Sequelize.STRING },
137+
customerId: {
138+
type: Sequelize.INTEGER,
139+
primaryKey: true,
140+
allowNull: false,
141+
},
142+
}, {
143+
underscored: true,
144+
});
145+
131146
models.address.belongsTo(models.user);
132147
models.addressWithUserAlias.belongsTo(models.user, { as: 'userAlias' });
133148
models.user.hasMany(models.address);
@@ -152,6 +167,21 @@ const HasManyDissociator = require('../src/services/has-many-dissociator');
152167
sourceKey: 'ownerId',
153168
});
154169

170+
models.customer.hasOne(models.picture, {
171+
foreignKey: {
172+
name: 'customerIdKey',
173+
field: 'customer_id',
174+
},
175+
as: 'picture',
176+
});
177+
models.picture.belongsTo(models.customer, {
178+
foreignKey: {
179+
name: 'customerIdKey',
180+
field: 'customer_id',
181+
},
182+
as: 'customer',
183+
});
184+
155185
Interface.Schemas = {
156186
schemas: {
157187
user: {
@@ -330,6 +360,26 @@ const HasManyDissociator = require('../src/services/has-many-dissociator');
330360
{ field: 'clicks', type: 'Number' },
331361
],
332362
},
363+
customer: {
364+
name: 'owner',
365+
idField: 'id',
366+
primaryKeys: ['id'],
367+
isCompositePrimary: false,
368+
fields: [
369+
{ field: 'id', type: 'Number' },
370+
{ field: 'name', type: 'STRING' },
371+
],
372+
},
373+
picture: {
374+
name: 'picture',
375+
idField: 'customerId',
376+
primaryKeys: ['customerId'],
377+
isCompositePrimary: false,
378+
fields: [
379+
{ field: 'customerId', type: 'Number', reference: 'customer.id' },
380+
{ field: 'name', type: 'STRING' },
381+
],
382+
},
333383
},
334384
};
335385

@@ -642,6 +692,34 @@ const HasManyDissociator = require('../src/services/has-many-dissociator');
642692
});
643693
});
644694

695+
describe('create a record on a collection with a foreign key which is a primary key', () => {
696+
it('should create a record', async () => {
697+
expect.assertions(6);
698+
const { models } = initializeSequelize();
699+
try {
700+
await new ResourceCreator(models.customer, {
701+
id: 1,
702+
name: 'foo',
703+
}).perform();
704+
const result = await new ResourceCreator(models.picture, {
705+
name: 'bar',
706+
customer: 1,
707+
}).perform();
708+
709+
expect(result.customerId).toStrictEqual(1);
710+
expect(result.customerIdKey).toStrictEqual(1);
711+
expect(result.name).toStrictEqual('bar');
712+
713+
const picture = await models.picture.findOne({ where: { name: 'bar' }, include: { model: models.customer, as: 'customer' } });
714+
expect(picture).not.toBeNull();
715+
expect(picture.customerId).toStrictEqual(1);
716+
expect(picture.customer.id).toStrictEqual(1);
717+
} finally {
718+
connectionManager.closeConnection();
719+
}
720+
});
721+
});
722+
645723
describe('create a record on a collection with a composite primary key', () => {
646724
it('should create a record', async () => {
647725
expect.assertions(3);

0 commit comments

Comments
 (0)