Skip to content

Instantly share code, notes, and snippets.

@softmonkeyjapan
Last active July 6, 2016 00:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save softmonkeyjapan/c250c5c6192b4fe03bc6 to your computer and use it in GitHub Desktop.
Save softmonkeyjapan/c250c5c6192b4fe03bc6 to your computer and use it in GitHub Desktop.
(function () {
'use strict';
angular
.module('lk-active-resource', [
'ngResource',
'platanus.inflector'
])
.provider('ARSettings', function () {
this.apiUrl = undefined;
this.$get = function () {
return {
apiUrl: this.apiUrl,
};
};
this.configure = function (config) {
for (var key in config) {
this[key] = config[key];
}
};
})
.factory('ActiveResource', function ($resource, ARSettings, inflector, $injector) {
/**
* Check whether an object is a number.
*
* @param {Object} object - Object to check numericallity on.
* @return {Boolean} True if number, false otherwise.
*/
var isNumeric = function (object) {
return !isNaN(parseFloat(object)) && isFinite(object);
};
/**
* Generate options based on arguments passed.
* If the object passed is :
* - An object : Use it directly.
* - A string : Inflect it and use the default config.
* - Something else : Throw an error.
*
* @param {Object} args - Javascript object or string as the name of the resource (singular).
* @return {Object} Options to pass to $resource.
*/
var sanitizeOptions = function (args) {
if (args !== null && typeof args === 'string') {
var _resName = inflector.pluralize(args);
return {
url: '/' + _resName + '/:id/:action',
params: { id: '@id' },
namespace: args
};
} else if (args !== null && typeof args === 'object') {
return args;
} else {
throw new Error(args + ' is not a valid options');
}
};
/**
* ActiveResource core definition.
*/
var Resource = function (options) {
options = sanitizeOptions(options);
options.params = options.params || {};
options.methods = options.methods || {};
/**
* Transform data before querying the server.
* In the case of Rails, will wrap the data with a resource namespace.
*
* @param {Object} data - Data to send.
* @return {String} Stringify data.
*/
var transformRequest = function (data) {
if (!options.namespace) {
return JSON.stringify(data);
}
var datas = {};
datas[options.namespace] = data;
return JSON.stringify(datas);
};
/**
* Transform data after querying the server.
* If the response contains an object (instead of a query) with the resource namespace in plural :
*
* new ActiveResource('user') => Check for the key users
*
* then attach to each object the Resource object. This is a particular case
* mostly used in pagination scenario.
*
* @param {Object} data - Data to send.
* @return {String} Stringify data.
*/
var transformResponse = function (data) {
data = JSON.parse(data);
if (options.namespace) {
var namespace = inflector.pluralize(options.namespace);
if (data[namespace]) {
var ClassObject = $injector.get(
inflector.camelize(inflector.singularize(namespace), true)
);
angular.forEach(data[namespace], function (object, index) {
var instance = new ClassObject();
data[namespace][index] = angular.extend(instance, object);
});
}
}
return data;
};
var defaults = {
browse: { method: 'GET', transformResponse: transformResponse },
query: { method: 'GET', transformResponse: transformResponse, isArray: true },
get: { method: 'GET', transformResponse: transformResponse },
create: { method: 'POST', transformRequest: transformRequest },
update: { method: 'PATCH', transformRequest: transformRequest },
destroy: { method: 'DELETE' }
};
angular.extend(defaults, options.methods);
var resource = $resource(ARSettings.apiUrl + options.url, options.params, defaults);
/**
* Get an entire collection of objects.
*
* @param {Object} args - $resource.query arguments.
* @return {Promise} Promise
*/
resource.all = function (args) {
var options = args || {};
return this.query(options);
};
/**
* Get an entire collection of objects.
* Since a search is often returning pagination type of data,
* the collection of object will be wrapped under a key within that response.
* See transformResponse for more information about that case.
*
* @param {Object} args - $resource.query arguments.
* @return {Promise} Promise
*/
resource.search = function (args) {
var options = args || {};
return this.browse(options);
};
/**
* Find a specific object.
*
* @param {Object|Integer} args - $resource.get arguments, or { id: args } if numeric.
* @param {Function} callback - $resource.get callback function if any.
* @return {Promise} Promise
*/
resource.find = function (args, callback) {
var options = isNumeric(args) ? { id: args } : args;
return this.get(options, callback);
};
/**
* Mixin custom methods to instance.
*
* @param {Object} args - Set of properties to mixin the $resource object.
* @return {this} this. Chainable.
*/
resource.instanceMethods = function (args) {
angular.extend(this.prototype, args);
return this;
};
/**
* $resource's $save method override.
* Allow to use $save in order to create or update a resource based on it's id.
*
* @return {Promise} Promise
*/
resource.prototype.save = function () {
var action = this.id ? '$update' : '$create';
return this[action]();
};
/**
* Delete instance object.
*
* @return {Promise} Promise
*/
resource.prototype.delete = function () {
if (!this.id) {
throw new Error('Object must have an id to be deleted.');
}
var options = { id: this.id };
return this.$destroy(options);
};
return resource;
};
return Resource;
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment