Skip to content
11 changes: 10 additions & 1 deletion src/adapters/sequelize.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const P = require('bluebird');
const Interface = require('forest-express');
const ApimapFieldBuilder = require('../services/apimap-field-builder');
const ApimapFieldTypeDetector = require('../services/apimap-field-type-detector');
const primaryKeyIsForeignKey = require('../utils/primaryKey-is-ForeignKey');

module.exports = (model, opts) => {
const fields = [];
Expand Down Expand Up @@ -82,8 +83,16 @@ module.exports = (model, opts) => {
idField = 'forestCompositePrimary';
}

Object.entries(model.associations).forEach(([, association]) => {
const pkIsFk = primaryKeyIsForeignKey(association);
if (pkIsFk) {
const fk = fields.find((field) => field.reference === `${association.associationAccessor}.${association.foreignKey}`);
fk.foreignAndPrimaryKey = true;
}
});

_.remove(fields, (field) =>
_.includes(fieldNamesToExclude, field.columnName) && !field.primaryKey);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

primaryKey does not exist, but isPrimaryKey does.
Neithertheless, we still need to remove it for a better user experience.

_.includes(fieldNamesToExclude, field.columnName));

return {
name: model.name,
Expand Down
5 changes: 5 additions & 0 deletions src/services/resource-creator.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const { ErrorHTTP422 } = require('./errors');
const ResourceGetter = require('./resource-getter');
const CompositeKeysManager = require('./composite-keys-manager');
const associationRecord = require('../utils/association-record');
const primaryKeyIsForeignKey = require('../utils/primaryKey-is-ForeignKey');

class ResourceCreator {
constructor(model, params) {
Expand All @@ -28,6 +29,10 @@ class ResourceCreator {
if (association.associationType === 'BelongsTo') {
const setterName = `set${_.upperFirst(name)}`;
const targetKey = await this._getTargetKey(name, association);
const pkIsFk = primaryKeyIsForeignKey(association);
if (pkIsFk) {
record[association.source.primaryKeyAttribute] = this.params[name];
}
return record[setterName](targetKey, { save: false });
}
return null;
Expand Down
3 changes: 3 additions & 0 deletions src/utils/primaryKey-is-ForeignKey.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = (association) =>
Object.values(association.source.rawAttributes).filter((attr) =>
attr.field === association.source.primaryKeyField).length > 1;
39 changes: 39 additions & 0 deletions test/adapters/sequelize.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,36 @@ function getField(schema, name) {
},
});

models.customer = sequelize.define('customer', {
name: { type: Sequelize.STRING },
});

models.picture = sequelize.define('picture', {
name: { type: Sequelize.STRING },
customerId: {
type: Sequelize.INTEGER,
primaryKey: true,
allowNull: false,
},
}, {
underscored: true,
});

models.customer.hasOne(models.picture, {
foreignKey: {
name: 'customerIdKey',
field: 'customer_id',
},
as: 'picture',
});
models.picture.belongsTo(models.customer, {
foreignKey: {
name: 'customerIdKey',
field: 'customer_id',
},
as: 'customer',
});

describe(`with dialect ${connectionManager.getDialect()} (port: ${connectionManager.getPort()})`, () => {
describe('with model `users`', () => {
it('should set name correctly', async () => {
Expand Down Expand Up @@ -120,5 +150,14 @@ function getField(schema, name) {
});
});
});

describe('with association', () => {
it('should set foreignAndPrimaryKey to true', async () => {
expect.assertions(1);

const schema = await getSchema(models.picture, sequelizeOptions);
expect(schema.fields.find((x) => x.field === 'customer').foreignAndPrimaryKey).toBeTrue();
});
});
});
});
78 changes: 78 additions & 0 deletions test/databases.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,21 @@ const HasManyDissociator = require('../src/services/has-many-dissociator');
clicks: { type: Sequelize.BIGINT },
});

models.customer = sequelize.define('customer', {
name: { type: Sequelize.STRING },
});

models.picture = sequelize.define('picture', {
name: { type: Sequelize.STRING },
customerId: {
type: Sequelize.INTEGER,
primaryKey: true,
allowNull: false,
},
}, {
underscored: true,
});

models.address.belongsTo(models.user);
models.addressWithUserAlias.belongsTo(models.user, { as: 'userAlias' });
models.user.hasMany(models.address);
Expand All @@ -152,6 +167,21 @@ const HasManyDissociator = require('../src/services/has-many-dissociator');
sourceKey: 'ownerId',
});

models.customer.hasOne(models.picture, {
foreignKey: {
name: 'customerIdKey',
field: 'customer_id',
},
as: 'picture',
});
models.picture.belongsTo(models.customer, {
foreignKey: {
name: 'customerIdKey',
field: 'customer_id',
},
as: 'customer',
});

Interface.Schemas = {
schemas: {
user: {
Expand Down Expand Up @@ -330,6 +360,26 @@ const HasManyDissociator = require('../src/services/has-many-dissociator');
{ field: 'clicks', type: 'Number' },
],
},
customer: {
name: 'owner',
idField: 'id',
primaryKeys: ['id'],
isCompositePrimary: false,
fields: [
{ field: 'id', type: 'Number' },
{ field: 'name', type: 'STRING' },
],
},
picture: {
name: 'picture',
idField: 'customerId',
primaryKeys: ['customerId'],
isCompositePrimary: false,
fields: [
{ field: 'customerId', type: 'Number', reference: 'customer.id' },
{ field: 'name', type: 'STRING' },
],
},
},
};

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

describe('create a record on a collection with a foreign key which is a primary key', () => {
it('should create a record', async () => {
expect.assertions(6);
const { models } = initializeSequelize();
try {
await new ResourceCreator(models.customer, {
id: 1,
name: 'foo',
}).perform();
const result = await new ResourceCreator(models.picture, {
name: 'bar',
customer: 1,
}).perform();

expect(result.customerId).toStrictEqual(1);
expect(result.customerIdKey).toStrictEqual(1);
expect(result.name).toStrictEqual('bar');

const picture = await models.picture.findOne({ where: { name: 'bar' }, include: { model: models.customer, as: 'customer' } });
expect(picture).not.toBeNull();
expect(picture.customerId).toStrictEqual(1);
expect(picture.customer.id).toStrictEqual(1);
} finally {
connectionManager.closeConnection();
}
});
});

describe('create a record on a collection with a composite primary key', () => {
it('should create a record', async () => {
expect.assertions(3);
Expand Down