'use strict';
var request = require('superagent');
var Route = require('./Route');
var Promise = require('es6-promise').Promise;
var Collection = require('./Collection');
/**
* Resource Factory
*
* @example
* var MyResource = new Resource({url: '/myresources'});
* // MyResource now works with the /myresources endpoint
* MyResource.query();
*
* @param {Object} options
* @param {String} options.url root url for the resource collection
* @param {Array} options.idParams params that represent the resource id
*/
function ResourceFactory(options) {
if (typeof options === 'string') {
options = {url: options};
}
var route = new Route(options.url);
/**
* Resource
*
* Abstract class that provides common behavior and serialization for resources.
*
* @class
* @param {Object} attrs attributes
*/
function Resource(attrs) {
for (var key in attrs) {
this[key] = attrs[key];
}
}
Resource.Collection = new Collection({resource: Resource});
if (!options.idParams) {
options.idParams = ['id'];
}
/**
* Extract resource id from an object.
*
* By default the returns the `id` property.
* Override to provide a different value.
*/
Resource.getId = function(params) {
for (var i = 0; i < options.idParams.length; i++) {
if (params[options.idParams[i]]) {
return params[options.idParams[i]];
}
}
};
/**
* Delete id(s) from the params.
*
* By default removes the `id` property.
*/
Resource.cleanId = function(params) {
var cleaned = [];
Object.keys(params).forEach(function(key) {
if (options.idParams.indexOf(key) === -1) {
cleaned[key] = params[key];
}
});
return cleaned;
};
/**
* Fetch collection.
*
* @example
* var collection = Resource.query();
* collection.then(function(data) {
* // data are now fetched
* });
*
* @param {Object} params query params
* @return {Collection} collection and its promise
*/
Resource.query = function(params) {
var collection = new Resource.Collection();
var promise = new Promise(function(resolve, reject) {
request
.get(route.url(params))
.query(route.query(params))
.accept('json')
.end(function(response) {
if (response.error) {
var error = (response.body || JSON.parse(response.text)).error || response.error;
reject({error: error});
} else {
var body = response.body || JSON.parse(response.text);
collection.length = 0;
var newCollection = new Resource.Collection();
body.data.forEach(function(data) {
route.params.forEach(function(param) {
data[param] = params[param];
});
newCollection.push(new Resource(data));
});
collection.push.apply(collection, newCollection);
if (body.pagination && body.pagination.next_page) {
collection.next_page = body.pagination.next_page;
newCollection.next_page = body.pagination.next_page;
}
if (body.pagination && body.pagination.previous_page) {
collection.prev_page = body.pagination.previous_page;
newCollection.prev_page = body.pagination.previous_page;
}
resolve(newCollection);
}
}.bind(this));
}.bind(this));
collection.then = function() { promise.then.apply(promise, arguments); };
collection['catch'] = function() { promise['catch'].apply(promise, arguments); };
return collection;
};
/**
* @alias query
*/
Resource.where = Resource.query;
/**
* Fetch resource.
*
* @param {Object} params parameters for fetch
* @return {Promise} resource object that will be eventually filled with data
*/
Resource.get = function(params) {
var id = Resource.getId(params);
params = Resource.cleanId(params);
return new Promise(function(resolve, reject) {
request
.get(route.url(params) + '/' + id)
.query(route.query(params))
.accept('json')
.end(function(response) {
if (response.error) {
var error = (response.body || JSON.parse(response.text)).error || response.error;
reject({error: error});
} else {
var body = response.body || JSON.parse(response.text);
route.params.forEach(function(param) {
body.data[param] = params[param];
});
resolve(new Resource(body.data));
}
}.bind(this));
}.bind(this));
};
/**
* @alias get
*/
Resource.find = Resource.get;
/**
* Create a resource.
*
* @param {Object} params resource data
* @return {Promise} promise
*/
Resource.create = function(params) {
return new Promise(function(resolve, reject) {
request
.post(route.url(params))
.send(route.body(params))
.accept('json')
.end(function(response) {
if (response.error) {
var error = (response.body || JSON.parse(response.text)).error || response.error;
reject({error: error});
} else {
var body = response.body || JSON.parse(response.text);
resolve(new Resource(body.data));
}
}.bind(this));
}.bind(this));
};
/**
* Update resource.
*
* @param {Object} params
* @return {Promise} promise
*/
Resource.update = function(params) {
var id = Resource.getId(params);
params = Resource.cleanId(params);
return new Promise(function(resolve, reject) {
request
.put(route.url(params) + '/' + id)
.send(route.body(params))
.accept('json')
.end(function(response) {
if (response.error) {
var error = (response.body || JSON.parse(response.text)).error || response.error;
reject({error: error});
} else {
var body = response.body || JSON.parse(response.text);
resolve(new Resource(body.data));
}
}.bind(this));
}.bind(this));
};
/**
* Url of the resource instance.
*
* @return {String} resource url
*/
Resource.prototype.url = function() {
var id = Resource.getId(this);
if (!id) {
throw new Error('Cannot retrieve URL of a resource that is not saved yet and does not have an ID.');
}
var params = {};
route.params.forEach(function(param) {
params[param] = this[param];
}.bind(this));
return route.url(params) + '/' + id;
};
/**
* Save resource.
*
* @return {Promise} promise
*/
Resource.prototype.save = function() {
var id = Resource.getId();
if (id) {
return Resource.update(this);
} else {
return Resource.create(this);
}
};
/**
* Delete a resource.
*
* @return {Promise} promise
*/
Resource.prototype['delete'] = function() {
return new Promise(function(resolve, reject) {
request
.del(route.url(this))
.accept('json')
.end(function(response) {
if (response.error) {
var error = (response.body || JSON.parse(response.text)).error || response.error;
reject({error: error});
} else {
resolve();
}
}.bind(this));
}.bind(this));
};
/**
* @alias delete
*/
Resource.prototype.del = Resource.prototype['delete'];
/**
* @alias delete
*/
Resource.prototype.remove = Resource.prototype['delete'];
return Resource;
}
module.exports = ResourceFactory;