fixed bug where reference fields not being parsed when in index

This commit is contained in:
michael shanks 2019-07-18 07:44:26 +01:00
parent 921b31d595
commit 5d0d14f16a
6 changed files with 85 additions and 72 deletions

View File

@ -1,7 +1,8 @@
{
"env": {
"browser": true,
"es6": true
"es6": true,
"jest": true
},
"extends": "eslint:recommended",
"parser": "babel-eslint",

View File

@ -3,6 +3,7 @@ import {
flatten,
map,
filter,
isEqual
} from 'lodash/fp';
import { initialiseChildCollections } from '../collectionApi/initialise';
import { validate } from './validate';
@ -15,6 +16,7 @@ import {
getExactNodeForPath,
isRecord,
getNode,
getLastPartInKey,
fieldReversesReferenceToNode,
} from '../templateApi/hierarchy';
import { mapRecord } from '../indexing/evaluate';
@ -79,7 +81,6 @@ export const _save = async (app, record, context, skipValidation = false) => {
getRecordFileName(recordClone.key),
recordClone,
);
await app.publish(events.recordApi.save.onRecordUpdated, {
old: oldRecord,
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, [
getFlattenedHierarchy,
filter(isRecord),

View File

@ -24,8 +24,24 @@ const hasStringValue = (ob, path) => has(ob, path)
const isObjectWithKey = v => isObjectLike(v)
&& 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(
[isObjectWithKey, parsedSuccess],
[isString, tryParseFromString],
[isNull, () => parsedSuccess(referenceNothing())],
[defaultCase, parsedFailed],
)(v);

View File

@ -2,7 +2,7 @@ import {setupApphierarchy,
basicAppHierarchyCreator_WithFields,
basicAppHierarchyCreator_WithFields_AndIndexes} from "./specHelpers";
import {joinKey} from "../src/common";
import {some, isArray} from "lodash";
import {some, isArray, isObjectLike} from "lodash";
describe("recordApi > create > reindex", () => {
@ -107,6 +107,32 @@ describe("recordApi > create > reindex", () => {
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 () => {
const {recordApi, indexApi} =
await setupApphierarchy(basicAppHierarchyCreator_WithFields);
@ -573,7 +599,7 @@ describe("referenced object changed", () => {
it("should update the reference", async () => {
const {recordApi, indexApi} =
const { recordApi } =
await setupApphierarchy(basicAppHierarchyCreator_WithFields);
const partner1 = recordApi.getNew("/partners", "partner");

View File

@ -189,6 +189,32 @@ describe('recordApi > save then load', () => {
const savedAgain = await recordApi.load(saved.key);
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", () => {

View File

@ -1,16 +1,15 @@
import path from "path";
import {getAppApis, getRecordApi,
import {getRecordApi,
getCollectionApi, getIndexApi, getActionsApi} from "../src";
import memory from "./memory";
import {setupDatastore} from "../src/appInitialise";
import {configFolder, fieldDefinitions,
templateDefinitions, isNothing,
templateDefinitions,
joinKey,
isSomething} from "../src/common";
import { getNewIndexTemplate } from "../src/templateApi/createNodes";
import {indexTypes} from "../src/templateApi/indexes";
import getTemplateApi from "../src/templateApi";
import {getApplicationDefinition} from "../src/templateApi/getApplicationDefinition";
import getAuthApi from "../src/authApi";
import {createEventAggregator} from "../src/appInitialise/eventAggregator";
import {filter, find} from "lodash/fp";
@ -116,7 +115,7 @@ export const hierarchyFactory = (...additionalFeatures) => templateApi => {
const customerRecord = templateApi.getNewRecordTemplate(root, "customer");
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");
partnerRecord.collectionName = "partners";
@ -157,7 +156,7 @@ export const withFields = (hierarchy, templateApi) => {
const partnersReferenceIndex = templateApi.getNewIndexTemplate(root);
partnersReferenceIndex.name = "partnersReference";
partnersReferenceIndex.map = "return {name:record.businessName};";
partnersReferenceIndex.map = "return {name:record.businessName, phone:record.phone};";
partnersReferenceIndex.allowedRecordNodeIds = [partnerRecord.nodeId];
const partnerCustomersReverseIndex = templateApi.getNewIndexTemplate(partnerRecord, indexTypes.reference);
@ -172,7 +171,7 @@ export const withFields = (hierarchy, templateApi) => {
newCustomerField("createddate", "datetime");
newCustomerField("age", "number");
newCustomerField("profilepic", "file");
const customerPartnerField = newCustomerField("partner", "reference", undefined, {
newCustomerField("partner", "reference", undefined, {
indexNodeKey : "/partnersReference",
displayValue : "name",
reverseIndexNodeKeys : [joinKey(
@ -205,6 +204,7 @@ export const withFields = (hierarchy, templateApi) => {
const newPartnerField = getNewFieldAndAdd(templateApi, partnerRecord);
newPartnerField("businessName", "string");
newPartnerField("phone", "string");
const newPartnerInvoiceField = getNewFieldAndAdd(templateApi, partnerInvoiceRecord);
const partnerInvoiceTotalIncVatVield = newPartnerInvoiceField("totalIncVat", "number");
@ -215,7 +215,7 @@ export const withFields = (hierarchy, templateApi) => {
const newChargeField = getNewFieldAndAdd(templateApi, chargeRecord);
newChargeField("amount", "number");
const chargePartnerInvoiceField = newChargeField("partnerInvoice", "reference", undefined, {
newChargeField("partnerInvoice", "reference", undefined, {
reverseIndexNodeKeys : [joinKey(
partnerInvoiceRecord.nodeKey(), "partnerCharges"
)],
@ -236,7 +236,7 @@ export const withFields = (hierarchy, templateApi) => {
customersReferenceIndex.filter = "record.isalive === true";
customersReferenceIndex.allowedRecordNodeIds = [customerRecord.nodeId];
const invoiceCustomerField = newInvoiceField("customer", "reference", undefined, {
newInvoiceField("customer", "reference", undefined, {
indexNodeKey : "/customersReference",
reverseIndexNodeKeys : [findCollectionDefaultIndex(invoiceRecord).nodeKey()],
displayValue : "name"
@ -376,15 +376,11 @@ export const setupApphierarchy = async (creator, disableCleanupTransactions=fals
return apis;
};
const disableCleanupTransactions = app => {
}
export const getNewFieldAndAdd = (templateApi, record) => (name, type, initial, typeOptions) => {
const field = templateApi.getNewField(type);
field.name = name;
field.getInitialValue = !initial ? "default" : initial;
if(!!typeOptions)
if(typeOptions)
field.typeOptions = typeOptions;
templateApi.addField(record, field);
return field;
@ -397,8 +393,8 @@ export const stubEventHandler = () => {
events.push({name, context});
},
events,
getEvents: n => filter(e => e.name === n)
(events)
getEvents: n => filter(
e => e.name === n)(events)
};
};