Skip to content

Commit 50844b7

Browse files
Related to #25, Handle Parent relations in the same way as child relations so that loadRelations can be called on any js-data relations
1 parent 55fdacb commit 50844b7

File tree

4 files changed

+312
-194
lines changed

4 files changed

+312
-194
lines changed

Diff for: src/JsonApiAdapter.ts

+2
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ export class JsonApiAdapter implements JSData.IDSAdapter {
136136
*/
137137
getPath(method: string, resourceConfig: JSData.DSResourceDefinition<any>, id: Object, options: JSData.DSConfiguration): string {
138138

139+
//(<JsonApiAdapter.DSJsonApiAdapterOptions>options).jsonApi.jsonApiPath;
139140
if (Helper.JsonApiHelper.ContainsJsonApiContentTypeHeader(this.DSUtils.get<{ [name: string]: string }>(options, 'headers'))) {
140141
//Get the resource item
141142
var item: JsonApi.JsonApiData;
@@ -159,6 +160,7 @@ export class JsonApiAdapter implements JSData.IDSAdapter {
159160
// ANOTHER option is to pass the relationship self link in options, but would prefer to be able to obtains this transparently!!
160161

161162
//[1] Get back the parent object referenced in finaAll / loadRelations
163+
// This requires a belongsTo relationship with parent set true!!
162164
let parentResourceName = (<any>resourceConfig).parent;
163165

164166
// The local key of the child <==> the foreign key of the parent

Diff for: src/JsonApiSerializer.ts

+72-40
Original file line numberDiff line numberDiff line change
@@ -165,17 +165,13 @@ export class SerializationOptions {
165165
return resource ? new SerializationOptions(resource) : null;
166166
}
167167

168-
getParentRelation(relationName?: string): JSData.RelationDefinition {
168+
getBelongsToRelation(parentType: string, relationName? : string): JSData.RelationDefinition {
169169
if (this.resourceDef.relations && this.resourceDef.relations.belongsTo) {
170170

171171
if (relationName) {
172-
return this.resourceDef.relations.belongsTo[relationName];
172+
return this.resourceDef.relations.belongsTo[parentType];
173173
}else {
174-
for (var r in this.resourceDef.relations.belongsTo) {
175-
if (this.resourceDef.relations.belongsTo[r] && this.resourceDef.relations.belongsTo[r][0]) {
176-
return this.resourceDef.relations.belongsTo[r][0];
177-
}
178-
}
174+
return this.resourceDef.relations.belongsTo[parentType];
179175
}
180176
}
181177

@@ -286,6 +282,31 @@ export class SerializationOptions {
286282
});
287283
}
288284

285+
/**
286+
* @name enumerateAllParentRelations
287+
* @desc Encapsulates enumerating child relationships
288+
* @param callback A function tobe called back with child relationship data
289+
*/
290+
enumerateAllParentRelations(callback: (relation: JSData.RelationDefinition, index?: number, source?: Object) => boolean): void {
291+
DSUTILS.forEach(this.resourceDef.relationList, (relation: JSData.RelationDefinition, index?: number, source?: Object) => {
292+
if (relation.type === jsDataBelongsTo) {
293+
return callback(relation, index, source);
294+
}
295+
});
296+
}
297+
298+
/**
299+
* @name enumerateRelations
300+
* @desc Encapsulates enumerating child relationships
301+
* @param callback A function tobe called back with child relationship data
302+
*/
303+
enumerateRelations(callback: (relation: JSData.RelationDefinition, index?: number, source?: Object) => boolean): void {
304+
DSUTILS.forEach(this.resourceDef.relationList, (relation: JSData.RelationDefinition, index?: number, source?: Object) => {
305+
return callback(relation, index, source);
306+
});
307+
}
308+
309+
289310
//Find relationship by relationship name
290311
private getChildRelations(relationType: string): Array<JSData.RelationDefinition> {
291312
relationType = relationType.toLowerCase();
@@ -1075,7 +1096,7 @@ export class JsonApiHelper {
10751096
}
10761097

10771098
// Get the parent relation of the joining type
1078-
var joiningTableChildRelation = joiningTypeResourceDef.getParentRelation(joinMetaData.type);
1099+
var joiningTableChildRelation = joiningTypeResourceDef.getBelongsToRelation(joinMetaData.type);
10791100
if (!joiningTableChildRelation || !joiningTableChildRelation[0]) {
10801101
throw new Error(
10811102
'Expected Many-To-Many Joining table to have a "belongsTo" relation of type:' + joinMetaData.type +
@@ -1323,39 +1344,44 @@ export class JsonApiHelper {
13231344
* links in the links object provided.
13241345
* This will modify the response object.
13251346
*/
1326-
private static setParentIds(options: SerializationOptions, data: JsonApi.JsonApiData, fields: any, metaData: MetaData): string {
1327-
// This object belongs to a parent then search backwards in url for the
1347+
private static setParentIds(options: SerializationOptions, data: JsonApi.JsonApiData, fields: any, metaData: MetaData): void {
1348+
// This object belongs to a parent so search backwards in the self link url for the
13281349
// parent resource and then the next field we assume contains the parent reource id
1329-
// e.g. api/Parent/1/Children
1330-
var parentRel = options.getParentRelation();
1331-
if (parentRel) {
1332-
// If Type is set and it has links and links has a self link
1333-
if (data.type && data.GetSelfLink && data.GetSelfLink()) {
1350+
// e.g. api/Parent/1/Childrelation
1351+
//Here 'Parent' should be the data.type of the parent, follows by its id, then the child relationship
13341352

1335-
var parentName = parentRel.relation;
1336-
// Get configured local key
1337-
var localKey = parentRel.localKey;
1353+
//Here:
1354+
// - Parent is the name of the parent type , follows by the id.
1355+
// - ChildRelation - is the localField/relationName on the parent
13381356

1339-
if (!localKey) {
1340-
throw new Error(
1341-
'ERROR: Incorrect js-data, relationship definition on js-data resource, Name:' + options.type + 'Relationship Name:' + parentRel.relation +
1342-
'A "belongsTo" relationship requires a "localKey" to be configured');
1343-
}
13441357

1345-
var selfLinkArray = data.GetSelfLink().split('/');
1346-
var parentResourceIndex = selfLinkArray.lastIndexOf(parentName);
1347-
if (parentResourceIndex > 0) {
1348-
fields[localKey] = selfLinkArray[parentResourceIndex + 1]; // Set Parent Id
1358+
// Get parent link
1359+
if (data.type && data.GetSelfLink && data.GetSelfLink()) {
1360+
var selfLinkArray = data.GetSelfLink().split('/');
1361+
1362+
// Iterate over parent relations
1363+
// Look for parent type in self link.
1364+
// If found get the id and set to loalKey
1365+
options.enumerateAllParentRelations((rel: JSData.RelationDefinition) => {
1366+
1367+
// If we find a parent/belongsTo relationship with the same type as the parent then use it.
1368+
// There should only ever be one parent relationship of a given type.
1369+
var parentResourceIndex = selfLinkArray.lastIndexOf(rel.relation);
1370+
if (parentResourceIndex >= 0 && rel.localKey) {
1371+
//We found a match
1372+
fields[rel.localKey] = selfLinkArray[parentResourceIndex + 1]; // Set Parent Id
13491373
var parentLink = selfLinkArray.slice(0, parentResourceIndex + 2).join('/');
13501374

1351-
metaData.WithRelationshipLink(JSONAPI_PARENT_LINK, JSONAPI_PARENT_LINK, parentRel.relation, parentLink);
1352-
return parentLink;
1375+
// This will allow loadRelations to load this a parent relationship!!
1376+
// Im not sure when it would make sense to call loadRelations on a parent link?
1377+
// Maybe if somehow you got an object without first loading its parent and called load relations on the
1378+
// parent relation ?
1379+
metaData.WithRelationshipLink(rel.localField, JSONAPI_PARENT_LINK, rel.relation, parentLink);
1380+
return false;
13531381
}
1354-
} else {
1355-
LogWarning('Resource ' + options.type + ' has a "belongsTo" relation but is missing a self link and so its parent relation can not be set');
1356-
}
1382+
return true;
1383+
});
13571384
}
1358-
return null;
13591385
}
13601386

13611387
/**
@@ -1386,10 +1412,10 @@ export class JsonApiHelper {
13861412
// This is not required oneach data item this applies to the Resource Definition
13871413
var def: SerializationOptions = new SerializationOptions(resource);
13881414

1389-
def.enumerateAllChildRelations((relationDef: JSData.RelationDefinition) => {
1415+
def.enumerateRelations((relationDef: JSData.RelationDefinition) => {
13901416
if (typeof relationDef.load !== 'function') {
13911417

1392-
// This adds a customrelationship load method that is called by loadRelations to override default behaviour
1418+
// This adds a custom relationship load method that is called by loadRelations to override default behaviour
13931419
// Here we add the relationship url to options and store a reference to this functon, set the relationshipDef load functions to undefined
13941420
// and call loadRelations again, this time options will contain the stored relatedLink and the load function will not get re-called as we have removed it
13951421
// Then after loadRelations completes or fails, restore the load function once again
@@ -1400,11 +1426,17 @@ export class JsonApiHelper {
14001426
optionsOrig: JSData.DSAdapterOperationConfiguration) => {
14011427

14021428
var meta = MetaData.TryGetMetaData(instance);
1403-
var relatedLink = meta.getRelationshipLink(relationDef.localField, JSONAPI_RELATED_LINK);
1404-
var options: JsonApiAdapter.DSJsonApiAdapterOptions = <any>optionsOrig;
1405-
if (relatedLink) {
1406-
options.jsonApi = options.jsonApi || <JsonApiAdapter.DSJsonApiOptions>{};
1407-
options.jsonApi.jsonApiPath = options.jsonApi.jsonApiPath || relatedLink.url;
1429+
if (meta) {
1430+
var relatedLink = meta.getRelationshipLink(
1431+
relationDef.localField,
1432+
(relationDef.type === jsDataBelongsTo) ? JSONAPI_PARENT_LINK : JSONAPI_RELATED_LINK
1433+
);
1434+
1435+
if (relatedLink) {
1436+
var options: JsonApiAdapter.DSJsonApiAdapterOptions = <any>optionsOrig;
1437+
options.jsonApi = options.jsonApi || <JsonApiAdapter.DSJsonApiOptions>{};
1438+
options.jsonApi.jsonApiPath = options.jsonApi.jsonApiPath || relatedLink.url;
1439+
}
14081440
}
14091441

14101442
var tmp = { target: relationDef, func: relationDef.load };

0 commit comments

Comments
 (0)