Skip to content

Instantly share code, notes, and snippets.

@syul
Created July 26, 2016 11:57
Show Gist options
  • Save syul/6d24c7a748835203074cb9536cb97fae to your computer and use it in GitHub Desktop.
Save syul/6d24c7a748835203074cb9536cb97fae to your computer and use it in GitHub Desktop.
import {Inject} from '../decorators/inject';
import {Factory} from '../decorators/factory';
export interface IModelFactory {
attributes: any;
id: string;
getRelation(relation?: string): ng.IPromise<any>;
update(): ng.IPromise<any>;
delete(model: IModelFactory): ng.IPromise<any>;
add(model?: IModelFactory): ng.IPromise<any>;
};
export interface IModelFactoryConstructor {
new (modelData?: Object | string): IModelFactory;
};
export interface IRelation {
name: string;
url: string;
};
export interface ILink {
name: string;
url: string;
};
export interface IModel {
relations: IRelation[];
links: ILink[];
type: string;
attributes: any;
parent: string;
self: string;
id: string;
};
interface IModelData extends Object {
data?: Object | Object[];
links?: Object[];
}
/**
*
*
* @export
* @class ModelFactory
* @implements {IModelFactory}
*/
@Factory()
export class ModelFactory implements IModelFactory {
@Inject
private $timeout: ng.ITimeoutService;
@Inject
private $q: ng.IQService;
@Inject
private $http: ng.IHttpService;
@Inject
private $log: ng.ILogService;
@Inject
private $rootScope: ng.IRootScopeService;
private type: string = '';
public id: string = '';
private parent: string = '';
private self: string = '';
private links: ILink[] = [];
public attributes: any = {};
private relations: IRelation[] = [];
/**
* Creates an instance of ModelFactory.
*
* @param {(Object | string)} [modelData]
*/
constructor(modelData?: Object | string) {
if (angular.isObject(modelData)) {
let result: IModel = this.parseModel(angular.copy(modelData));
this.relations = result.relations;
this.type = result.type;
this.attributes = result.attributes;
this.parent = result.parent;
this.self = result.self;
this.id = result.id;
this.links = result.links;
} else if (angular.isString(modelData)) {
this.self = modelData as string;
}
};
/**
* Get relations from the raw model data
*
* @private
* @param {*} modelData
* @returns {IRelation[]}
*/
private getRelations(modelData: any): IRelation[] {
let relationships: IRelation[] = [];
angular.forEach(modelData.data.relationships, (value, key) => {
relationships.push({
name: key,
url: value.links.related
});
});
return relationships;
};
/**
* Get links from the raw model data
*
* @private
* @param {*} modelData
* @returns {ILink[]}
*/
private getLinks(modelData: any): ILink[] {
let links: ILink[] = [];
angular.forEach(modelData.links, (value, key) => {
links.push({
name: key,
url: value
});
});
return links;
}
/**
* Parces raw model data and fills out properties of the current instance
*
* @private
* @param {*} modelData
* @returns {IModel}
*/
private parseModel(modelData: any): IModel {
return {
relations: this.getRelations(modelData),
links: this.getLinks(modelData),
type: modelData.data.type,
attributes: modelData.data.attributes,
parent: modelData.links.self,
self: modelData.data.links.self,
id: modelData.data.id
};
};
/**
* Gets model method url by given method name
*
* @param {string} linkName
* @returns {string}
*/
public getLink(linkName: string): string {
let filtered: ILink[] = this.links.filter((el: ILink) => {
return el.name === linkName;
});
if (filtered.length) {
return filtered[0].url;
} else {
return undefined;
}
};
/**
* Every model has link property that represents all methods linked with this model.
* Current method invokes required method by given method name
*
* @param {string} methodName
* @param {(string | Object)} param1
* @param {Object} [param2]
* @returns {ng.IPromise<any>}
*/
public invokeMethod(methodName: string, param1: string | Object, param2?: Object): ng.IPromise<any> {
let deferred: ng.IDeferred<any> = this.$q.defer();
let methodType: string = 'GET';
let methodParameters: Object = {};
//define the method type and method parameters regarding to passed arguments
if (param2) {
methodParameters = angular.isObject(param2) ? param2 : methodParameters;
methodType = angular.isString(param1) ? param1 as string : methodType;
} else if (param1) {
methodParameters = angular.isObject(param1) ? param1 : methodParameters;
methodType = angular.isString(param1) ? param1 as string : methodType;
};
if (methodName) {
// find the required url by the given method name
let methodUrl: string = '';
this.links.forEach((element: ILink) => {
if (element.name === methodName) {
methodUrl = element.url;
};
});
// if method was not found reject promise with error message otherwice procced http request
// according to passed parameters and method type
if (!methodUrl) {
deferred.reject({ message: `Could not find method with name ${methodName}` });
} else {
this.$http({
method: methodType,
url: methodUrl,
data: methodType != 'GET' ? methodParameters : {},
params: methodType === 'GET' ? methodParameters : {}
}).then((result) => {
deferred.resolve(result.data);
}).catch((err) => {
deferred.reject(err);
});
}
} else {
deferred.reject({ message: "Has not passed the method name parameter" });
}
return deferred.promise;
};
/**
* Create a single model according to the given raw model data
*
* @private
* @param {Object} modelData
* @returns {IModelFactory}
*/
private createSingleModel(modelData: IModelData): IModelFactory {
let modelFactoryConstructor: IModelFactoryConstructor = (ModelFactory as Function)();
return new modelFactoryConstructor(modelData);
};
/**
* Create a collection of the models according to the passed array
* of raw models data
*
* @private
* @param {Object[]} collectionData
* @returns {IModelFactory[]}
*/
private createCollection(collectionData: IModelData): IModelFactory[] {
let modelFactoryConstructor: IModelFactoryConstructor = (ModelFactory as Function)();
return (collectionData.data as Object[]).map((el: Object) => {
let modelData: IModelData = {
data: el,
links: collectionData.links
}
return new modelFactoryConstructor(modelData);
}) as IModelFactory[];
};
/**
* According to the api realization there are some ways to get the model instance.
* The first way is to get the model by the relation name and the second way to
* build the model using direct url passed to the constructor.
*
* @param {(string | Object)} [param1] - relation name or parameters to get model
* using direct url
* @param {Object} [param2] - parameters to get model using relation name
* @returns {ng.IPromise<any>}
*/
public getRelation(param1?: string | Object, param2?: Object): ng.IPromise<any> {
let deferred: ng.IDeferred<any> = this.$q.defer();
//target url to get the data - it may be url related to current model
//as well as the url of the related to current model resource if the name
//of the resource is passed. By default we are working with current model
let targetUrl: string = this.self;
//parameters for http request by default is using empty object we do
//not have any additional parameters by default
let requestParameters: Object = {};
//the name of the resource related to current model. By default it is
//an ampty string consider that we are working with current model
let relationName: string = '';
//first parameter is an object in this case consider it as a filter for http request
if (angular.isObject(param1)) {
requestParameters = param1;
//first parameter is a string in this case consider it as a name of the related resource
} else if (angular.isString(param1)) {
relationName = param1 as string;
requestParameters = param2 ? param2 : undefined;
let filtered: IRelation[] = this.relations.filter((el): boolean => {
return el.name == relationName;
});
if (filtered.length > 1) {
this.$log.warn(`Model ${this.self} has more than one relation with name ${relationName} the only first relation will be procceded.`);
}
let currentRelation: IRelation = filtered.length ? filtered[0] : undefined;
if (!currentRelation) {
throw new Error(`Could not find relation with name ${relationName}`);
}
targetUrl = currentRelation.url;
}
//make a request to get the model data according to passed parameters
this.$http.get(targetUrl, { params: requestParameters }).then((response) => {
let modelData: IModelData = response.data;
//create collection
if (angular.isArray(modelData.data)) {
deferred.resolve(this.createCollection(modelData));
} else if (angular.isObject(modelData.data)) {
deferred.resolve(this.createSingleModel(modelData));
} else {
throw new Error(`Incorrect structure of the model data of relation ${relationName}`);
}
}).catch((err) => {
deferred.reject(err);
});
//return promise
return deferred.promise;
};
/**
* Gets named relation by it's id
*
* @param {string} relationName
* @param {string} relationId
* @returns {ng.IPromise<any>}
*/
public getRelationById(relationName: string, relationId: string): ng.IPromise<any> {
let deferred: ng.IDeferred<any> = this.$q.defer();
let filtered: IRelation[] = this.relations.filter((el): boolean => {
return el.name == relationName;
});
if (filtered.length > 1) {
this.$log.warn(`Model ${this.self} has more than one relation with name ${relationName} the only first relation will be procceded.`);
}
let currentRelation: IRelation = filtered.length ? filtered[0] : undefined;
if (!currentRelation) {
throw new Error(`Could not find relation with name ${relationName}`);
}
let targetUrl: string = currentRelation.url + `/${relationId}`;
this.$http.get(targetUrl).then((response) => {
let modelData: IModelData = response.data;
//create collection
if (angular.isArray(modelData.data)) {
deferred.resolve(this.createCollection(modelData));
} else if (angular.isObject(modelData.data)) {
console.log(`http request ${this.$rootScope.$$phase}`);
deferred.resolve(this.createSingleModel(modelData));
} else {
throw new Error(`Incorrect structure of the model data of relation ${relationName}`);
}
}).catch((err) => {
deferred.reject(err);
});
//return promise
return deferred.promise;
}
public serialize(): any {
return {
data: {
type: this.type,
id: this.id,
attributes: this.attributes
}
};
};
/**
* Updates the current model
*
* @returns {ng.IPromise<any>}
*/
public update(): ng.IPromise<any> {
let deferred = this.$q.defer();
this.$http.patch(this.self, this.serialize()).then((result: any) => {
deferred.resolve(result);
}).catch((err: any) => {
deferred.reject(err);
});
return deferred.promise;
};
/**
* Adds new element to the model collection
* Is not implemented yet
*
* @param {IModelFactory} model
* @returns {ng.IPromise<any>}
*/
public add(model: IModelFactory): ng.IPromise<any> {
let deferred = this.$q.defer();
deferred.resolve();
return deferred.promise;
};
/**
* Removes current element from the collection
* Is not implemented yet
*
* @param {IModelFactory} [model]
* @returns {ng.IPromise<any>}
*/
public delete(model?: IModelFactory): ng.IPromise<any> {
let deferred = this.$q.defer();
deferred.resolve();
return deferred.promise;
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment