Skip to content

Commit

Permalink
Fix improper saving of subdocument arrays with multi-part paths (#85)
Browse files Browse the repository at this point in the history
Properly handle subdocument arrays with multi-part paths
  • Loading branch information
shawnmcknight authored Apr 25, 2022
1 parent 34d002d commit 30297a8
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 15 deletions.
52 changes: 52 additions & 0 deletions src/__tests__/Document.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,30 @@ describe('createDocumentFromRecordString', () => {

expect(document.prop1).toEqual([['foo', 'bar', 'baz']]);
});

test('should create a new document from the provided record string using document arrays at subvalue positions', () => {
const definition: SchemaDefinition = {
subdocumentArray: [
{
prop1: { type: 'string', path: '1.1' },
prop2: { type: 'number', path: '1.2', dbDecimals: 2 },
},
],
};
const schema = new Schema(definition);

const document = Document.createDocumentFromRecordString(
schema,
`foo${svm}bar${vm}123${svm}456`,
mockDelimiters,
);

const expected = [
{ prop1: 'foo', prop2: 1.23 },
{ prop1: 'bar', prop2: 4.56 },
];
expect(document.subdocumentArray).toEqual(expected);
});
});

describe('transformDocumentToRecord', () => {
Expand Down Expand Up @@ -582,6 +606,34 @@ describe('transformDocumentToRecord', () => {
const expected: MvRecord = [undefined, 'foo', undefined, '123'];
expect(document.transformDocumentToRecord()).toEqual(expected);
});

test('should transform document arrays at subvalue position', () => {
const definition: SchemaDefinition = {
subdocumentArray: [
{
prop1: { type: 'string', path: '1.1' },
prop2: { type: 'number', path: '1.2', dbDecimals: 2 },
},
],
};
const schema = new Schema(definition);

const data = {
subdocumentArray: [
{ prop1: 'foo', prop2: 1.23 },
{ prop1: 'bar', prop2: 4.56 },
],
};
const document = new DocumentSubclass(schema, { data });

const expected: MvRecord = [
[
['foo', 'bar'],
['123', '456'],
],
];
expect(document.transformDocumentToRecord()).toEqual(expected);
});
});

describe('buildForeignKeyDefinitions', () => {
Expand Down
17 changes: 9 additions & 8 deletions src/schemaType/DocumentArrayType.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { cloneDeep, isPlainObject, set as setIn } from 'lodash';
import { cloneDeep, get as getIn, isPlainObject, set as setIn } from 'lodash';
import Document from '../Document';
import type { ForeignKeyDbDefinition } from '../ForeignKeyDbTransformer';
import type Schema from '../Schema';
Expand Down Expand Up @@ -41,18 +41,19 @@ class DocumentArrayType extends BaseSchemaType {
}

/** Set specified document array value into mv record */
public set(originalRecord: MvRecord, setValue: Document[]): MvRecord {
public set(originalRecord: MvRecord, documents: Document[]): MvRecord {
const record = cloneDeep(originalRecord);
const mvPaths = this.valueSchema.getMvPaths();
// A subdocumentArray is always overwritten entirely so clear out all associated fields
this.valueSchema.getMvPaths().forEach((path) => {
mvPaths.forEach((path) => {
setIn(record, path, null);
});
setValue.forEach((subdocument, iteration) => {
documents.forEach((subdocument, iteration) => {
const subrecord = subdocument.transformDocumentToRecord();
subrecord.forEach((value, arrayPos) => {
if (typeof value !== 'undefined') {
setIn(record, [arrayPos, iteration], value);
}

mvPaths.forEach((path) => {
const value = getIn(subrecord, path, null);
setIn(record, path.concat(iteration), value);
});
});
return record;
Expand Down
138 changes: 131 additions & 7 deletions src/schemaType/__tests__/DocumentArrayType.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,14 @@ describe('get', () => {
});

describe('set', () => {
const definition: SchemaDefinition = {
prop1: { type: 'string', path: '2' },
prop2: { type: 'number', path: '3', dbDecimals: 2 },
};
const valueSchema = new Schema(definition);
const documentArrayType = new DocumentArrayType(valueSchema);

test('should return a record with the subdocument array merged in', () => {
const definition: SchemaDefinition = {
prop1: { type: 'string', path: '2' },
prop2: { type: 'number', path: '3', dbDecimals: 2 },
};
const valueSchema = new Schema(definition);
const documentArrayType = new DocumentArrayType(valueSchema);

const originalRecord: MvRecord = ['unrelated'];
const value1 = Document.createSubdocumentFromRecord(valueSchema, originalRecord);
const value2 = Document.createSubdocumentFromRecord(valueSchema, originalRecord);
Expand All @@ -117,7 +117,39 @@ describe('set', () => {
expect(record).toEqual(expected);
});

test('should return a record with the subdocument array merged in and clear existing extra subdocument', () => {
const definition: SchemaDefinition = {
prop1: { type: 'string', path: '2' },
prop2: { type: 'number', path: '3', dbDecimals: 2 },
};
const valueSchema = new Schema(definition);
const documentArrayType = new DocumentArrayType(valueSchema);

const originalRecord: MvRecord = [
'unrelated',
['cleared', 'cleared', 'cleared'],
['cleared', 'cleared', 'cleared'],
];
const value1 = Document.createSubdocumentFromRecord(valueSchema, originalRecord);
const value2 = Document.createSubdocumentFromRecord(valueSchema, originalRecord);
value1.prop1 = 'foo';
value1.prop2 = 1.23;
value2.prop1 = 'bar';
value2.prop2 = 4.56;

const record = documentArrayType.set(originalRecord, [value1, value2]);
const expected: MvRecord = ['unrelated', ['foo', 'bar'], ['123', '456']];
expect(record).toEqual(expected);
});

test('should return a record with the subdocument array merged in when the document has missing schema properties', () => {
const definition: SchemaDefinition = {
prop1: { type: 'string', path: '2' },
prop2: { type: 'number', path: '3', dbDecimals: 2 },
};
const valueSchema = new Schema(definition);
const documentArrayType = new DocumentArrayType(valueSchema);

const originalRecord: MvRecord = ['unrelated'];
const value1 = Document.createSubdocumentFromRecord(valueSchema, originalRecord);
const value2 = Document.createSubdocumentFromRecord(valueSchema, originalRecord);
Expand All @@ -128,6 +160,98 @@ describe('set', () => {
const expected: MvRecord = ['unrelated', [null, 'bar'], ['123', null]];
expect(record).toEqual(expected);
});

test('should return a record with the subdocument array merged in when the schema definition is using multi-part paths', () => {
const definition: SchemaDefinition = {
prop1: { type: 'string', path: '2.1' },
prop2: { type: 'number', path: '2.2', dbDecimals: 2 },
};
const valueSchema = new Schema(definition);
const documentArrayType = new DocumentArrayType(valueSchema);

const originalRecord: MvRecord = ['unrelated'];
const value1 = Document.createSubdocumentFromRecord(valueSchema, originalRecord);
const value2 = Document.createSubdocumentFromRecord(valueSchema, originalRecord);
value1.prop1 = 'foo';
value1.prop2 = 1.23;
value2.prop1 = 'bar';
value2.prop2 = 4.56;

const record = documentArrayType.set(originalRecord, [value1, value2]);
const expected: MvRecord = [
'unrelated',
[
['foo', 'bar'],
['123', '456'],
],
];
expect(record).toEqual(expected);
});

test('should return a record with the subdocument array merged in when the schema definition is using multi-part paths and clear existing extra subdocument', () => {
const definition: SchemaDefinition = {
prop1: { type: 'string', path: '2.1' },
prop2: { type: 'number', path: '2.2', dbDecimals: 2 },
};
const valueSchema = new Schema(definition);
const documentArrayType = new DocumentArrayType(valueSchema);

const originalRecord: MvRecord = [
'unrelated',
[
['cleared', 'cleared', 'cleared'],
['cleared', 'cleared', 'cleared'],
],
];
const value1 = Document.createSubdocumentFromRecord(valueSchema, originalRecord);
const value2 = Document.createSubdocumentFromRecord(valueSchema, originalRecord);
value1.prop1 = 'foo';
value1.prop2 = 1.23;
value2.prop1 = 'bar';
value2.prop2 = 4.56;

const record = documentArrayType.set(originalRecord, [value1, value2]);
const expected: MvRecord = [
'unrelated',
[
['foo', 'bar'],
['123', '456'],
],
];
expect(record).toEqual(expected);
});

test('should return a record with the subdocument array merged in when subdocuments include arrays', () => {
const definition: SchemaDefinition = {
prop1: { type: 'string', path: '2' },
prop2: { type: 'number', path: '3', dbDecimals: 2 },
prop3: [{ type: 'string', path: '4' }],
};
const valueSchema = new Schema(definition);
const documentArrayType = new DocumentArrayType(valueSchema);

const originalRecord: MvRecord = ['unrelated'];
const value1 = Document.createSubdocumentFromRecord(valueSchema, originalRecord);
const value2 = Document.createSubdocumentFromRecord(valueSchema, originalRecord);
value1.prop1 = 'foo';
value1.prop2 = 1.23;
value1.prop3 = ['value1-pos0', 'value1-pos1'];
value2.prop1 = 'bar';
value2.prop2 = 4.56;
value2.prop3 = ['value2-pos0', 'value2-pos1'];

const record = documentArrayType.set(originalRecord, [value1, value2]);
const expected: MvRecord = [
'unrelated',
['foo', 'bar'],
['123', '456'],
[
['value1-pos0', 'value1-pos1'],
['value2-pos0', 'value2-pos1'],
],
];
expect(record).toEqual(expected);
});
});

describe('validate', () => {
Expand Down

0 comments on commit 30297a8

Please sign in to comment.