Skip to content

Commit c915f86

Browse files
authored
fix: unflatten relationships in their original attribute (#790)
1 parent 9064878 commit c915f86

File tree

4 files changed

+170
-66
lines changed

4 files changed

+170
-66
lines changed

src/services/flattener.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,24 @@ module.exports = class Flattener {
7979
return { parentObjectName, unflattenedObject };
8080
}
8181

82+
static _unwrapFlattenedReferences(request) {
83+
if (!request.body.data.attributes) request.body.data.attributes = {};
84+
85+
const { attributes, relationships } = request.body.data;
86+
87+
Object.entries(relationships)
88+
.filter(([attributeName]) => Flattener._isFieldFlattened(attributeName))
89+
.forEach(([attributeName, value]) => {
90+
const { parentObjectName, unflattenedObject } = Flattener._unflattenAttribute(
91+
attributeName,
92+
value.data?.id,
93+
attributes,
94+
);
95+
attributes[parentObjectName] = _.merge(attributes[parentObjectName], unflattenedObject);
96+
delete relationships[attributeName];
97+
});
98+
}
99+
82100
static _unflattenAttributes(request) {
83101
Object.entries(request.body.data.attributes).forEach(([attributeName, value]) => {
84102
if (Flattener._isFieldFlattened(attributeName)) {
@@ -115,6 +133,9 @@ module.exports = class Flattener {
115133
Flattener._unflattenSubsetQuery(request);
116134
}
117135
}
136+
if (!_.isEmpty(request.body?.data?.relationships)) {
137+
Flattener._unwrapFlattenedReferences(request);
138+
}
118139
if (!_.isEmpty(request.query?.fields)) {
119140
Flattener._unflattenFields(request);
120141
}

src/services/resource-creator.js

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { Schemas } from 'forest-express';
22
import utils from '../utils/schema';
33
import ResourceGetter from './resource-getter';
4-
import Flattener from './flattener';
54

65
class ResourceCreator {
76
constructor(model, params, body, user) {
@@ -10,13 +9,6 @@ class ResourceCreator {
109
this._body = body;
1110
this._user = user;
1211
this._schema = Schemas.schemas[utils.getModelName(model)];
13-
14-
/*
15-
We unflatten the attributes here, because the deserializer validates the fields'
16-
name against the schema so we can not do it in the middleware, we have to unflatten
17-
at the very end of the record creation flow.
18-
*/
19-
Flattener.unflattenFieldNamesInObject(this._body);
2012
}
2113

2214
async _create() {

test/tests/services/flattener.test.js

Lines changed: 149 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -572,41 +572,169 @@ describe('service > Flattener', () => {
572572
});
573573

574574
describe('for a POST request', () => {
575-
const mockResponse = {};
576-
const mockNext = jest.fn();
577-
const request = {
578-
originalUrl: 'http://localhost:3311/forest/cars',
579-
body: {
580-
data: {
581-
attributes: {
582-
[`engine${FLATTEN_SEPARATOR}horsePower`]: '125cv',
583-
[`engine${FLATTEN_SEPARATOR}identification${FLATTEN_SEPARATOR}serialNumber`]: '1234567',
584-
name: 'Car',
575+
const setupTest = () => ({
576+
mockResponse: {},
577+
mockNext: jest.fn(),
578+
request: {
579+
originalUrl: 'http://localhost:3311/forest/cars',
580+
body: {
581+
data: {
582+
attributes: {
583+
[`engine${FLATTEN_SEPARATOR}horsePower`]: '125cv',
584+
[`engine${FLATTEN_SEPARATOR}identification${FLATTEN_SEPARATOR}serialNumber`]: '1234567',
585+
name: 'Car',
586+
},
587+
relationships: {
588+
[`engine${FLATTEN_SEPARATOR}identification${FLATTEN_SEPARATOR}company`]: {
589+
data: {
590+
type: 'companies',
591+
id: '5fd78361f8e514b2abe7044b',
592+
},
593+
},
594+
company: {
595+
data: {
596+
type: 'companies',
597+
id: '5fd78361f8e514b2abe7044b',
598+
},
599+
},
600+
},
585601
},
602+
type: 'cars',
586603
},
587604
},
588-
};
605+
});
589606

590607
it('should unflatten the attributes in the body', () => {
591608
expect.assertions(2);
592609

610+
const { request, mockResponse, mockNext } = setupTest();
611+
593612
Flattener.requestUnflattener(request, mockResponse, mockNext);
594613

595-
expect(request.body).toStrictEqual({
596-
data: {
597-
attributes: {
598-
engine: {
599-
horsePower: '125cv',
600-
identification: {
601-
serialNumber: '1234567',
602-
},
603-
},
604-
name: 'Car',
614+
const { attributes } = request.body.data;
615+
616+
expect(attributes).toStrictEqual({
617+
engine: {
618+
horsePower: '125cv',
619+
identification: {
620+
company: '5fd78361f8e514b2abe7044b',
621+
serialNumber: '1234567',
605622
},
606623
},
624+
name: 'Car',
607625
});
608626
expect(mockNext).toHaveBeenCalledTimes(1);
609627
});
628+
629+
describe('handling relationships', () => {
630+
it('should not change the request if no relationship exits', () => {
631+
expect.assertions(2);
632+
633+
const relationshipUnWrapperSpy = jest.spyOn(Flattener, '_unwrapFlattenedReferences');
634+
const {
635+
request,
636+
mockResponse,
637+
mockNext,
638+
} = setupTest();
639+
640+
request.body.data.relationships = {};
641+
const originalRequest = Object.assign(request);
642+
643+
Flattener.requestUnflattener(request, mockResponse, mockNext);
644+
645+
expect(originalRequest).toStrictEqual(request);
646+
expect(relationshipUnWrapperSpy).not.toHaveBeenCalled();
647+
648+
relationshipUnWrapperSpy.mockClear();
649+
});
650+
651+
it('should not change the request if relationships are not flattened ones', () => {
652+
expect.assertions(2);
653+
654+
const relationshipUnWrapperSpy = jest.spyOn(Flattener, '_unwrapFlattenedReferences');
655+
const {
656+
request,
657+
mockResponse,
658+
mockNext,
659+
} = setupTest();
660+
661+
delete request.body.data.relationships[`engine${FLATTEN_SEPARATOR}identification${FLATTEN_SEPARATOR}company`];
662+
const originalRequest = Object.assign(request);
663+
664+
Flattener.requestUnflattener(request, mockResponse, mockNext);
665+
666+
expect(originalRequest).toStrictEqual(request);
667+
expect(relationshipUnWrapperSpy).toHaveBeenCalledTimes(1);
668+
669+
relationshipUnWrapperSpy.mockClear();
670+
});
671+
672+
it('should move flattened relationships in the original attribute', () => {
673+
expect.assertions(4);
674+
675+
const {
676+
request,
677+
mockResponse,
678+
mockNext,
679+
} = setupTest();
680+
681+
Flattener.requestUnflattener(request, mockResponse, mockNext);
682+
683+
const { attributes, relationships } = request.body.data;
684+
685+
expect(attributes.engine.identification.company).toStrictEqual('5fd78361f8e514b2abe7044b');
686+
expect(relationships[`engine${FLATTEN_SEPARATOR}identification${FLATTEN_SEPARATOR}company`]).toBeUndefined();
687+
expect(relationships).toStrictEqual({
688+
company: {
689+
data: {
690+
type: 'companies',
691+
id: '5fd78361f8e514b2abe7044b',
692+
},
693+
},
694+
});
695+
expect(mockNext).toHaveBeenCalledTimes(1);
696+
});
697+
698+
it('should correctly unwrap relationship even if original object is not present', () => {
699+
expect.assertions(2);
700+
701+
const {
702+
request,
703+
mockResponse,
704+
mockNext,
705+
} = setupTest();
706+
707+
request.body.data.attributes = {
708+
name: 'Car',
709+
};
710+
711+
Flattener.requestUnflattener(request, mockResponse, mockNext);
712+
713+
const { attributes, relationships } = request.body.data;
714+
715+
expect(attributes.engine.identification.company).toStrictEqual('5fd78361f8e514b2abe7044b');
716+
expect(relationships[`engine${FLATTEN_SEPARATOR}identification${FLATTEN_SEPARATOR}company`]).toBeUndefined();
717+
});
718+
719+
it('should correctly unwrap relationship even if no attributes has been set', () => {
720+
expect.assertions(2);
721+
722+
const {
723+
request,
724+
mockResponse,
725+
mockNext,
726+
} = setupTest();
727+
728+
request.body.data.attributes = undefined;
729+
730+
Flattener.requestUnflattener(request, mockResponse, mockNext);
731+
732+
const { attributes, relationships } = request.body.data;
733+
734+
expect(attributes.engine.identification.company).toStrictEqual('5fd78361f8e514b2abe7044b');
735+
expect(relationships[`engine${FLATTEN_SEPARATOR}identification${FLATTEN_SEPARATOR}company`]).toBeUndefined();
736+
});
737+
});
610738
});
611739

612740
describe('for a bulk delete', () => {

test/tests/services/resource-creator.test.js

Lines changed: 0 additions & 37 deletions
This file was deleted.

0 commit comments

Comments
 (0)