PK YXFCJv v angularjs-rails-resource.js/** * A resource factory inspired by $resource from AngularJS * @version v1.0.0-pre1 - 2013-10-06 * @link https://github.com/FineLinePrototyping/angularjs-rails-resource.git * @author */ (function (undefined) { angular.module('rails', ['ng']); }()); (function (undefined) { angular.module('rails').factory('RailsInflector', function() { function camelize(key) { if (!angular.isString(key)) { return key; } // should this match more than word and digit characters? return key.replace(/_[\w\d]/g, function (match, index, string) { return index === 0 ? match : string.charAt(index + 1).toUpperCase(); }); } function underscore(key) { if (!angular.isString(key)) { return key; } // TODO match the latest logic from Active Support return key.replace(/[A-Z]/g, function (match, index) { return index === 0 ? match : '_' + match.toLowerCase(); }); } function pluralize(value) { // TODO match Active Support return value + 's'; } return { camelize: camelize, underscore: underscore, pluralize: pluralize } }); }()); (function (undefined) { angular.module('rails').factory('RailsResourceInjector', ['$injector', function($injector) { /** * Allow dependencies to be referenced by name or instance. If referenced by name AngularJS $injector * is used to retrieve the dependency. * * @param dependency (string | function) The dependency to retrieve * @returns {*} The dependency */ function getDependency(dependency) { if (dependency) { return angular.isString(dependency) ? $injector.get(dependency) : dependency } return undefined; } /** * Looks up and instantiates an instance of the requested service. If the service is not a string then it is * assumed to be a constuctor function. * * @param service (string | function) The service to instantiate * @returns {*} A new instance of the requested service */ function createService(service) { if (service) { return $injector.instantiate(getDependency(service)); } return undefined; } return { createService: createService, getDependency: getDependency } }]); }()); /** * @ngdoc function * @name rails.railsUrlBuilder * @function * @requires $interpolate * * @description * * Compiles a URL template string into an interpolation function using $interpolate. If no interpolation bindings * found then {{id}} is appended to the url string. *
expect(railsUrlBuilder('/books')()).toEqual('/books') expect(railsUrlBuilder('/books')({id: 1})).toEqual('/books/1') expect(railsUrlBuilder('/authors/{{authorId}}/books/{{id}}')({id: 1, authorId: 2})).toEqual('/authors/2/books/1')* * If the $interpolate startSymbol and endSymbol have been customized those values should be used instead of {{ and }} * * @param {string|function} url If the url is a function then that function is returned. Otherwise the url string * is passed to $interpolate as an expression. * * @returns {function(context)} As stated by $interpolate documentation: * An interpolation function which is used to compute the interpolated * string. The function has these parameters: * * * `context`: an object against which any expressions embedded in the strings are evaluated * against. * */ (function (undefined) { angular.module('rails').factory('railsUrlBuilder', ['$interpolate', function($interpolate) { return function (url) { var expression; if (angular.isFunction(url) || angular.isUndefined(url)) { return url; } if (url.indexOf($interpolate.startSymbol()) === -1) { url = url + '/' + $interpolate.startSymbol() + 'id' + $interpolate.endSymbol(); } expression = $interpolate(url); return function (params) { url = expression(params); if (url.charAt(url.length - 1) === '/') { url = url.substr(0, url.length - 1); } return url; }; }; }]) }()); (function (undefined) { angular.module('rails').provider('railsSerializer', function() { var defaultOptions = { underscore: undefined, camelize: undefined, pluralize: undefined, exclusionMatchers: [] }; /** * Configures the underscore method used by the serializer. If not defined then
RailsInflector.underscore
* will be used.
*
* @param {function(string):string} fn The function to use for underscore conversion
* @returns {railsSerializerProvider} The provider for chaining
*/
this.underscore = function(fn) {
defaultOptions.underscore = fn;
return this;
};
/**
* Configures the camelize method used by the serializer. If not defined then RailsInflector.camelize
* will be used.
*
* @param {function(string):string} fn The function to use for camelize conversion
* @returns {railsSerializerProvider} The provider for chaining
*/
this.camelize = function(fn) {
defaultOptions.camelize = fn;
return this;
};
/**
* Configures the pluralize method used by the serializer. If not defined then RailsInflector.pluralize
* will be used.
*
* @param {function(string):string} fn The function to use for pluralizing strings.
* @returns {railsSerializerProvider} The provider for chaining
*/
this.pluralize = function(fn) {
defaultOptions.pluralize = fn;
return this;
};
/**
* Configures the array exclusion matchers by the serializer. Exclusion matchers can be one of the following:
* * string - Defines a prefix that is used to test for exclusion
* * RegExp - A custom regular expression that is tested against the attribute name
* * function - A custom function that accepts a string argument and returns a boolean with true indicating exclusion.
*
* @param {Array.{attributeName}_attributes
to work with the Rails nested_attributes feature.
* @param attributeNames... {string} Variable number of attribute name parameters
* @returns {Serializer} this for chaining support
*/
Serializer.prototype.nestedAttribute = function () {
var self = this;
angular.forEach(arguments, function (attributeName) {
self.rename(attributeName, attributeName + '_attributes');
});
return this;
};
/**
* Specifies an attribute that is a nested resource within the parent object.
* Nested resources do not imply nested attributes, if you want both you still have to specify call nestedAttribute
as well.
*
* A nested resource serves two purposes. First, it defines the resource that should be used when constructing resources from the server.
* Second, it specifies how the nested object should be serialized.
*
* An optional third parameter serializer
is available to override the serialization logic
* of the resource in case you need to serialize it differently in multiple contexts.
*
* @param attributeName {string} The name of the attribute that is a nested resource
* @param resource {string | Resource} A reference to the resource that the attribute is a type of.
* @param serializer {string | Serializer} (optional) An optional serializer reference to override the nested resource's default serializer
* @returns {Serializer} this for chaining support
*/
Serializer.prototype.resource = function (attributeName, resource, serializer) {
this.nestedResources[attributeName] = resource;
if (serializer) {
this.serializeWith(attributeName, serializer);
}
return this;
};
/**
* Specifies a custom name mapping for an attribute.
* On serializing to JSON the jsonName will be used.
* On deserialization, if jsonName is seen then it will be renamed as javascriptName in the resulting resource.
*
* @param javascriptName {string} The attribute name as it appears in the JavaScript object
* @param jsonName {string} The attribute name as it should appear in JSON
* @param bidirectional {boolean} (optional) Allows turning off the bidirectional renaming, defaults to true.
* @returns {Serializer} this for chaining support
*/
Serializer.prototype.rename = function (javascriptName, jsonName, bidirectional) {
this.serializeMappings[javascriptName] = jsonName;
if (bidirectional || bidirectional === undefined) {
this.deserializeMappings[jsonName] = javascriptName;
}
return this;
};
/**
* Allows custom attribute creation as part of the serialization to JSON.
*
* @param attributeName {string} The name of the attribute to add
* @param value {*} The value to add, if specified as a function then the function will be called during serialization
* and should return the value to add.
* @returns {Serializer} this for chaining support
*/
Serializer.prototype.add = function (attributeName, value) {
this.customSerializedAttributes[attributeName] = value;
return this;
};
/**
* Allows the attribute to be preserved unmodified in the resulting object.
*
* @param attributeName {string} The name of the attribute to add
* @returns {Serializer} this for chaining support
*/
Serializer.prototype.preserve = function(attributeName) {
this.preservedAttributes[attributeName] = true;
return this;
};
/**
* Specify a custom serializer to use for an attribute.
*
* @param attributeName {string} The name of the attribute
* @param serializer {string | function} A reference to the custom serializer to use for the attribute.
* @returns {Serializer} this for chaining support
*/
Serializer.prototype.serializeWith = function (attributeName, serializer) {
this.customSerializers[attributeName] = serializer;
return this;
};
/**
* Determines whether or not an attribute should be excluded.
*
* If the option excludeByDefault has been set then attributes will default to excluded and will only
* be included if they have been included using the "only" customization function.
*
* If the option excludeByDefault has not been set then attributes must be explicitly excluded using the "exclude"
* customization function or must be matched by one of the exclusionMatchers.
*
* @param attributeName The name of the attribute to check for exclusion
* @returns {boolean} true if excluded, false otherwise
*/
Serializer.prototype.isExcludedFromSerialization = function (attributeName) {
if ((this.options.excludeByDefault && !this.inclusions.hasOwnProperty(attributeName)) || this.exclusions.hasOwnProperty(attributeName)) {
return true;
}
if (this.options.exclusionMatchers) {
var excluded = false;
angular.forEach(this.options.exclusionMatchers, function (matcher) {
if (angular.isString(matcher)) {
excluded = excluded || attributeName.indexOf(matcher) === 0;
} else if (angular.isFunction(matcher)) {
excluded = excluded || matcher.call(undefined, attributeName);
} else if (matcher instanceof RegExp) {
excluded = excluded || matcher.test(attributeName);
}
});
return excluded;
}
return false;
};
/**
* Remaps the attribute name to the serialized form which includes:
* - checking for exclusion
* - remapping to a custom value specified by the rename customization function
* - underscoring the name
*
* @param attributeName The current attribute name
* @returns {*} undefined if the attribute should be excluded or the mapped attribute name
*/
Serializer.prototype.getSerializedAttributeName = function (attributeName) {
var mappedName = this.serializeMappings[attributeName] || attributeName;
if (this.isExcludedFromSerialization(attributeName) || this.isExcludedFromSerialization(mappedName)) {
return undefined;
}
return this.underscore(mappedName);
};
/**
* Determines whether or not an attribute should be excluded from deserialization.
*
* By default, we do not exclude any attributes from deserialization.
*
* @param attributeName The name of the attribute to check for exclusion
* @returns {boolean} true if excluded, false otherwise
*/
Serializer.prototype.isExcludedFromDeserialization = function (attributeName) {
return false;
};
/**
* Remaps the attribute name to the deserialized form which includes:
* - camelizing the name
* - checking for exclusion
* - remapping to a custom value specified by the rename customization function
*
* @param attributeName The current attribute name
* @returns {*} undefined if the attribute should be excluded or the mapped attribute name
*/
Serializer.prototype.getDeserializedAttributeName = function (attributeName) {
var camelizedName = this.camelize(attributeName);
camelizedName = this.deserializeMappings[attributeName]
|| this.deserializeMappings[camelizedName]
|| camelizedName;
if (this.isExcludedFromDeserialization(attributeName) || this.isExcludedFromDeserialization(camelizedName)) {
return undefined;
}
return camelizedName;
};
/**
* Returns a reference to the nested resource that has been specified for the attribute.
* @param attributeName The attribute name
* @returns {*} undefined if no nested resource has been specified or a reference to the nested resource class
*/
Serializer.prototype.getNestedResource = function (attributeName) {
return RailsResourceInjector.getDependency(this.nestedResources[attributeName]);
};
/**
* Returns a custom serializer for the attribute if one has been specified. Custom serializers can be specified
* in one of two ways. The serializeWith customization method allows specifying a custom serializer for any attribute.
* Or an attribute could have been specified as a nested resource in which case the nested resource's serializer
* is used. Custom serializers specified using serializeWith take precedence over the nested resource serializer.
*
* @param attributeName The attribute name
* @returns {*} undefined if no custom serializer has been specified or an instance of the Serializer
*/
Serializer.prototype.getAttributeSerializer = function (attributeName) {
var resource = this.getNestedResource(attributeName),
serializer = this.customSerializers[attributeName];
// custom serializer takes precedence over resource serializer
if (serializer) {
return RailsResourceInjector.createService(serializer)
} else if (resource) {
return resource.config.serializer;
}
return undefined;
};
/**
* Prepares the data for serialization to JSON.
*
* @param data The data to prepare
* @returns {*} A new object or array that is ready for JSON serialization
*/
Serializer.prototype.serializeValue = function (data) {
var result = data,
self = this;
if (angular.isArray(data)) {
result = [];
angular.forEach(data, function (value) {
result.push(self.serializeValue(value));
});
} else if (angular.isObject(data)) {
if (angular.isDate(data)) {
return data;
}
result = {};
angular.forEach(data, function (value, key) {
// if the value is a function then it can't be serialized to JSON so we'll just skip it
if (!angular.isFunction(value)) {
self.serializeAttribute(result, key, value);
}
});
}
return result;
};
/**
* Transforms an attribute and its value and stores it on the parent data object. The attribute will be
* renamed as needed and the value itself will be serialized as well.
*
* @param data The object that the attribute will be added to
* @param attribute The attribute to transform
* @param value The current value of the attribute
*/
Serializer.prototype.serializeAttribute = function (data, attribute, value) {
var serializer = this.getAttributeSerializer(attribute),
serializedAttributeName = this.getSerializedAttributeName(attribute);
// undefined means the attribute should be excluded from serialization
if (serializedAttributeName === undefined) {
return;
}
data[serializedAttributeName] = serializer ? serializer.serialize(value) : this.serializeValue(value);
};
/**
* Serializes the data by applying various transformations such as:
* - Underscoring attribute names
* - attribute renaming
* - attribute exclusion
* - custom attribute addition
*
* @param data The data to prepare
* @returns {*} A new object or array that is ready for JSON serialization
*/
Serializer.prototype.serialize = function (data) {
var result = this.serializeValue(data),
self = this;
if (angular.isObject(result)) {
angular.forEach(this.customSerializedAttributes, function (value, key) {
if (angular.isFunction(value)) {
value = value.call(data, data);
}
self.serializeAttribute(result, key, value);
});
}
return result;
};
/**
* Iterates over the data deserializing each entry on arrays and each key/value on objects.
*
* @param data The object to deserialize
* @param Resource (optional) The resource type to deserialize the result into
* @returns {*} A new object or an instance of Resource populated with deserialized data.
*/
Serializer.prototype.deserializeValue = function (data, Resource) {
var result = data,
self = this;
if (angular.isArray(data)) {
result = [];
angular.forEach(data, function (value) {
result.push(self.deserializeValue(value, Resource));
});
} else if (angular.isObject(data)) {
if (angular.isDate(data)) {
return data;
}
result = {};
if (Resource) {
result = new Resource.config.resourceConstructor();
}
angular.forEach(data, function (value, key) {
self.deserializeAttribute(result, key, value);
});
}
return result;
};
/**
* Transforms an attribute and its value and stores it on the parent data object. The attribute will be
* renamed as needed and the value itself will be deserialized as well.
*
* @param data The object that the attribute will be added to
* @param attribute The attribute to transform
* @param value The current value of the attribute
*/
Serializer.prototype.deserializeAttribute = function (data, attribute, value) {
var serializer,
NestedResource,
attributeName = this.getDeserializedAttributeName(attribute);
// undefined means the attribute should be excluded from serialization
if (attributeName === undefined) {
return;
}
serializer = this.getAttributeSerializer(attributeName);
NestedResource = this.getNestedResource(attributeName);
// preserved attributes are assigned unmodified
if (this.preservedAttributes[attributeName]) {
data[attributeName] = value;
} else {
data[attributeName] = serializer ? serializer.deserialize(value, NestedResource) : this.deserializeValue(value, NestedResource);
}
};
/**
* Deserializes the data by applying various transformations such as:
* - Camelizing attribute names
* - attribute renaming
* - attribute exclusion
* - nested resource creation
*
* @param data The object to deserialize
* @param Resource (optional) The resource type to deserialize the result into
* @returns {*} A new object or an instance of Resource populated with deserialized data
*/
Serializer.prototype.deserialize = function (data, Resource) {
// just calls deserializeValue for now so we can more easily add on custom attribute logic for deserialize too
return this.deserializeValue(data, Resource);
};
Serializer.prototype.pluralize = function (value) {
if (this.options.pluralize) {
return this.options.pluralize(value);
}
return value;
};
Serializer.prototype.underscore = function (value) {
if (this.options.underscore) {
return this.options.underscore(value);
}
return value;
};
Serializer.prototype.camelize = function (value) {
if (this.options.camelize) {
return this.options.camelize(value);
}
return value;
}
return Serializer;
}
railsSerializer.defaultOptions = defaultOptions;
return railsSerializer;
}];
});
}());
(function (undefined) {
angular.module('rails').factory('railsRootWrappingTransformer', function () {
return function (data, resource) {
var result = {};
result[angular.isArray(data) ? resource.config.pluralName : resource.config.name] = data;
return result;
};
});
angular.module('rails').factory('railsRootWrappingInterceptor', function () {
return function (promise) {
var resource = promise.resource;
if (!resource) {
return promise;
}
return promise.then(function (response) {
if (response.data && response.data.hasOwnProperty(resource.config.name)) {
response.data = response.data[resource.config.name];
} else if (response.data && response.data.hasOwnProperty(resource.config.pluralName)) {
response.data = response.data[resource.config.pluralName];
}
return response;
});
};
});
angular.module('rails').provider('RailsResource', function () {
var defaultOptions = {
rootWrapping: true,
updateMethod: 'put',
httpConfig: {},
defaultParams: undefined
};
this.rootWrapping = function (value) {
defaultOptions.rootWrapping = value;
return this;
};
this.updateMethod = function (value) {
defaultOptions.updateMethod = value;
return this;
};
this.httpConfig = function (value) {
defaultOptions.httpConfig = value;
return this;
};
this.defaultParams = function (value) {
defaultOptions.defaultParams = value;
return this;
};
this.$get = ['$http', '$q', 'railsUrlBuilder', 'railsSerializer', 'railsRootWrappingTransformer', 'railsRootWrappingInterceptor', 'RailsResourceInjector',
function ($http, $q, railsUrlBuilder, railsSerializer, railsRootWrappingTransformer, railsRootWrappingInterceptor, RailsResourceInjector) {
function appendPath(url, path) {
if (path) {
if (path[0] !== '/') {
url += '/';
}
url += path;
}
return url;
}
function forEachDependency(list, callback) {
var dependency;
for (var i = 0, len = list.length; i < len; i++) {
dependency = list[i];
if (angular.isString(dependency)) {
dependency = list[i] = RailsResourceInjector.getDependency(dependency);
}
callback(dependency);
}
}
function RailsResource(value) {
var instance = this;
if (value) {
var immediatePromise = function (data) {
return {
resource: RailsResource,
context: instance,
response: data,
then: function (callback) {
this.response = callback(this.response, this.resource, this.context);
return immediatePromise(this.response);
}
}
};
var data = this.constructor.callInterceptors(immediatePromise({data: value}), this).response.data;
angular.extend(this, data);
}
}
RailsResource.extend = function (child) {
// Extend logic copied from CoffeeScript generated code
var __hasProp = {}.hasOwnProperty, parent = this;
for (var key in parent) {
if (__hasProp.call(parent, key)) child[key] = parent[key];
}
function ctor() {
this.constructor = child;
}
ctor.prototype = parent.prototype;
child.prototype = new ctor();
child.__super__ = parent.prototype;
return child;
};
// allow calling configure multiple times to set configuration options and override values from inherited resources
RailsResource.configure = function (cfg) {
cfg = cfg || {};
if (this.config) {
cfg = angular.extend({}, this.config, cfg);
}
this.config = {};
this.config.url = cfg.url;
this.config.rootWrapping = cfg.rootWrapping === undefined ? defaultOptions.rootWrapping : cfg.rootWrapping; // using undefined check because config.rootWrapping || true would be true when config.rootWrapping === false
this.config.httpConfig = cfg.httpConfig || defaultOptions.httpConfig;
this.config.httpConfig.headers = angular.extend({'Accept': 'application/json', 'Content-Type': 'application/json'}, this.config.httpConfig.headers || {});
this.config.defaultParams = cfg.defaultParams || defaultOptions.defaultParams;
this.config.updateMethod = (cfg.updateMethod || defaultOptions.updateMethod).toLowerCase();
this.config.requestTransformers = cfg.requestTransformers ? cfg.requestTransformers.slice(0) : [];
this.config.responseInterceptors = cfg.responseInterceptors ? cfg.responseInterceptors.slice(0) : [];
this.config.afterResponseInterceptors = cfg.afterResponseInterceptors ? cfg.afterResponseInterceptors.slice(0) : [];
// strings and functions are not considered objects by angular.isObject()
if (angular.isObject(cfg.serializer)) {
this.config.serializer = cfg.serializer;
} else {
this.config.serializer = RailsResourceInjector.createService(cfg.serializer || railsSerializer());
}
this.config.name = this.config.serializer.underscore(cfg.name);
this.config.pluralName = this.config.serializer.underscore(cfg.pluralName || this.config.serializer.pluralize(this.config.name));
this.config.urlBuilder = railsUrlBuilder(this.config.url);
this.config.resourceConstructor = this;
};
RailsResource.configure({});
RailsResource.setUrl = function (url) {
this.configure({url: url});
};
RailsResource.buildUrl = function (context) {
return this.config.urlBuilder(context);
};
/**
* Add a callback to run on response and construction.
* @param fn(response data, constructor, context) - response data is either the resource instance returned or an array of resource instances,
* constructor is the resource class calling the function,
* context is the resource instance of the calling method (create, update, delete) or undefined if the method was a class method (get, query)
*/
RailsResource.beforeResponse = function (fn) {
fn = RailsResourceInjector.getDependency(fn);
this.config.responseInterceptors.push(function (promise) {
return promise.then(function (response) {
fn(response.data, promise.resource.config.resourceConstructor, promise.context);
return response;
});
});
};
/**
* Add a callback to run after response has been processed. These callbacks are not called on object construction.
* @param fn(response data, constructor) - response data is either the resource instance returned or an array of resource instances and constructor is the resource class calling the function
*/
RailsResource.afterResponse = function (fn) {
fn = RailsResourceInjector.getDependency(fn);
this.config.afterResponseInterceptors.push(function (promise) {
return promise.then(function (response) {
fn(response, promise.resource.config.resourceConstructor);
return response;
});
});
};
/**
* Adds a function to run after serializing the data to send to the server, but before root-wrapping it.
* @param fn (data, constructor) - data object is the serialized resource instance, and constructor the resource class calling the function
*/
RailsResource.beforeRequest = function (fn) {
fn = RailsResourceInjector.getDependency(fn);
this.config.requestTransformers.push(function (data, resource) {
return fn(data, resource.config.resourceConstructor) || data;
});
};
// transform data for request:
RailsResource.transformData = function (data) {
var config = this.config;
data = config.serializer.serialize(data);
forEachDependency(this.config.requestTransformers, function (transformer) {
data = transformer(data, config.resourceConstructor);
});
if (config.rootWrapping) {
data = railsRootWrappingTransformer(data, config.resourceConstructor);
}
return data;
};
// transform data on response:
RailsResource.callInterceptors = function (promise, context) {
var config = this.config;
promise = promise.then(function (response) {
// store off the data in case something (like our root unwrapping) assigns data as a new object
response.originalData = response.data;
return response;
});
if (config.rootWrapping) {
promise.resource = config.resourceConstructor;
promise = railsRootWrappingInterceptor(promise);
}
promise.then(function (response) {
response.data = config.serializer.deserialize(response.data, config.resourceConstructor);
return response;
});
// data is now deserialized. call response interceptors including beforeResponse
forEachDependency(config.responseInterceptors, function (interceptor) {
promise.resource = config.resourceConstructor;
promise.context = context;
promise = interceptor(promise);
});
return promise;
};
// transform data after response has been converted to a resource instance:
RailsResource.callAfterInterceptors = function (promise) {
var config = this.config;
// data is now deserialized. call response interceptors including afterResponse
forEachDependency(config.afterResponseInterceptors, function (interceptor) {
promise.resource = config.resourceConstructor;
promise = interceptor(promise);
});
return promise;
};
RailsResource.processResponse = function (promise) {
promise = this.callInterceptors(promise).then(function (response) {
return response.data;
});
return this.callAfterInterceptors(promise);
};
RailsResource.getParameters = function (queryParams) {
var params;
if (this.config.defaultParams) {
params = this.config.defaultParams;
}
if (angular.isObject(queryParams)) {
params = angular.extend(params || {}, queryParams);
}
return params;
};
RailsResource.getHttpConfig = function (queryParams) {
var params = this.getParameters(queryParams);
if (params) {
return angular.extend({params: params}, this.config.httpConfig);
}
return angular.copy(this.config.httpConfig);
};
/**
* Returns a URL from the given parameters. You can override this method on your resource definitions to provide
* custom logic for building your URLs or you can utilize the parameterized url strings to substitute values in the
* URL string.
*
* The parameters in the URL string follow the normal Angular binding expression using {{ and }} for the start/end symbols.
*
* If the context is a number and the URL string does not contain an id parameter then the number is appended
* to the URL string.
*
* If the context is a number and the URL string does
* @param context
* @param path {string} (optional) An additional path to append to the URL
* @return {string}
*/
RailsResource.$url = RailsResource.resourceUrl = function (context, path) {
if (!angular.isObject(context)) {
context = {id: context};
}
return appendPath(this.buildUrl(context || {}), path);
};
RailsResource.$get = function (url, queryParams) {
return this.processResponse($http.get(url, this.getHttpConfig(queryParams)));
};
RailsResource.query = function (queryParams, context) {
return this.$get(this.resourceUrl(context), queryParams);
};
RailsResource.get = function (context, queryParams) {
return this.$get(this.resourceUrl(context), queryParams);
};
/**
* Returns the URL for this resource.
*
* @param path {string} (optional) An additional path to append to the URL
* @returns {string} The URL for the resource
*/
RailsResource.prototype.$url = function (path) {
return appendPath(this.constructor.resourceUrl(this), path);
};
RailsResource.prototype.processResponse = function (promise) {
promise = this.constructor.callInterceptors(promise, this);
promise = promise.then(angular.bind(this, function (response) {
// we may not have response data
if (response.hasOwnProperty('data') && angular.isObject(response.data)) {
angular.extend(this, response.data);
}
return this;
}));
return this.constructor.callAfterInterceptors(promise);
};
angular.forEach(['post', 'put', 'patch'], function (method) {
RailsResource['$' + method] = function (url, data) {
var config;
// clone so we can manipulate w/o modifying the actual instance
data = this.transformData(angular.copy(data, {}));
config = angular.extend({method: method, url: url, data: data}, this.getHttpConfig());
return this.processResponse($http(config));
};
RailsResource.prototype['$' + method] = function (url) {
var data, config;
// clone so we can manipulate w/o modifying the actual instance
data = this.constructor.transformData(angular.copy(this, {}));
config = angular.extend({method: method, url: url, data: data}, this.constructor.getHttpConfig());
return this.processResponse($http(config));
};
});
RailsResource.prototype.create = function () {
return this.$post(this.$url(), this);
};
RailsResource.prototype.update = function () {
return this['$' + this.constructor.config.updateMethod](this.$url(), this);
};
RailsResource.prototype.isNew = function () {
return this.id == null;
};
RailsResource.prototype.save = function () {
if (this.isNew()) {
return this.create();
} else {
return this.update();
}
};
RailsResource['$delete'] = function (url) {
return this.processResponse($http['delete'](url, this.getHttpConfig()));
};
RailsResource.prototype['$delete'] = function (url) {
return this.processResponse($http['delete'](url, this.constructor.getHttpConfig()));
};
//using ['delete'] instead of .delete for IE7/8 compatibility
RailsResource.prototype.remove = RailsResource.prototype['delete'] = function () {
return this.$delete(this.$url());
};
return RailsResource;
}];
});
angular.module('rails').factory('railsResourceFactory', ['RailsResource', function (RailsResource) {
return function (config) {
function Resource() {
Resource.__super__.constructor.apply(this, arguments);
}
RailsResource.extend(Resource);
Resource.configure(config);
return Resource;
}
}]);
}());
PK
YXFC&k 0 0 angularjs-rails-resource.min.js/**
* A resource factory inspired by $resource from AngularJS
* @version v1.0.0-pre1 - 2013-10-06
* @link https://github.com/FineLinePrototyping/angularjs-rails-resource.git
* @author
*/
!function(){angular.module("rails",["ng"])}(),function(){angular.module("rails").factory("RailsInflector",function(){function a(a){return angular.isString(a)?a.replace(/_[\w\d]/g,function(a,b,c){return 0===b?a:c.charAt(b+1).toUpperCase()}):a}function b(a){return angular.isString(a)?a.replace(/[A-Z]/g,function(a,b){return 0===b?a:"_"+a.toLowerCase()}):a}function c(a){return a+"s"}return{camelize:a,underscore:b,pluralize:c}})}(),function(a){angular.module("rails").factory("RailsResourceInjector",["$injector",function(b){function c(c){return c?angular.isString(c)?b.get(c):c:a}function d(d){return d?b.instantiate(c(d)):a}return{createService:d,getDependency:c}}])}(),function(){angular.module("rails").factory("railsUrlBuilder",["$interpolate",function(a){return function(b){var c;return angular.isFunction(b)||angular.isUndefined(b)?b:(-1===b.indexOf(a.startSymbol())&&(b=b+"/"+a.startSymbol()+"id"+a.endSymbol()),c=a(b),function(a){return b=c(a),"/"===b.charAt(b.length-1)&&(b=b.substr(0,b.length-1)),b})}}])}(),function(a){angular.module("rails").provider("railsSerializer",function(){var b={underscore:a,camelize:a,pluralize:a,exclusionMatchers:[]};this.underscore=function(a){return b.underscore=a,this},this.camelize=function(a){return b.camelize=a,this},this.pluralize=function(a){return b.pluralize=a,this},this.exclusionMatchers=function(a){return b.exclusionMatchers=a,this},this.$get=["$injector","RailsInflector","RailsResourceInjector",function(c,d,e){function f(c,d){function f(){angular.isFunction(c)&&(d=c,c={}),this.exclusions={},this.inclusions={},this.serializeMappings={},this.deserializeMappings={},this.customSerializedAttributes={},this.preservedAttributes={},this.customSerializers={},this.nestedResources={},this.options=angular.extend({excludeByDefault:!1},b,c||{}),d&&d.call(this,this)}return f.prototype.exclude=function(){var a=this.exclusions;return angular.forEach(arguments,function(b){a[b]=!1}),this},f.prototype.only=function(){var a=this.inclusions;return this.options.excludeByDefault=!0,angular.forEach(arguments,function(b){a[b]=!0}),this},f.prototype.nestedAttribute=function(){var a=this;return angular.forEach(arguments,function(b){a.rename(b,b+"_attributes")}),this},f.prototype.resource=function(a,b,c){return this.nestedResources[a]=b,c&&this.serializeWith(a,c),this},f.prototype.rename=function(b,c,d){return this.serializeMappings[b]=c,(d||d===a)&&(this.deserializeMappings[c]=b),this},f.prototype.add=function(a,b){return this.customSerializedAttributes[a]=b,this},f.prototype.preserve=function(a){return this.preservedAttributes[a]=!0,this},f.prototype.serializeWith=function(a,b){return this.customSerializers[a]=b,this},f.prototype.isExcludedFromSerialization=function(b){if(this.options.excludeByDefault&&!this.inclusions.hasOwnProperty(b)||this.exclusions.hasOwnProperty(b))return!0;if(this.options.exclusionMatchers){var c=!1;return angular.forEach(this.options.exclusionMatchers,function(d){angular.isString(d)?c=c||0===b.indexOf(d):angular.isFunction(d)?c=c||d.call(a,b):d instanceof RegExp&&(c=c||d.test(b))}),c}return!1},f.prototype.getSerializedAttributeName=function(b){var c=this.serializeMappings[b]||b;return this.isExcludedFromSerialization(b)||this.isExcludedFromSerialization(c)?a:this.underscore(c)},f.prototype.isExcludedFromDeserialization=function(){return!1},f.prototype.getDeserializedAttributeName=function(b){var c=this.camelize(b);return c=this.deserializeMappings[b]||this.deserializeMappings[c]||c,this.isExcludedFromDeserialization(b)||this.isExcludedFromDeserialization(c)?a:c},f.prototype.getNestedResource=function(a){return e.getDependency(this.nestedResources[a])},f.prototype.getAttributeSerializer=function(b){var c=this.getNestedResource(b),d=this.customSerializers[b];return d?e.createService(d):c?c.config.serializer:a},f.prototype.serializeValue=function(a){var b=a,c=this;if(angular.isArray(a))b=[],angular.forEach(a,function(a){b.push(c.serializeValue(a))});else if(angular.isObject(a)){if(angular.isDate(a))return a;b={},angular.forEach(a,function(a,d){angular.isFunction(a)||c.serializeAttribute(b,d,a)})}return b},f.prototype.serializeAttribute=function(b,c,d){var e=this.getAttributeSerializer(c),f=this.getSerializedAttributeName(c);f!==a&&(b[f]=e?e.serialize(d):this.serializeValue(d))},f.prototype.serialize=function(a){var b=this.serializeValue(a),c=this;return angular.isObject(b)&&angular.forEach(this.customSerializedAttributes,function(d,e){angular.isFunction(d)&&(d=d.call(a,a)),c.serializeAttribute(b,e,d)}),b},f.prototype.deserializeValue=function(a,b){var c=a,d=this;if(angular.isArray(a))c=[],angular.forEach(a,function(a){c.push(d.deserializeValue(a,b))});else if(angular.isObject(a)){if(angular.isDate(a))return a;c={},b&&(c=new b.config.resourceConstructor),angular.forEach(a,function(a,b){d.deserializeAttribute(c,b,a)})}return c},f.prototype.deserializeAttribute=function(b,c,d){var e,f,g=this.getDeserializedAttributeName(c);g!==a&&(e=this.getAttributeSerializer(g),f=this.getNestedResource(g),b[g]=this.preservedAttributes[g]?d:e?e.deserialize(d,f):this.deserializeValue(d,f))},f.prototype.deserialize=function(a,b){return this.deserializeValue(a,b)},f.prototype.pluralize=function(a){return this.options.pluralize?this.options.pluralize(a):a},f.prototype.underscore=function(a){return this.options.underscore?this.options.underscore(a):a},f.prototype.camelize=function(a){return this.options.camelize?this.options.camelize(a):a},f}return b.underscore=b.underscore||d.underscore,b.camelize=b.camelize||d.camelize,b.pluralize=b.pluralize||d.pluralize,f.defaultOptions=b,f}]})}(),function(a){angular.module("rails").factory("railsRootWrappingTransformer",function(){return function(a,b){var c={};return c[angular.isArray(a)?b.config.pluralName:b.config.name]=a,c}}),angular.module("rails").factory("railsRootWrappingInterceptor",function(){return function(a){var b=a.resource;return b?a.then(function(a){return a.data&&a.data.hasOwnProperty(b.config.name)?a.data=a.data[b.config.name]:a.data&&a.data.hasOwnProperty(b.config.pluralName)&&(a.data=a.data[b.config.pluralName]),a}):a}}),angular.module("rails").provider("RailsResource",function(){var b={rootWrapping:!0,updateMethod:"put",httpConfig:{},defaultParams:a};this.rootWrapping=function(a){return b.rootWrapping=a,this},this.updateMethod=function(a){return b.updateMethod=a,this},this.httpConfig=function(a){return b.httpConfig=a,this},this.defaultParams=function(a){return b.defaultParams=a,this},this.$get=["$http","$q","railsUrlBuilder","railsSerializer","railsRootWrappingTransformer","railsRootWrappingInterceptor","RailsResourceInjector",function(c,d,e,f,g,h,i){function j(a,b){return b&&("/"!==b[0]&&(a+="/"),a+=b),a}function k(a,b){for(var c,d=0,e=a.length;e>d;d++)c=a[d],angular.isString(c)&&(c=a[d]=i.getDependency(c)),b(c)}function l(a){var b=this;if(a){var c=function(a){return{resource:l,context:b,response:a,then:function(a){return this.response=a(this.response,this.resource,this.context),c(this.response)}}},d=this.constructor.callInterceptors(c({data:a}),this).response.data;angular.extend(this,d)}}return l.extend=function(a){function b(){this.constructor=a}var c={}.hasOwnProperty,d=this;for(var e in d)c.call(d,e)&&(a[e]=d[e]);return b.prototype=d.prototype,a.prototype=new b,a.__super__=d.prototype,a},l.configure=function(c){c=c||{},this.config&&(c=angular.extend({},this.config,c)),this.config={},this.config.url=c.url,this.config.rootWrapping=c.rootWrapping===a?b.rootWrapping:c.rootWrapping,this.config.httpConfig=c.httpConfig||b.httpConfig,this.config.httpConfig.headers=angular.extend({Accept:"application/json","Content-Type":"application/json"},this.config.httpConfig.headers||{}),this.config.defaultParams=c.defaultParams||b.defaultParams,this.config.updateMethod=(c.updateMethod||b.updateMethod).toLowerCase(),this.config.requestTransformers=c.requestTransformers?c.requestTransformers.slice(0):[],this.config.responseInterceptors=c.responseInterceptors?c.responseInterceptors.slice(0):[],this.config.afterResponseInterceptors=c.afterResponseInterceptors?c.afterResponseInterceptors.slice(0):[],this.config.serializer=angular.isObject(c.serializer)?c.serializer:i.createService(c.serializer||f()),this.config.name=this.config.serializer.underscore(c.name),this.config.pluralName=this.config.serializer.underscore(c.pluralName||this.config.serializer.pluralize(this.config.name)),this.config.urlBuilder=e(this.config.url),this.config.resourceConstructor=this},l.configure({}),l.setUrl=function(a){this.configure({url:a})},l.buildUrl=function(a){return this.config.urlBuilder(a)},l.beforeResponse=function(a){a=i.getDependency(a),this.config.responseInterceptors.push(function(b){return b.then(function(c){return a(c.data,b.resource.config.resourceConstructor,b.context),c})})},l.afterResponse=function(a){a=i.getDependency(a),this.config.afterResponseInterceptors.push(function(b){return b.then(function(c){return a(c,b.resource.config.resourceConstructor),c})})},l.beforeRequest=function(a){a=i.getDependency(a),this.config.requestTransformers.push(function(b,c){return a(b,c.config.resourceConstructor)||b})},l.transformData=function(a){var b=this.config;return a=b.serializer.serialize(a),k(this.config.requestTransformers,function(c){a=c(a,b.resourceConstructor)}),b.rootWrapping&&(a=g(a,b.resourceConstructor)),a},l.callInterceptors=function(a,b){var c=this.config;return a=a.then(function(a){return a.originalData=a.data,a}),c.rootWrapping&&(a.resource=c.resourceConstructor,a=h(a)),a.then(function(a){return a.data=c.serializer.deserialize(a.data,c.resourceConstructor),a}),k(c.responseInterceptors,function(d){a.resource=c.resourceConstructor,a.context=b,a=d(a)}),a},l.callAfterInterceptors=function(a){var b=this.config;return k(b.afterResponseInterceptors,function(c){a.resource=b.resourceConstructor,a=c(a)}),a},l.processResponse=function(a){return a=this.callInterceptors(a).then(function(a){return a.data}),this.callAfterInterceptors(a)},l.getParameters=function(a){var b;return this.config.defaultParams&&(b=this.config.defaultParams),angular.isObject(a)&&(b=angular.extend(b||{},a)),b},l.getHttpConfig=function(a){var b=this.getParameters(a);return b?angular.extend({params:b},this.config.httpConfig):angular.copy(this.config.httpConfig)},l.$url=l.resourceUrl=function(a,b){return angular.isObject(a)||(a={id:a}),j(this.buildUrl(a||{}),b)},l.$get=function(a,b){return this.processResponse(c.get(a,this.getHttpConfig(b)))},l.query=function(a,b){return this.$get(this.resourceUrl(b),a)},l.get=function(a,b){return this.$get(this.resourceUrl(a),b)},l.prototype.$url=function(a){return j(this.constructor.resourceUrl(this),a)},l.prototype.processResponse=function(a){return a=this.constructor.callInterceptors(a,this),a=a.then(angular.bind(this,function(a){return a.hasOwnProperty("data")&&angular.isObject(a.data)&&angular.extend(this,a.data),this})),this.constructor.callAfterInterceptors(a)},angular.forEach(["post","put","patch"],function(a){l["$"+a]=function(b,d){var e;return d=this.transformData(angular.copy(d,{})),e=angular.extend({method:a,url:b,data:d},this.getHttpConfig()),this.processResponse(c(e))},l.prototype["$"+a]=function(b){var d,e;return d=this.constructor.transformData(angular.copy(this,{})),e=angular.extend({method:a,url:b,data:d},this.constructor.getHttpConfig()),this.processResponse(c(e))}}),l.prototype.create=function(){return this.$post(this.$url(),this)},l.prototype.update=function(){return this["$"+this.constructor.config.updateMethod](this.$url(),this)},l.prototype.isNew=function(){return null==this.id},l.prototype.save=function(){return this.isNew()?this.create():this.update()},l.$delete=function(a){return this.processResponse(c["delete"](a,this.getHttpConfig()))},l.prototype.$delete=function(a){return this.processResponse(c["delete"](a,this.constructor.getHttpConfig()))},l.prototype.remove=l.prototype["delete"]=function(){return this.$delete(this.$url())},l}]}),angular.module("rails").factory("railsResourceFactory",["RailsResource",function(a){return function(b){function c(){c.__super__.constructor.apply(this,arguments)}return a.extend(c),c.configure(b),c}}])}();PK
YXFCJv v angularjs-rails-resource.jsPK
YXFC&k 0 0 angularjs-rails-resource.min.jsPK