Skip to content

Commit 27bcac5

Browse files
fix: fix association with primary key different of 'id' (#878)
1 parent 4dc5adc commit 27bcac5

File tree

4 files changed

+251
-3
lines changed

4 files changed

+251
-3
lines changed

src/services/belongs-to-updater.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@ class BelongsToUpdater {
1616
// issue: https://github.com/sequelize/sequelize/issues/6069
1717
async _getTargetKey(association) {
1818
const pk = this.data.data.id;
19+
const targetKeyIsPrimaryKey = association.targetKey === association.target.primaryKeyAttribute;
1920
let targetKey = pk;
2021

21-
if (association.associationType === 'HasOne' || association.targetKey !== 'id') {
22+
if (association.associationType === 'HasOne' || !targetKeyIsPrimaryKey) {
2223
const record = await associationRecord.get(association.target, pk);
2324
if (association.associationType === 'HasOne') {
2425
targetKey = record;
25-
} else if (association.targetKey !== 'id') {
26+
} else if (!targetKeyIsPrimaryKey) {
2627
// NOTICE: special use case with foreign key non pointing to a primary key
2728
targetKey = record[association.targetKey];
2829
}

src/services/resource-creator.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class ResourceCreator {
2020
const primaryKey = this.body[name];
2121

2222
let targetKey = primaryKey;
23-
if (typeof primaryKey !== 'undefined' && association.targetKey !== 'id') {
23+
if (primaryKey && association.targetKey !== association.target.primaryKeyAttribute) {
2424
const record = await associationRecord.get(association.target, primaryKey);
2525
targetKey = record[association.targetKey];
2626
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import Sequelize from 'sequelize';
2+
import associationRecord from '../../src/utils/association-record';
3+
import BelongsToUpdater from '../../src/services/belongs-to-updater';
4+
5+
describe('services > belongs-to-updater', () => {
6+
const params = { timezone: 'Europe/Paris' };
7+
8+
const buildModelMock = () => {
9+
// Sequelize is created here without connection to a database
10+
const sequelize = new Sequelize({ dialect: 'postgres' });
11+
12+
const Actor = sequelize.define('actor', {
13+
Id: {
14+
type: Sequelize.DataTypes.INTEGER,
15+
primaryKey: true,
16+
},
17+
});
18+
const Author = sequelize.define('author', {
19+
id: {
20+
type: Sequelize.DataTypes.INTEGER,
21+
primaryKey: true,
22+
},
23+
name: {
24+
type: Sequelize.DataTypes.STRING,
25+
unique: true,
26+
},
27+
});
28+
const Film = sequelize.define('film', {});
29+
30+
Film.belongsTo(Actor);
31+
Film.belongsTo(Author, {
32+
targetKey: 'name',
33+
});
34+
35+
return { Actor, Author, Film };
36+
};
37+
38+
describe('_getTargetKey', () => {
39+
describe('when association does not have entry in data', () => {
40+
it('should return null', async () => {
41+
expect.assertions(2);
42+
43+
const { Film, Actor } = buildModelMock();
44+
45+
const data = {};
46+
47+
const spy = jest.spyOn(associationRecord, 'get');
48+
49+
const belongsToUpdater = new BelongsToUpdater(Film, null, null, params, { data });
50+
const targetKey = await belongsToUpdater._getTargetKey(
51+
Film.associations[Actor.name],
52+
);
53+
54+
expect(spy).not.toHaveBeenCalled();
55+
expect(targetKey).toBeNil();
56+
});
57+
});
58+
59+
describe('when association does not have value in body', () => {
60+
it('should return null', async () => {
61+
expect.assertions(2);
62+
63+
const { Film, Actor } = buildModelMock();
64+
65+
const data = { id: null };
66+
67+
const spy = jest.spyOn(associationRecord, 'get');
68+
69+
const belongsToUpdater = new BelongsToUpdater(Film, null, null, params, { data });
70+
const targetKey = await belongsToUpdater._getTargetKey(
71+
Film.associations[Actor.name],
72+
);
73+
74+
expect(spy).not.toHaveBeenCalled();
75+
expect(targetKey).toBeNil();
76+
});
77+
});
78+
79+
describe('when association target key is the primary key', () => {
80+
it('should return the body value', async () => {
81+
expect.assertions(2);
82+
83+
const { Film, Actor } = buildModelMock();
84+
85+
const data = { id: 2 };
86+
87+
const spy = jest.spyOn(associationRecord, 'get');
88+
89+
const belongsToUpdater = new BelongsToUpdater(Film, null, null, params, { data });
90+
const targetKey = await belongsToUpdater._getTargetKey(
91+
Film.associations[Actor.name],
92+
);
93+
94+
expect(spy).not.toHaveBeenCalled();
95+
expect(targetKey).toStrictEqual(2);
96+
});
97+
});
98+
99+
describe('when association target key is not the primary key', () => {
100+
it('should return the right value', async () => {
101+
expect.assertions(2);
102+
103+
const { Film, Author } = buildModelMock();
104+
105+
const data = { id: 2 };
106+
107+
const spy = jest.spyOn(associationRecord, 'get').mockResolvedValue({ id: 2, name: 'Scorsese' });
108+
109+
const belongsToUpdater = new BelongsToUpdater(Film, null, null, params, { data });
110+
const targetKey = await belongsToUpdater._getTargetKey(
111+
Film.associations[Author.name],
112+
);
113+
114+
expect(spy).toHaveBeenCalledWith(Author, 2);
115+
expect(targetKey).toStrictEqual('Scorsese');
116+
});
117+
});
118+
});
119+
});
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import Interface from 'forest-express';
2+
import Sequelize from 'sequelize';
3+
import associationRecord from '../../src/utils/association-record';
4+
import ResourceCreator from '../../src/services/resource-creator';
5+
6+
describe('services > resource-creator', () => {
7+
const user = { renderingId: 1 };
8+
const params = { timezone: 'Europe/Paris' };
9+
10+
const buildModelMock = () => {
11+
// Sequelize is created here without connection to a database
12+
const sequelize = new Sequelize({ dialect: 'postgres' });
13+
14+
const Actor = sequelize.define('actor', {
15+
Id: {
16+
type: Sequelize.DataTypes.INTEGER,
17+
primaryKey: true,
18+
},
19+
});
20+
const Author = sequelize.define('author', {
21+
id: {
22+
type: Sequelize.DataTypes.INTEGER,
23+
primaryKey: true,
24+
},
25+
name: {
26+
type: Sequelize.DataTypes.STRING,
27+
unique: true,
28+
},
29+
});
30+
const Film = sequelize.define('film', {});
31+
32+
Film.belongsTo(Actor);
33+
Film.belongsTo(Author, {
34+
targetKey: 'name',
35+
});
36+
37+
Interface.Schemas.schemas[Actor.name] = {};
38+
Interface.Schemas.schemas[Film.name] = {};
39+
40+
return { Actor, Author, Film };
41+
};
42+
43+
describe('_getTargetKey', () => {
44+
describe('when association does not have entry in body', () => {
45+
it('should return null', async () => {
46+
expect.assertions(2);
47+
48+
const { Film, Actor } = buildModelMock();
49+
50+
const body = {};
51+
52+
const spy = jest.spyOn(associationRecord, 'get');
53+
54+
const resourceCreator = new ResourceCreator(Film, params, body, user);
55+
const targetKey = await resourceCreator._getTargetKey(
56+
Actor.name,
57+
Film.associations[Actor.name],
58+
);
59+
60+
expect(spy).not.toHaveBeenCalled();
61+
expect(targetKey).toBeNil();
62+
});
63+
});
64+
65+
describe('when association does not have value in body', () => {
66+
it('should return null', async () => {
67+
expect.assertions(2);
68+
69+
const { Film, Actor } = buildModelMock();
70+
71+
const body = { [Actor.name]: null };
72+
73+
const spy = jest.spyOn(associationRecord, 'get');
74+
75+
const resourceCreator = new ResourceCreator(Film, params, body, user);
76+
const targetKey = await resourceCreator._getTargetKey(
77+
Actor.name,
78+
Film.associations[Actor.name],
79+
);
80+
81+
expect(spy).not.toHaveBeenCalled();
82+
expect(targetKey).toBeNil();
83+
});
84+
});
85+
86+
describe('when association target key is the primary key', () => {
87+
it('should return the body value', async () => {
88+
expect.assertions(2);
89+
90+
const { Film, Actor } = buildModelMock();
91+
92+
const body = { [Actor.name]: 2 };
93+
94+
const spy = jest.spyOn(associationRecord, 'get');
95+
96+
const resourceCreator = new ResourceCreator(Film, params, body, user);
97+
const targetKey = await resourceCreator._getTargetKey(
98+
Actor.name,
99+
Film.associations[Actor.name],
100+
);
101+
102+
expect(spy).not.toHaveBeenCalled();
103+
expect(targetKey).toStrictEqual(2);
104+
});
105+
});
106+
107+
describe('when association target key is not the primary key', () => {
108+
it('should return the right value', async () => {
109+
expect.assertions(2);
110+
111+
const { Film, Author } = buildModelMock();
112+
113+
const body = { [Author.name]: 2 };
114+
115+
const spy = jest.spyOn(associationRecord, 'get').mockResolvedValue({ id: 2, name: 'Scorsese' });
116+
117+
const resourceCreator = new ResourceCreator(Film, params, body, user);
118+
const targetKey = await resourceCreator._getTargetKey(
119+
Author.name,
120+
Film.associations[Author.name],
121+
);
122+
123+
expect(spy).toHaveBeenCalledWith(Author, 2);
124+
expect(targetKey).toStrictEqual('Scorsese');
125+
});
126+
});
127+
});
128+
});

0 commit comments

Comments
 (0)