Skip to content
734 changes: 0 additions & 734 deletions package-lock.json

Large diffs are not rendered by default.

81 changes: 75 additions & 6 deletions src/models/json-api.model.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ describe('JsonApiModel', () => {
const BOOK_NUMBER = 4;
const DATA = getAuthorData('books', BOOK_NUMBER);
author = new Author(datastore, DATA);
author.syncRelationships(DATA, getIncludedBooks(BOOK_NUMBER), 0);
author.syncRelationships(DATA, getIncludedBooks(BOOK_NUMBER));
expect(author).toBeDefined();
expect(author.id).toBe(AUTHOR_ID);
expect(author.books).toBeDefined();
Expand All @@ -90,7 +90,7 @@ describe('JsonApiModel', () => {
const DATA = getAuthorData('books', BOOK_NUMBER);
author = new Author(datastore, DATA);
datastore.addToStore(author);
author.syncRelationships(DATA, getIncludedBooks(BOOK_NUMBER), 0);
author.syncRelationships(DATA, getIncludedBooks(BOOK_NUMBER));
author.books.forEach((book: Book, index: number) => {
expect(book.author).toBeDefined();
expect(book.author).toEqual(author);
Expand All @@ -106,7 +106,7 @@ describe('JsonApiModel', () => {
const DATA = getAuthorData(REL, BOOK_NUMBER);
const INCLUDED = getIncludedBooks(BOOK_NUMBER, REL, CHAPTERS_NUMBER);
author = new Author(datastore, DATA);
author.syncRelationships(DATA, INCLUDED, 0);
author.syncRelationships(DATA, INCLUDED);
expect(author).toBeDefined();
expect(author.id).toBe(AUTHOR_ID);
expect(author.books).toBeDefined();
Expand Down Expand Up @@ -135,18 +135,87 @@ describe('JsonApiModel', () => {
const INCLUDED = getIncludedBooks(BOOK_NUMBER);
const NEW_BOOK_TITLE = 'The Hobbit';
author = new Author(datastore, DATA);
author.syncRelationships(DATA, INCLUDED, 0);
INCLUDED.forEach((model) => {
author.syncRelationships(DATA, INCLUDED);
const newIncluded = INCLUDED.concat([]);
newIncluded.forEach((model) => {
if (model.type === 'books') {
model.attributes.title = NEW_BOOK_TITLE;
}
});
author.syncRelationships(DATA, INCLUDED, 0);
author.syncRelationships(DATA, newIncluded);
author.books.forEach((book) => {
expect(book.title).toBe(NEW_BOOK_TITLE);
});
});
});
});

describe('parseBelongsTo', () => {
it('should parse the first level of belongsTo relationships', () => {
const REL = 'books';
const BOOK_NUMBER = 2;
const CHAPTERS_NUMBER = 4;
const DATA = getAuthorData(REL, BOOK_NUMBER);
const INCLUDED = getIncludedBooks(BOOK_NUMBER, 'books.chapters,books.firstChapter', 5);

author = new Author(datastore, DATA);
author.syncRelationships(DATA, INCLUDED);

expect(author.books[0].firstChapter).toBeDefined();
});

it('should parse the second level of belongsTo relationships', () => {
const REL = 'books';
const BOOK_NUMBER = 2;
const CHAPTERS_NUMBER = 4;
const DATA = getAuthorData(REL, BOOK_NUMBER);
const INCLUDED = getIncludedBooks(
BOOK_NUMBER,
'books.chapters,books.firstChapter,books.firstChapter.firstSection',
5
);

author = new Author(datastore, DATA);
author.syncRelationships(DATA, INCLUDED);

expect(author.books[0].firstChapter.firstSection).toBeDefined();
});

it('should parse the third level of belongsTo relationships', () => {
const REL = 'books';
const BOOK_NUMBER = 2;
const CHAPTERS_NUMBER = 4;
const DATA = getAuthorData(REL, BOOK_NUMBER);
const INCLUDED = getIncludedBooks(
BOOK_NUMBER,
// tslint:disable-next-line:max-line-length
'books.chapters,books.firstChapter,books.firstChapter.firstSection,books.firstChapter.firstSection.firstParagraph',
5
);

author = new Author(datastore, DATA);
author.syncRelationships(DATA, INCLUDED);

expect(author.books[0].firstChapter.firstSection.firstParagraph).toBeDefined();
});

it('should parse the fourth level of belongsTo relationships', () => {
const REL = 'books';
const BOOK_NUMBER = 2;
const CHAPTERS_NUMBER = 4;
const DATA = getAuthorData(REL, BOOK_NUMBER);
const INCLUDED = getIncludedBooks(
BOOK_NUMBER,
// tslint:disable-next-line:max-line-length
'books.chapters,books.firstChapter,books.firstChapter.firstSection,books.firstChapter.firstSection.firstParagraph,books.firstChapter.firstSection.firstParagraph.firstSentence',
5
);

author = new Author(datastore, DATA);
author.syncRelationships(DATA, INCLUDED);

expect(author.books[0].firstChapter.firstSection.firstParagraph.firstSentence).toBeDefined();
});
});
});
});
67 changes: 48 additions & 19 deletions src/models/json-api.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export class JsonApiModel {
id: string;
[key: string]: any;

lastSyncModels: Array<any>;

// tslint:disable-next-line:variable-name
constructor(private _datastore: JsonApiDatastore, data?: any) {
if (data) {
Expand All @@ -18,11 +20,23 @@ export class JsonApiModel {
}
}

syncRelationships(data: any, included: any, level: number): void {
syncRelationships(data: any, included: any, remainingModels?: Array<any>): void {
if (this.lastSyncModels === included) {
return;
}

if (data) {
this.parseHasMany(data, included, level);
this.parseBelongsTo(data, included, level);
let modelsForProcessing = remainingModels;

if (!modelsForProcessing) {
modelsForProcessing = [].concat(included);
}

this.parseHasMany(data, included, modelsForProcessing);
this.parseBelongsTo(data, included, modelsForProcessing);
}

this.lastSyncModels = included;
}

save(params?: any, headers?: Headers): Observable<this> {
Expand Down Expand Up @@ -71,7 +85,7 @@ export class JsonApiModel {
}


private parseHasMany(data: any, included: any, level: number): void {
private parseHasMany(data: any, included: any, remainingModels: Array<any>): void {
const hasMany: any = Reflect.getMetadata('HasMany', this);

if (hasMany) {
Expand All @@ -91,8 +105,14 @@ export class JsonApiModel {
const modelType: ModelType<this> = Reflect.getMetadata('JsonApiDatastoreConfig', this._datastore.constructor).models[typeName];

if (modelType) {
// tslint:disable-next-line:max-line-length
const relationshipModels: JsonApiModel[] = this.getHasManyRelationship(modelType, relationship.data, included, typeName, level);
const relationshipModels: JsonApiModel[] = this.getHasManyRelationship(
modelType,
relationship.data,
included,
typeName,
remainingModels
);

if (relationshipModels.length > 0) {
allModels = allModels.concat(relationshipModels);
}
Expand All @@ -110,7 +130,7 @@ export class JsonApiModel {
}
}

private parseBelongsTo(data: any, included: any, level: number): void {
private parseBelongsTo(data: any, included: Array<any>, remainingModels: Array<any>): void {
const belongsTo: any = Reflect.getMetadata('BelongsTo', this);

if (belongsTo) {
Expand All @@ -122,13 +142,14 @@ export class JsonApiModel {
const typeName: string = dataRelationship.type;
// tslint:disable-next-line:max-line-length
const modelType: ModelType<this> = Reflect.getMetadata('JsonApiDatastoreConfig', this._datastore.constructor).models[typeName];

if (modelType) {
const relationshipModel = this.getBelongsToRelationship(
modelType,
dataRelationship,
included,
typeName,
level
remainingModels
);

if (relationshipModel) {
Expand All @@ -148,45 +169,53 @@ export class JsonApiModel {
data: any,
included: any,
typeName: string,
level: number
remainingModels: Array<any>
): Array<T> {
const relationshipList: Array<T> = [];

data.forEach((item: any) => {
const relationshipData: any = find(included, { id: item.id, type: typeName });
const relationshipData: any = find(remainingModels, { id: item.id, type: typeName });

if (relationshipData) {
const newObject: T = this.createOrPeek(modelType, relationshipData);

if (level <= 2) {
newObject.syncRelationships(relationshipData, included, level + 1);
}
const indexOfNewlyFoundModel = remainingModels.indexOf(relationshipData);
const modelsForProcessing = remainingModels.concat([]);
modelsForProcessing.splice(indexOfNewlyFoundModel, 1);

newObject.syncRelationships(relationshipData, included, modelsForProcessing);

relationshipList.push(newObject);
}
});

return relationshipList;
}


private getBelongsToRelationship<T extends JsonApiModel>(
modelType: ModelType<T>,
data: any,
included: any,
included: Array<any>,
typeName: string,
level: number
remainingModels: Array<any>
): T | null {
const id: string = data.id;
const relationshipData: any = find(included, { id, type: typeName });

const relationshipData: any = find(remainingModels, { id, type: typeName });

if (relationshipData) {
const newObject: T = this.createOrPeek(modelType, relationshipData);

if (level <= 2) {
newObject.syncRelationships(relationshipData, included, level + 1);
}
const indexOfNewlyFoundModel = remainingModels.indexOf(relationshipData);
const modelsForProcessing = remainingModels.concat([]);
modelsForProcessing.splice(indexOfNewlyFoundModel, 1);

newObject.syncRelationships(relationshipData, included, modelsForProcessing);

return newObject;
}

return this._datastore.peekRecord(modelType, id);
}

Expand Down
4 changes: 2 additions & 2 deletions src/services/json-api-datastore.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ export class JsonApiDatastore {
this.addToStore(model);

if (body.included) {
model.syncRelationships(data, body.included, 0);
model.syncRelationships(data, body.included);
this.addToStore(model);
}

Expand Down Expand Up @@ -309,7 +309,7 @@ export class JsonApiDatastore {
const deserializedModel = model || this.deserializeModel(modelType, body.data);
this.addToStore(deserializedModel);
if (body.included) {
deserializedModel.syncRelationships(body.data, body.included, 0);
deserializedModel.syncRelationships(body.data, body.included);
this.addToStore(deserializedModel);
}

Expand Down
8 changes: 7 additions & 1 deletion test/datastore-with-config.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { JsonApiDatastore, JsonApiDatastoreConfig, DatastoreConfig } from '../sr
import { Author } from './models/author.model';
import { Book } from './models/book.model';
import { Chapter } from './models/chapter.model';
import { Section } from './models/section.model';
import { Paragraph } from './models/paragraph.model';
import { Sentence } from './models/sentence.model';

const BASE_URL = 'http://localhost:8080';
const API_VERSION = 'v1';
Expand All @@ -17,7 +20,10 @@ export const API_VERSION_FROM_CONFIG = 'v2';
models: {
authors: Author,
books: Book,
chapters: Chapter
chapters: Chapter,
paragraphs: Paragraph,
sections: Section,
sentences: Sentence,
}
})
export class DatastoreWithConfig extends JsonApiDatastore {
Expand Down
8 changes: 7 additions & 1 deletion test/datastore.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { JsonApiDatastore, JsonApiDatastoreConfig } from '../src';
import { Author } from './models/author.model';
import { Book } from './models/book.model';
import { Chapter } from './models/chapter.model';
import { Section } from './models/section.model';
import { Paragraph } from './models/paragraph.model';
import { Sentence } from './models/sentence.model';

export const BASE_URL = 'http://localhost:8080';
export const API_VERSION = 'v1';
Expand All @@ -14,7 +17,10 @@ export const API_VERSION = 'v1';
models: {
authors: Author,
books: Book,
chapters: Chapter
chapters: Chapter,
paragraphs: Paragraph,
sections: Section,
sentences: Sentence,
}
})
export class Datastore extends JsonApiDatastore {
Expand Down
Loading