Skip to content

Commit 774affe

Browse files
authored
Merge pull request #120 from ghidoz/feature/small-fixes
Feature/small fixes
2 parents ca285d7 + 5840a7b commit 774affe

File tree

9 files changed

+111
-24
lines changed

9 files changed

+111
-24
lines changed

README.MD

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,12 @@ export class Datastore extends JsonApiDatastore {
418418
* `models` - all the models which will be stored in the datastore
419419
* `baseUrl` - base API URL
420420
* `apiVersion` - optional, a string which will be appended to the baseUrl
421+
* `overrides` - used for overriding internal methods to achive custom functionalities
422+
423+
##### Overrides
424+
425+
* `getDirtyAttributes` - determines which model attributes are dirty
426+
* `toQueryString` - transforms query parameters to a query string
421427

422428

423429
### Model config

src/constants/symbols.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// tslint:disable-next-line:variable-name
2+
export const AttributeMetadata = Symbol('AttributeMetadata');

src/decorators/attribute.decorator.ts

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { format, parse } from 'date-fns';
1+
import { AttributeMetadata } from '../constants/symbols';
22
import { AttributeDecoratorOptions } from '../interfaces/attribute-decorator-options.interface';
33
import { DateConverter } from '../converters/date/date.converter';
44

@@ -30,26 +30,43 @@ export function Attribute(options: AttributeDecoratorOptions = {}): PropertyDeco
3030
return value;
3131
};
3232

33-
const saveAnnotations = function (hasDirtyAttributes: boolean, oldValue: any, newValue: any, isNew: boolean) {
34-
const annotations = Reflect.getMetadata('Attribute', target) || {};
35-
const targetType = Reflect.getMetadata('design:type', target, propertyName);
33+
const saveAnnotations = function () {
34+
const metadata = Reflect.getMetadata('Attribute', target) || {};
35+
36+
metadata[propertyName] = {
37+
marked: true
38+
};
39+
40+
Reflect.defineMetadata('Attribute', metadata, target);
3641

3742
const mappingMetadata = Reflect.getMetadata('AttributeMapping', target) || {};
3843
const serializedPropertyName = options.serializedName !== undefined ? options.serializedName : propertyName;
3944
mappingMetadata[serializedPropertyName] = propertyName;
4045
Reflect.defineMetadata('AttributeMapping', mappingMetadata, target);
46+
};
47+
48+
const setMetadata = function (
49+
hasDirtyAttributes: boolean,
50+
instance: any,
51+
oldValue: any,
52+
newValue: any,
53+
isNew: boolean
54+
) {
55+
const targetType = Reflect.getMetadata('design:type', target, propertyName);
56+
57+
if (!instance[AttributeMetadata]) {
58+
instance[AttributeMetadata] = {};
59+
}
4160

4261
const propertyHasDirtyAttributes = typeof oldValue === 'undefined' && !isNew ? false : hasDirtyAttributes;
4362

44-
annotations[propertyName] = {
63+
instance[AttributeMetadata][propertyName] = {
4564
newValue,
4665
oldValue,
4766
serializedName: options.serializedName,
4867
hasDirtyAttributes: propertyHasDirtyAttributes,
4968
serialisationValue: converter(targetType, newValue, true)
5069
};
51-
52-
Reflect.defineMetadata('Attribute', annotations, target);
5370
};
5471

5572
const getter = function () {
@@ -61,13 +78,13 @@ export function Attribute(options: AttributeDecoratorOptions = {}): PropertyDeco
6178
const convertedValue = converter(targetType, newVal);
6279

6380
if (convertedValue !== this['_' + propertyName]) {
64-
saveAnnotations(true, this['_' + propertyName], newVal, !this.id);
81+
setMetadata(true, this, this['_' + propertyName], newVal, !this.id);
6582
this['_' + propertyName] = convertedValue;
6683
}
6784
};
6885

6986
if (delete target[propertyName]) {
70-
saveAnnotations(false, undefined, target[propertyName], target.id);
87+
saveAnnotations();
7188
Object.defineProperty(target, propertyName, {
7289
get: getter,
7390
set: setter,

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export * from './models/json-api.model';
99
export * from './models/error-response.model';
1010
export * from './models/json-api-query-data';
1111

12+
export * from './interfaces/overrides.interface';
1213
export * from './interfaces/datastore-config.interface';
1314
export * from './interfaces/model-config.interface';
1415
export * from './interfaces/attribute-decorator-options.interface';
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import { Overrides } from './overrides.interface';
2+
13
export interface DatastoreConfig {
24
apiVersion?: string;
35
baseUrl?: string;
4-
models?: Object;
6+
models?: object;
7+
overrides?: Overrides;
58
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export interface Overrides {
2+
getDirtyAttributes?: (attributedMetadata: any) => object;
3+
toQueryString?: (params: any) => string;
4+
}

src/models/json-api.model.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Observable } from 'rxjs/Observable';
55
import { JsonApiDatastore, ModelType } from '../services/json-api-datastore.service';
66
import { ModelConfig } from '../interfaces/model-config.interface';
77
import * as _ from 'lodash';
8+
import { AttributeMetadata } from '../constants/symbols';
89

910
export class JsonApiModel {
1011
id: string;
@@ -26,12 +27,12 @@ export class JsonApiModel {
2627
}
2728

2829
save(params?: any, headers?: Headers): Observable<this> {
29-
const attributesMetadata: any = Reflect.getMetadata('Attribute', this);
30+
const attributesMetadata: any = this[AttributeMetadata];
3031
return this._datastore.saveRecord(attributesMetadata, this, params, headers);
3132
}
3233

3334
get hasDirtyAttributes() {
34-
const attributesMetadata: any = Reflect.getMetadata('Attribute', this);
35+
const attributesMetadata: any = this[AttributeMetadata];
3536
let hasDirtyAttributes = false;
3637
for (const propertyName in attributesMetadata) {
3738
if (attributesMetadata.hasOwnProperty(propertyName)) {
@@ -47,7 +48,7 @@ export class JsonApiModel {
4748
}
4849

4950
rollbackAttributes(): void {
50-
const attributesMetadata: any = Reflect.getMetadata('Attribute', this);
51+
const attributesMetadata: any = this[AttributeMetadata];
5152
let metadata: any;
5253
for (const propertyName in attributesMetadata) {
5354
if (attributesMetadata.hasOwnProperty(propertyName)) {
@@ -62,7 +63,8 @@ export class JsonApiModel {
6263
}
6364
}
6465
}
65-
Reflect.defineMetadata('Attribute', attributesMetadata, this);
66+
67+
this[AttributeMetadata] = attributesMetadata;
6668
}
6769

6870
get modelConfig(): ModelConfig {

src/services/json-api-datastore.service.spec.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,24 @@ describe('JsonApiDatastore', () => {
256256
});
257257
datastore.query(Book, { arrayParam: [4, 5, 6] }).subscribe();
258258
});
259+
260+
it('should generate correct query string for nested params with findAll', () => {
261+
backend.connections.subscribe((c: MockConnection) => {
262+
const decodedQueryString = decodeURI(c.request.url).split('?')[1];
263+
const expectedQueryString = 'filter[text]=test123';
264+
expect(decodedQueryString).toEqual(expectedQueryString);
265+
});
266+
datastore.findAll(Book, { filter: { text: 'test123' } }).subscribe();
267+
});
268+
269+
it('should generate correct query string for nested array params with findAll', () => {
270+
backend.connections.subscribe((c: MockConnection) => {
271+
const decodedQueryString = decodeURI(c.request.url).split('?')[1];
272+
const expectedQueryString = 'filter[text][]=1&filter[text][]=2';
273+
expect(decodedQueryString).toEqual(expectedQueryString);
274+
});
275+
datastore.findAll(Book, { filter: { text: [1, 2] } }).subscribe();
276+
});
259277
});
260278

261279
describe('findRecord', () => {
@@ -364,14 +382,35 @@ describe('JsonApiDatastore', () => {
364382
expect(obj.id).toBeUndefined();
365383
expect(obj.type).toBe('authors');
366384
expect(obj.relationships).toBeDefined();
385+
expect(obj.relationships.books.data.length).toBe(0);
386+
});
387+
388+
const author = datastore.createRecord(Author, {
389+
name: AUTHOR_NAME
390+
});
391+
392+
author.books = [datastore.createRecord(Book, {
393+
title: BOOK_TITLE
394+
})];
395+
396+
author.save().subscribe();
397+
});
398+
399+
it('should create new author with new ToMany-relationship 2', () => {
400+
backend.connections.subscribe((c: MockConnection) => {
401+
const obj = c.request.json().data;
402+
expect(obj.id).toBeUndefined();
403+
expect(obj.relationships).toBeDefined();
367404
expect(obj.relationships.books.data.length).toBe(1);
368-
expect(obj.relationships.books.data[0].attributes.title).toBe(BOOK_TITLE);
369405
});
370406
const author = datastore.createRecord(Author, {
371407
name: AUTHOR_NAME
372408
});
373409
author.books = [datastore.createRecord(Book, {
410+
id: 123,
374411
title: BOOK_TITLE
412+
}), datastore.createRecord(Book, {
413+
title: `New book - ${BOOK_TITLE}`
375414
})];
376415
author.save().subscribe();
377416
});

src/services/json-api-datastore.service.ts

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { JsonApiQueryData } from '../models/json-api-query-data';
1313
import * as qs from 'qs';
1414
import { DatastoreConfig } from '../interfaces/datastore-config.interface';
1515
import { ModelConfig } from '../interfaces/model-config.interface';
16+
import { AttributeMetadata } from '../constants/symbols';
1617

1718
export type ModelType<T extends JsonApiModel> = { new(datastore: JsonApiDatastore, data: any): T; };
1819

@@ -21,7 +22,11 @@ export class JsonApiDatastore {
2122
// tslint:disable:variable-name
2223
private _headers: Headers;
2324
private _store: {[type: string]: {[id: string]: JsonApiModel}} = {};
24-
25+
// tslint:disable:max-line-length
26+
private getDirtyAttributes: Function = this.datastoreConfig.overrides && this.datastoreConfig.overrides.getDirtyAttributes ? this.datastoreConfig.overrides.getDirtyAttributes : this._getDirtyAttributes;
27+
private toQueryString: Function = this.datastoreConfig.overrides && this.datastoreConfig.overrides.toQueryString ? this.datastoreConfig.overrides.toQueryString : this._toQueryString;
28+
// tslint:enable:max-line-length
29+
2530
protected config: DatastoreConfig;
2631

2732
constructor(private http: Http) {}
@@ -73,7 +78,7 @@ export class JsonApiDatastore {
7378
return new modelType(this, { attributes: data });
7479
}
7580

76-
private getDirtyAttributes(attributesMetadata: any): { string: any} {
81+
private _getDirtyAttributes(attributesMetadata: any): { string: any} {
7782
const dirtyData: any = {};
7883

7984
for (const propertyName in attributesMetadata) {
@@ -189,13 +194,21 @@ export class JsonApiDatastore {
189194
if (data.hasOwnProperty(key)) {
190195
if (data[key] instanceof JsonApiModel) {
191196
relationships = relationships || {};
192-
relationships[key] = {
193-
data: this.buildSingleRelationshipData(data[key])
194-
};
197+
198+
if (data[key].id) {
199+
relationships[key] = {
200+
data: this.buildSingleRelationshipData(data[key])
201+
};
202+
}
195203
} else if (data[key] instanceof Array && data[key].length > 0 && this.isValidToManyRelation(data[key])) {
196204
relationships = relationships || {};
205+
206+
const relationshipData = data[key]
207+
.filter((model: JsonApiModel) => model.id)
208+
.map((model: JsonApiModel) => this.buildSingleRelationshipData(model));
209+
197210
relationships[key] = {
198-
data: data[key].map((model: JsonApiModel) => this.buildSingleRelationshipData(model))
211+
data: relationshipData
199212
};
200213
}
201214
}
@@ -330,7 +343,7 @@ export class JsonApiDatastore {
330343
return new RequestOptions({ headers: requestHeaders });
331344
}
332345

333-
private toQueryString(params: any) {
346+
private _toQueryString(params: any): string {
334347
return qs.stringify(params, { arrayFormat: 'brackets' });
335348
}
336349

@@ -351,7 +364,7 @@ export class JsonApiDatastore {
351364
private resetMetadataAttributes<T extends JsonApiModel>(res: T, attributesMetadata: any, modelType: ModelType<T>) {
352365
// TODO check why is attributesMetadata from the arguments never used
353366
// tslint:disable-next-line:no-param-reassign
354-
attributesMetadata = Reflect.getMetadata('Attribute', res);
367+
attributesMetadata = res[AttributeMetadata];
355368

356369
for (const propertyName in attributesMetadata) {
357370
if (attributesMetadata.hasOwnProperty(propertyName)) {
@@ -363,7 +376,7 @@ export class JsonApiDatastore {
363376
}
364377
}
365378

366-
Reflect.defineMetadata('Attribute', attributesMetadata, res);
379+
res[AttributeMetadata] = attributesMetadata;
367380
return res;
368381
}
369382

0 commit comments

Comments
 (0)