fixed bug where reference fields not being parsed when in index
This commit is contained in:
parent
921b31d595
commit
5d0d14f16a
|
@ -1,7 +1,8 @@
|
||||||
{
|
{
|
||||||
"env": {
|
"env": {
|
||||||
"browser": true,
|
"browser": true,
|
||||||
"es6": true
|
"es6": true,
|
||||||
|
"jest": true
|
||||||
},
|
},
|
||||||
"extends": "eslint:recommended",
|
"extends": "eslint:recommended",
|
||||||
"parser": "babel-eslint",
|
"parser": "babel-eslint",
|
||||||
|
|
|
@ -3,6 +3,7 @@ import {
|
||||||
flatten,
|
flatten,
|
||||||
map,
|
map,
|
||||||
filter,
|
filter,
|
||||||
|
isEqual
|
||||||
} from 'lodash/fp';
|
} from 'lodash/fp';
|
||||||
import { initialiseChildCollections } from '../collectionApi/initialise';
|
import { initialiseChildCollections } from '../collectionApi/initialise';
|
||||||
import { validate } from './validate';
|
import { validate } from './validate';
|
||||||
|
@ -15,6 +16,7 @@ import {
|
||||||
getExactNodeForPath,
|
getExactNodeForPath,
|
||||||
isRecord,
|
isRecord,
|
||||||
getNode,
|
getNode,
|
||||||
|
getLastPartInKey,
|
||||||
fieldReversesReferenceToNode,
|
fieldReversesReferenceToNode,
|
||||||
} from '../templateApi/hierarchy';
|
} from '../templateApi/hierarchy';
|
||||||
import { mapRecord } from '../indexing/evaluate';
|
import { mapRecord } from '../indexing/evaluate';
|
||||||
|
@ -79,7 +81,6 @@ export const _save = async (app, record, context, skipValidation = false) => {
|
||||||
getRecordFileName(recordClone.key),
|
getRecordFileName(recordClone.key),
|
||||||
recordClone,
|
recordClone,
|
||||||
);
|
);
|
||||||
|
|
||||||
await app.publish(events.recordApi.save.onRecordUpdated, {
|
await app.publish(events.recordApi.save.onRecordUpdated, {
|
||||||
old: oldRecord,
|
old: oldRecord,
|
||||||
new: recordClone,
|
new: recordClone,
|
||||||
|
@ -122,59 +123,6 @@ const initialiseReverseReferenceIndexes = async (app, record) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const maintainReferentialIntegrity = async (app, indexingApi, oldRecord, newRecord) => {
|
|
||||||
/*
|
|
||||||
FOREACH Field that reference this object
|
|
||||||
- options Index node that for field
|
|
||||||
- has options index changed for referenced record?
|
|
||||||
- FOREACH reverse index of field
|
|
||||||
- FOREACH referencingRecord in reverse index
|
|
||||||
- Is field value still pointing to referencedRecord
|
|
||||||
- Update referencingRecord.fieldName to new value
|
|
||||||
- Save
|
|
||||||
*/
|
|
||||||
const recordNode = getExactNodeForPath(app.hierarchy)(newRecord.key);
|
|
||||||
const referenceFields = fieldsThatReferenceThisRecord(
|
|
||||||
app, recordNode,
|
|
||||||
);
|
|
||||||
|
|
||||||
const updates = $(referenceFields, [
|
|
||||||
map(f => ({
|
|
||||||
node: getNode(
|
|
||||||
app.hierarchy, f.typeOptions.indexNodeKey,
|
|
||||||
),
|
|
||||||
field: f,
|
|
||||||
})),
|
|
||||||
map(n => ({
|
|
||||||
old: mapRecord(oldRecord, n.node),
|
|
||||||
new: mapRecord(newRecord, n.node),
|
|
||||||
indexNode: n.node,
|
|
||||||
field: n.field,
|
|
||||||
reverseIndexKeys: $(n.field.typeOptions.reverseIndexNodeKeys, [
|
|
||||||
map(k => joinKey(
|
|
||||||
newRecord.key,
|
|
||||||
getLastPartInKey(k),
|
|
||||||
)),
|
|
||||||
]),
|
|
||||||
})),
|
|
||||||
filter(diff => !isEqual(diff.old)(diff.new)),
|
|
||||||
]);
|
|
||||||
|
|
||||||
for (const update of updates) {
|
|
||||||
for (const reverseIndexKey of update.reverseIndexKeys) {
|
|
||||||
const rows = await listItems(app)(reverseIndexKey);
|
|
||||||
|
|
||||||
for (const key of map(r => r.key)(rows)) {
|
|
||||||
const record = await _load(app, key);
|
|
||||||
if (record[update.field.name].key === newRecord.key) {
|
|
||||||
record[update.field.name] = update.new;
|
|
||||||
await _save(app, indexingApi, record, undefined, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const fieldsThatReferenceThisRecord = (app, recordNode) => $(app.hierarchy, [
|
const fieldsThatReferenceThisRecord = (app, recordNode) => $(app.hierarchy, [
|
||||||
getFlattenedHierarchy,
|
getFlattenedHierarchy,
|
||||||
filter(isRecord),
|
filter(isRecord),
|
||||||
|
|
|
@ -24,8 +24,24 @@ const hasStringValue = (ob, path) => has(ob, path)
|
||||||
const isObjectWithKey = v => isObjectLike(v)
|
const isObjectWithKey = v => isObjectLike(v)
|
||||||
&& hasStringValue(v, 'key');
|
&& hasStringValue(v, 'key');
|
||||||
|
|
||||||
|
const tryParseFromString = s => {
|
||||||
|
|
||||||
|
try {
|
||||||
|
const asObj = JSON.parse(s);
|
||||||
|
if(isObjectWithKey) {
|
||||||
|
return parsedSuccess(asObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(_) {
|
||||||
|
// EMPTY
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedFailed(s);
|
||||||
|
}
|
||||||
|
|
||||||
const referenceTryParse = v => switchCase(
|
const referenceTryParse = v => switchCase(
|
||||||
[isObjectWithKey, parsedSuccess],
|
[isObjectWithKey, parsedSuccess],
|
||||||
|
[isString, tryParseFromString],
|
||||||
[isNull, () => parsedSuccess(referenceNothing())],
|
[isNull, () => parsedSuccess(referenceNothing())],
|
||||||
[defaultCase, parsedFailed],
|
[defaultCase, parsedFailed],
|
||||||
)(v);
|
)(v);
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {setupApphierarchy,
|
||||||
basicAppHierarchyCreator_WithFields,
|
basicAppHierarchyCreator_WithFields,
|
||||||
basicAppHierarchyCreator_WithFields_AndIndexes} from "./specHelpers";
|
basicAppHierarchyCreator_WithFields_AndIndexes} from "./specHelpers";
|
||||||
import {joinKey} from "../src/common";
|
import {joinKey} from "../src/common";
|
||||||
import {some, isArray} from "lodash";
|
import {some, isArray, isObjectLike} from "lodash";
|
||||||
|
|
||||||
describe("recordApi > create > reindex", () => {
|
describe("recordApi > create > reindex", () => {
|
||||||
|
|
||||||
|
@ -107,6 +107,32 @@ describe("recordApi > create > reindex", () => {
|
||||||
expect(customers[0].name).toBe("Ledog");
|
expect(customers[0].name).toBe("Ledog");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should add reference field to index and reparse", async () => {
|
||||||
|
const {recordApi, indexApi} =
|
||||||
|
await setupApphierarchy(basicAppHierarchyCreator_WithFields);
|
||||||
|
|
||||||
|
const partner = recordApi.getNew("/partners", "partner");
|
||||||
|
partner.businessName = "ACME";
|
||||||
|
partner.phone = "098766e6";
|
||||||
|
await recordApi.save(partner);
|
||||||
|
|
||||||
|
const customer = recordApi.getNew("/customers", "customer");
|
||||||
|
customer.surname = "Ledog";
|
||||||
|
customer.age = 9;
|
||||||
|
customer.isalive = true,
|
||||||
|
customer.createdDate = new Date();
|
||||||
|
customer.partner = partner;
|
||||||
|
await recordApi.save(customer);
|
||||||
|
|
||||||
|
const customers = await indexApi.listItems("/customer_index");
|
||||||
|
|
||||||
|
expect(customers.length).toBe(1);
|
||||||
|
expect(isObjectLike(customer.partner)).toBeTruthy();
|
||||||
|
expect(customers[0].partner.key).toBe(partner.key);
|
||||||
|
expect(customers[0].partner.name).toBe(partner.businessName);
|
||||||
|
expect(customers[0].partner.phone).toBe(partner.phone);
|
||||||
|
});
|
||||||
|
|
||||||
it("should add to reverse reference index, when required", async () => {
|
it("should add to reverse reference index, when required", async () => {
|
||||||
const {recordApi, indexApi} =
|
const {recordApi, indexApi} =
|
||||||
await setupApphierarchy(basicAppHierarchyCreator_WithFields);
|
await setupApphierarchy(basicAppHierarchyCreator_WithFields);
|
||||||
|
@ -573,7 +599,7 @@ describe("referenced object changed", () => {
|
||||||
|
|
||||||
it("should update the reference", async () => {
|
it("should update the reference", async () => {
|
||||||
|
|
||||||
const {recordApi, indexApi} =
|
const { recordApi } =
|
||||||
await setupApphierarchy(basicAppHierarchyCreator_WithFields);
|
await setupApphierarchy(basicAppHierarchyCreator_WithFields);
|
||||||
|
|
||||||
const partner1 = recordApi.getNew("/partners", "partner");
|
const partner1 = recordApi.getNew("/partners", "partner");
|
||||||
|
|
|
@ -189,6 +189,32 @@ describe('recordApi > save then load', () => {
|
||||||
const savedAgain = await recordApi.load(saved.key);
|
const savedAgain = await recordApi.load(saved.key);
|
||||||
expect(savedAgain.surname).toBe(saved.surname);
|
expect(savedAgain.surname).toBe(saved.surname);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should maintain referential integrity", async () => {
|
||||||
|
const {recordApi} =
|
||||||
|
await setupApphierarchy(basicAppHierarchyCreator_WithFields);
|
||||||
|
|
||||||
|
const referredByCustomer = recordApi.getNew("/customers", "customer");
|
||||||
|
referredByCustomer.surname = "Ledog";
|
||||||
|
referredByCustomer.age = 9;
|
||||||
|
referredByCustomer.isalive = true,
|
||||||
|
referredByCustomer.createdDate = new Date();
|
||||||
|
const savedReferredBy = await recordApi.save(referredByCustomer);
|
||||||
|
|
||||||
|
const referredCustomer = recordApi.getNew("/customers", "customer");
|
||||||
|
referredCustomer.surname = "Zeecat";
|
||||||
|
referredCustomer.age = 9;
|
||||||
|
referredCustomer.isalive = true,
|
||||||
|
referredCustomer.createdDate = new Date();
|
||||||
|
referredCustomer.referredBy = referredByCustomer;
|
||||||
|
await recordApi.save(referredCustomer);
|
||||||
|
|
||||||
|
savedReferredBy.surname = "Zeedog";
|
||||||
|
await recordApi.save(savedReferredBy);
|
||||||
|
|
||||||
|
const loadedReferredTo = await recordApi.load(referredCustomer.key);
|
||||||
|
expect(loadedReferredTo.referredBy.surname).toBe("Zeedog");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("save", () => {
|
describe("save", () => {
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import {getAppApis, getRecordApi,
|
import {getRecordApi,
|
||||||
getCollectionApi, getIndexApi, getActionsApi} from "../src";
|
getCollectionApi, getIndexApi, getActionsApi} from "../src";
|
||||||
import memory from "./memory";
|
import memory from "./memory";
|
||||||
import {setupDatastore} from "../src/appInitialise";
|
import {setupDatastore} from "../src/appInitialise";
|
||||||
import {configFolder, fieldDefinitions,
|
import {configFolder, fieldDefinitions,
|
||||||
templateDefinitions, isNothing,
|
templateDefinitions,
|
||||||
joinKey,
|
joinKey,
|
||||||
isSomething} from "../src/common";
|
isSomething} from "../src/common";
|
||||||
import { getNewIndexTemplate } from "../src/templateApi/createNodes";
|
import { getNewIndexTemplate } from "../src/templateApi/createNodes";
|
||||||
import {indexTypes} from "../src/templateApi/indexes";
|
import {indexTypes} from "../src/templateApi/indexes";
|
||||||
import getTemplateApi from "../src/templateApi";
|
import getTemplateApi from "../src/templateApi";
|
||||||
import {getApplicationDefinition} from "../src/templateApi/getApplicationDefinition";
|
|
||||||
import getAuthApi from "../src/authApi";
|
import getAuthApi from "../src/authApi";
|
||||||
import {createEventAggregator} from "../src/appInitialise/eventAggregator";
|
import {createEventAggregator} from "../src/appInitialise/eventAggregator";
|
||||||
import {filter, find} from "lodash/fp";
|
import {filter, find} from "lodash/fp";
|
||||||
|
@ -116,7 +115,7 @@ export const hierarchyFactory = (...additionalFeatures) => templateApi => {
|
||||||
|
|
||||||
const customerRecord = templateApi.getNewRecordTemplate(root, "customer");
|
const customerRecord = templateApi.getNewRecordTemplate(root, "customer");
|
||||||
customerRecord.collectionName = "customers";
|
customerRecord.collectionName = "customers";
|
||||||
findCollectionDefaultIndex(customerRecord).map = "return {surname:record.surname, isalive:record.isalive};";
|
findCollectionDefaultIndex(customerRecord).map = "return {surname:record.surname, isalive:record.isalive, partner:record.partner};";
|
||||||
|
|
||||||
const partnerRecord = templateApi.getNewRecordTemplate(root, "partner");
|
const partnerRecord = templateApi.getNewRecordTemplate(root, "partner");
|
||||||
partnerRecord.collectionName = "partners";
|
partnerRecord.collectionName = "partners";
|
||||||
|
@ -157,7 +156,7 @@ export const withFields = (hierarchy, templateApi) => {
|
||||||
|
|
||||||
const partnersReferenceIndex = templateApi.getNewIndexTemplate(root);
|
const partnersReferenceIndex = templateApi.getNewIndexTemplate(root);
|
||||||
partnersReferenceIndex.name = "partnersReference";
|
partnersReferenceIndex.name = "partnersReference";
|
||||||
partnersReferenceIndex.map = "return {name:record.businessName};";
|
partnersReferenceIndex.map = "return {name:record.businessName, phone:record.phone};";
|
||||||
partnersReferenceIndex.allowedRecordNodeIds = [partnerRecord.nodeId];
|
partnersReferenceIndex.allowedRecordNodeIds = [partnerRecord.nodeId];
|
||||||
|
|
||||||
const partnerCustomersReverseIndex = templateApi.getNewIndexTemplate(partnerRecord, indexTypes.reference);
|
const partnerCustomersReverseIndex = templateApi.getNewIndexTemplate(partnerRecord, indexTypes.reference);
|
||||||
|
@ -172,7 +171,7 @@ export const withFields = (hierarchy, templateApi) => {
|
||||||
newCustomerField("createddate", "datetime");
|
newCustomerField("createddate", "datetime");
|
||||||
newCustomerField("age", "number");
|
newCustomerField("age", "number");
|
||||||
newCustomerField("profilepic", "file");
|
newCustomerField("profilepic", "file");
|
||||||
const customerPartnerField = newCustomerField("partner", "reference", undefined, {
|
newCustomerField("partner", "reference", undefined, {
|
||||||
indexNodeKey : "/partnersReference",
|
indexNodeKey : "/partnersReference",
|
||||||
displayValue : "name",
|
displayValue : "name",
|
||||||
reverseIndexNodeKeys : [joinKey(
|
reverseIndexNodeKeys : [joinKey(
|
||||||
|
@ -205,6 +204,7 @@ export const withFields = (hierarchy, templateApi) => {
|
||||||
|
|
||||||
const newPartnerField = getNewFieldAndAdd(templateApi, partnerRecord);
|
const newPartnerField = getNewFieldAndAdd(templateApi, partnerRecord);
|
||||||
newPartnerField("businessName", "string");
|
newPartnerField("businessName", "string");
|
||||||
|
newPartnerField("phone", "string");
|
||||||
|
|
||||||
const newPartnerInvoiceField = getNewFieldAndAdd(templateApi, partnerInvoiceRecord);
|
const newPartnerInvoiceField = getNewFieldAndAdd(templateApi, partnerInvoiceRecord);
|
||||||
const partnerInvoiceTotalIncVatVield = newPartnerInvoiceField("totalIncVat", "number");
|
const partnerInvoiceTotalIncVatVield = newPartnerInvoiceField("totalIncVat", "number");
|
||||||
|
@ -215,7 +215,7 @@ export const withFields = (hierarchy, templateApi) => {
|
||||||
const newChargeField = getNewFieldAndAdd(templateApi, chargeRecord);
|
const newChargeField = getNewFieldAndAdd(templateApi, chargeRecord);
|
||||||
newChargeField("amount", "number");
|
newChargeField("amount", "number");
|
||||||
|
|
||||||
const chargePartnerInvoiceField = newChargeField("partnerInvoice", "reference", undefined, {
|
newChargeField("partnerInvoice", "reference", undefined, {
|
||||||
reverseIndexNodeKeys : [joinKey(
|
reverseIndexNodeKeys : [joinKey(
|
||||||
partnerInvoiceRecord.nodeKey(), "partnerCharges"
|
partnerInvoiceRecord.nodeKey(), "partnerCharges"
|
||||||
)],
|
)],
|
||||||
|
@ -236,7 +236,7 @@ export const withFields = (hierarchy, templateApi) => {
|
||||||
customersReferenceIndex.filter = "record.isalive === true";
|
customersReferenceIndex.filter = "record.isalive === true";
|
||||||
customersReferenceIndex.allowedRecordNodeIds = [customerRecord.nodeId];
|
customersReferenceIndex.allowedRecordNodeIds = [customerRecord.nodeId];
|
||||||
|
|
||||||
const invoiceCustomerField = newInvoiceField("customer", "reference", undefined, {
|
newInvoiceField("customer", "reference", undefined, {
|
||||||
indexNodeKey : "/customersReference",
|
indexNodeKey : "/customersReference",
|
||||||
reverseIndexNodeKeys : [findCollectionDefaultIndex(invoiceRecord).nodeKey()],
|
reverseIndexNodeKeys : [findCollectionDefaultIndex(invoiceRecord).nodeKey()],
|
||||||
displayValue : "name"
|
displayValue : "name"
|
||||||
|
@ -376,15 +376,11 @@ export const setupApphierarchy = async (creator, disableCleanupTransactions=fals
|
||||||
return apis;
|
return apis;
|
||||||
};
|
};
|
||||||
|
|
||||||
const disableCleanupTransactions = app => {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getNewFieldAndAdd = (templateApi, record) => (name, type, initial, typeOptions) => {
|
export const getNewFieldAndAdd = (templateApi, record) => (name, type, initial, typeOptions) => {
|
||||||
const field = templateApi.getNewField(type);
|
const field = templateApi.getNewField(type);
|
||||||
field.name = name;
|
field.name = name;
|
||||||
field.getInitialValue = !initial ? "default" : initial;
|
field.getInitialValue = !initial ? "default" : initial;
|
||||||
if(!!typeOptions)
|
if(typeOptions)
|
||||||
field.typeOptions = typeOptions;
|
field.typeOptions = typeOptions;
|
||||||
templateApi.addField(record, field);
|
templateApi.addField(record, field);
|
||||||
return field;
|
return field;
|
||||||
|
@ -397,8 +393,8 @@ export const stubEventHandler = () => {
|
||||||
events.push({name, context});
|
events.push({name, context});
|
||||||
},
|
},
|
||||||
events,
|
events,
|
||||||
getEvents: n => filter(e => e.name === n)
|
getEvents: n => filter(
|
||||||
(events)
|
e => e.name === n)(events)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue