angularjs-rails-resource 2.3.0 → 2.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +5 -0
- data/Gruntfile.js +1 -7
- data/bower.json +6 -5
- data/dist/angularjs-rails-resource.js +1703 -0
- data/dist/angularjs-rails-resource.min.js +8 -0
- data/dist/extensions/snapshots.js +148 -0
- data/dist/extensions/snapshots.min.js +8 -0
- data/lib/angularjs-rails-resource/version.rb +1 -1
- data/package-lock.json +409 -171
- data/package.json +4 -5
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 02ee284de9ae42fafae6f2d8f5e102fddeb1e511
|
4
|
+
data.tar.gz: 1e51b537318f682dcc4ca6ab6fabf12a330268f6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2690f964e3f318dc91bc22185a221754f764c802c1fc2c6956c585300b14fef3c12b5ba7ccb39b506c6cad29ee670ede7480c3b3fc7627f4f8867f8c610f5833
|
7
|
+
data.tar.gz: 027b2c3e8ae30b98710f392cfa0c890128032aa851e132e8f0cf7f688d384b6f650adc183fec6b6fbdffccc7cf2479d95688cb184051d3c68966d3b96bec06b5
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
data/Gruntfile.js
CHANGED
@@ -22,7 +22,7 @@ module.exports = function(grunt) {
|
|
22
22
|
},
|
23
23
|
|
24
24
|
dirs: {
|
25
|
-
dest: '
|
25
|
+
dest: 'dist'
|
26
26
|
},
|
27
27
|
|
28
28
|
clean: ['<%= dirs.dest %>'],
|
@@ -82,11 +82,6 @@ module.exports = function(grunt) {
|
|
82
82
|
}
|
83
83
|
},
|
84
84
|
|
85
|
-
watch: {
|
86
|
-
files: ['<%= jshint.files %>'],
|
87
|
-
tasks: ['jshint']
|
88
|
-
},
|
89
|
-
|
90
85
|
bump: {
|
91
86
|
options: {
|
92
87
|
files: ['package.json', 'bower.json'],
|
@@ -99,7 +94,6 @@ module.exports = function(grunt) {
|
|
99
94
|
|
100
95
|
grunt.loadNpmTasks('grunt-contrib-uglify');
|
101
96
|
grunt.loadNpmTasks('grunt-contrib-jshint');
|
102
|
-
grunt.loadNpmTasks('grunt-contrib-watch');
|
103
97
|
grunt.loadNpmTasks('grunt-contrib-concat');
|
104
98
|
grunt.loadNpmTasks('grunt-contrib-copy');
|
105
99
|
grunt.loadNpmTasks('grunt-contrib-clean');
|
data/bower.json
CHANGED
@@ -1,27 +1,28 @@
|
|
1
1
|
{
|
2
2
|
"name": "angularjs-rails-resource",
|
3
|
-
"version": "2.3.
|
3
|
+
"version": "2.3.1",
|
4
4
|
"main": "angularjs-rails-resource.js",
|
5
5
|
"description": "A resource factory inspired by $resource from AngularJS",
|
6
6
|
"repository": {
|
7
7
|
"type": "git",
|
8
|
-
"url": "https://github.com/FineLinePrototyping/
|
8
|
+
"url": "https://github.com/FineLinePrototyping/angularjs-rails-resource.git"
|
9
9
|
},
|
10
10
|
"dependencies": {
|
11
11
|
"angular": "^1.3"
|
12
12
|
},
|
13
13
|
"ignore": [
|
14
|
-
"
|
14
|
+
"**/.*",
|
15
|
+
"angularjs-rails-resource.gemspec",
|
16
|
+
"changelog.js",
|
15
17
|
"Gemfile",
|
16
18
|
"Gruntfile.js",
|
17
19
|
"karma.conf.js",
|
20
|
+
"package-lock.json",
|
18
21
|
"package.json",
|
19
22
|
"Rakefile",
|
20
|
-
"angularjs-rails-resource.gemspec",
|
21
23
|
"test",
|
22
24
|
"vendor",
|
23
25
|
"lib",
|
24
|
-
"*.zip",
|
25
26
|
".travis.yml",
|
26
27
|
".gitignore"
|
27
28
|
]
|
@@ -0,0 +1,1703 @@
|
|
1
|
+
/**
|
2
|
+
* A resource factory inspired by $resource from AngularJS
|
3
|
+
* @version v2.3.1 - 2018-02-11
|
4
|
+
* @link https://github.com/FineLinePrototyping/angularjs-rails-resource.git
|
5
|
+
* @author
|
6
|
+
*/
|
7
|
+
|
8
|
+
(function (undefined) {
|
9
|
+
angular.module('rails', ['ng']);
|
10
|
+
}());
|
11
|
+
|
12
|
+
|
13
|
+
|
14
|
+
(function (undefined) {
|
15
|
+
angular.module('rails').factory('RailsInflector', function() {
|
16
|
+
function camelize(key) {
|
17
|
+
if (!angular.isString(key)) {
|
18
|
+
return key;
|
19
|
+
}
|
20
|
+
|
21
|
+
// should this match more than word and digit characters?
|
22
|
+
return key.replace(/_[\w\d]/g, function (match, index, string) {
|
23
|
+
return index === 0 ? match : string.charAt(index + 1).toUpperCase();
|
24
|
+
});
|
25
|
+
}
|
26
|
+
|
27
|
+
function underscore(key) {
|
28
|
+
if (!angular.isString(key)) {
|
29
|
+
return key;
|
30
|
+
}
|
31
|
+
|
32
|
+
// TODO match the latest logic from Active Support
|
33
|
+
return key.replace(/[A-Z]/g, function (match, index) {
|
34
|
+
return index === 0 ? match : '_' + match.toLowerCase();
|
35
|
+
});
|
36
|
+
}
|
37
|
+
|
38
|
+
function pluralize(value) {
|
39
|
+
// TODO match Active Support
|
40
|
+
return value + 's';
|
41
|
+
}
|
42
|
+
|
43
|
+
return {
|
44
|
+
camelize: camelize,
|
45
|
+
underscore: underscore,
|
46
|
+
pluralize: pluralize
|
47
|
+
};
|
48
|
+
});
|
49
|
+
}());
|
50
|
+
(function (undefined) {
|
51
|
+
angular.module('rails').factory('RailsResourceInjector', ['$injector', function($injector) {
|
52
|
+
/**
|
53
|
+
* Allow dependencies to be referenced by name or instance. If referenced by name AngularJS $injector
|
54
|
+
* is used to retrieve the dependency.
|
55
|
+
*
|
56
|
+
* @param dependency (string | function) The dependency to retrieve
|
57
|
+
* @returns {*} The dependency
|
58
|
+
*/
|
59
|
+
function getDependency(dependency) {
|
60
|
+
if (dependency) {
|
61
|
+
return angular.isString(dependency) ? $injector.get(dependency) : dependency;
|
62
|
+
}
|
63
|
+
|
64
|
+
return undefined;
|
65
|
+
}
|
66
|
+
|
67
|
+
/**
|
68
|
+
* Looks up and instantiates an instance of the requested service. If the service is not a string then it is
|
69
|
+
* assumed to be a constructor function.
|
70
|
+
*
|
71
|
+
* @param {String|function|Object} service The service to instantiate
|
72
|
+
* @returns {*} A new instance of the requested service
|
73
|
+
*/
|
74
|
+
function createService(service) {
|
75
|
+
if (service) {
|
76
|
+
return $injector.instantiate(getDependency(service));
|
77
|
+
}
|
78
|
+
|
79
|
+
return undefined;
|
80
|
+
}
|
81
|
+
|
82
|
+
/**
|
83
|
+
* Looks up and instantiates an instance of the requested service if .
|
84
|
+
* @param {String|function|Object} service The service to instantiate
|
85
|
+
* @returns {*}
|
86
|
+
*/
|
87
|
+
function getService(service) {
|
88
|
+
// strings and functions are not considered objects by angular.isObject()
|
89
|
+
if (angular.isObject(service)) {
|
90
|
+
return service;
|
91
|
+
} else if (service) {
|
92
|
+
return createService(service);
|
93
|
+
}
|
94
|
+
|
95
|
+
return undefined;
|
96
|
+
}
|
97
|
+
|
98
|
+
return {
|
99
|
+
createService: createService,
|
100
|
+
getService: getService,
|
101
|
+
getDependency: getDependency
|
102
|
+
};
|
103
|
+
}]);
|
104
|
+
}());
|
105
|
+
/**
|
106
|
+
* @ngdoc function
|
107
|
+
* @name rails.railsUrlBuilder
|
108
|
+
* @function
|
109
|
+
* @requires $interpolate
|
110
|
+
*
|
111
|
+
* @description
|
112
|
+
*
|
113
|
+
* Compiles a URL template string into an interpolation function using $interpolate. If no interpolation bindings
|
114
|
+
* found then {{id}} is appended to the url string.
|
115
|
+
*
|
116
|
+
<pre>
|
117
|
+
expect(railsUrlBuilder('/books')()).toEqual('/books')
|
118
|
+
expect(railsUrlBuilder('/books')({id: 1})).toEqual('/books/1')
|
119
|
+
expect(railsUrlBuilder('/authors/{{authorId}}/books/{{id}}')({id: 1, authorId: 2})).toEqual('/authors/2/books/1')
|
120
|
+
</pre>
|
121
|
+
*
|
122
|
+
* If the $interpolate startSymbol and endSymbol have been customized those values should be used instead of {{ and }}
|
123
|
+
*
|
124
|
+
* @param {string|function} url If the url is a function then that function is returned. Otherwise the url string
|
125
|
+
* is passed to $interpolate as an expression.
|
126
|
+
*
|
127
|
+
* @returns {function(context)} As stated by $interpolate documentation:
|
128
|
+
* An interpolation function which is used to compute the interpolated
|
129
|
+
* string. The function has these parameters:
|
130
|
+
*
|
131
|
+
* * `context`: an object against which any expressions embedded in the strings are evaluated
|
132
|
+
* against.
|
133
|
+
*
|
134
|
+
*/
|
135
|
+
(function (undefined) {
|
136
|
+
angular.module('rails').factory('railsUrlBuilder', ['$interpolate', function($interpolate) {
|
137
|
+
return function (config) {
|
138
|
+
var url = config.url,
|
139
|
+
idAttribute = config.idAttribute,
|
140
|
+
expression;
|
141
|
+
|
142
|
+
if (angular.isFunction(url) || angular.isUndefined(url)) {
|
143
|
+
return url;
|
144
|
+
}
|
145
|
+
|
146
|
+
if (!config.singular && url.indexOf($interpolate.startSymbol()) === -1) {
|
147
|
+
url = url + '/' + $interpolate.startSymbol() + idAttribute + $interpolate.endSymbol();
|
148
|
+
}
|
149
|
+
|
150
|
+
expression = $interpolate(url);
|
151
|
+
|
152
|
+
return function (params) {
|
153
|
+
url = expression(params);
|
154
|
+
|
155
|
+
if (url.charAt(url.length - 1) === '/') {
|
156
|
+
url = url.substr(0, url.length - 1);
|
157
|
+
}
|
158
|
+
|
159
|
+
return url;
|
160
|
+
};
|
161
|
+
};
|
162
|
+
}]);
|
163
|
+
}());
|
164
|
+
|
165
|
+
(function (undefined) {
|
166
|
+
angular.module('rails').provider('railsSerializer', function() {
|
167
|
+
var defaultOptions = {
|
168
|
+
underscore: undefined,
|
169
|
+
camelize: undefined,
|
170
|
+
pluralize: undefined,
|
171
|
+
exclusionMatchers: []
|
172
|
+
};
|
173
|
+
|
174
|
+
/**
|
175
|
+
* Configures the underscore method used by the serializer. If not defined then <code>RailsInflector.underscore</code>
|
176
|
+
* will be used.
|
177
|
+
*
|
178
|
+
* @param {function(string):string} fn The function to use for underscore conversion
|
179
|
+
* @returns {railsSerializerProvider} The provider for chaining
|
180
|
+
*/
|
181
|
+
this.underscore = function(fn) {
|
182
|
+
defaultOptions.underscore = fn;
|
183
|
+
return this;
|
184
|
+
};
|
185
|
+
|
186
|
+
/**
|
187
|
+
* Configures the camelize method used by the serializer. If not defined then <code>RailsInflector.camelize</code>
|
188
|
+
* will be used.
|
189
|
+
*
|
190
|
+
* @param {function(string):string} fn The function to use for camelize conversion
|
191
|
+
* @returns {railsSerializerProvider} The provider for chaining
|
192
|
+
*/
|
193
|
+
this.camelize = function(fn) {
|
194
|
+
defaultOptions.camelize = fn;
|
195
|
+
return this;
|
196
|
+
};
|
197
|
+
|
198
|
+
/**
|
199
|
+
* Configures the pluralize method used by the serializer. If not defined then <code>RailsInflector.pluralize</code>
|
200
|
+
* will be used.
|
201
|
+
*
|
202
|
+
* @param {function(string):string} fn The function to use for pluralizing strings.
|
203
|
+
* @returns {railsSerializerProvider} The provider for chaining
|
204
|
+
*/
|
205
|
+
this.pluralize = function(fn) {
|
206
|
+
defaultOptions.pluralize = fn;
|
207
|
+
return this;
|
208
|
+
};
|
209
|
+
|
210
|
+
/**
|
211
|
+
* Configures the array exclusion matchers by the serializer. Exclusion matchers can be one of the following:
|
212
|
+
* * string - Defines a prefix that is used to test for exclusion
|
213
|
+
* * RegExp - A custom regular expression that is tested against the attribute name
|
214
|
+
* * function - A custom function that accepts a string argument and returns a boolean with true indicating exclusion.
|
215
|
+
*
|
216
|
+
* @param {Array.<string|function(string):boolean|RegExp} exclusions An array of exclusion matchers
|
217
|
+
* @returns {railsSerializerProvider} The provider for chaining
|
218
|
+
*/
|
219
|
+
this.exclusionMatchers = function(exclusions) {
|
220
|
+
defaultOptions.exclusionMatchers = exclusions;
|
221
|
+
return this;
|
222
|
+
};
|
223
|
+
|
224
|
+
this.$get = ['$injector', 'RailsInflector', 'RailsResourceInjector', function ($injector, RailsInflector, RailsResourceInjector) {
|
225
|
+
defaultOptions.underscore = defaultOptions.underscore || RailsInflector.underscore;
|
226
|
+
defaultOptions.camelize = defaultOptions.camelize || RailsInflector.camelize;
|
227
|
+
defaultOptions.pluralize = defaultOptions.pluralize || RailsInflector.pluralize;
|
228
|
+
|
229
|
+
function railsSerializer(options, customizer) {
|
230
|
+
|
231
|
+
function Serializer() {
|
232
|
+
if (angular.isFunction(options)) {
|
233
|
+
customizer = options;
|
234
|
+
options = {};
|
235
|
+
}
|
236
|
+
|
237
|
+
this.exclusions = {};
|
238
|
+
this.inclusions = {};
|
239
|
+
this.serializeMappings = {};
|
240
|
+
this.deserializeMappings = {};
|
241
|
+
this.customSerializedAttributes = {};
|
242
|
+
this.preservedAttributes = {};
|
243
|
+
this.customSerializers = {};
|
244
|
+
this.nestedResources = {};
|
245
|
+
this.polymorphics = {};
|
246
|
+
this.options = angular.extend({excludeByDefault: false}, defaultOptions, options || {});
|
247
|
+
|
248
|
+
if (customizer) {
|
249
|
+
customizer.call(this, this);
|
250
|
+
}
|
251
|
+
}
|
252
|
+
|
253
|
+
/**
|
254
|
+
* Accepts a variable list of attribute names to exclude from JSON serialization.
|
255
|
+
*
|
256
|
+
* @param attributeNames... {string} Variable number of attribute name parameters
|
257
|
+
* @returns {Serializer} this for chaining support
|
258
|
+
*/
|
259
|
+
Serializer.prototype.exclude = function () {
|
260
|
+
var exclusions = this.exclusions;
|
261
|
+
|
262
|
+
angular.forEach(arguments, function (attributeName) {
|
263
|
+
exclusions[attributeName] = false;
|
264
|
+
});
|
265
|
+
|
266
|
+
return this;
|
267
|
+
};
|
268
|
+
|
269
|
+
/**
|
270
|
+
* Accepts a variable list of attribute names that should be included in JSON serialization.
|
271
|
+
* Using this method will by default exclude all other attributes and only the ones explicitly included using <code>only</code> will be serialized.
|
272
|
+
* @param attributeNames... {string} Variable number of attribute name parameters
|
273
|
+
* @returns {Serializer} this for chaining support
|
274
|
+
*/
|
275
|
+
Serializer.prototype.only = function () {
|
276
|
+
var inclusions = this.inclusions;
|
277
|
+
this.options.excludeByDefault = true;
|
278
|
+
|
279
|
+
angular.forEach(arguments, function (attributeName) {
|
280
|
+
inclusions[attributeName] = true;
|
281
|
+
});
|
282
|
+
|
283
|
+
return this;
|
284
|
+
};
|
285
|
+
|
286
|
+
/**
|
287
|
+
* This is a shortcut for rename that allows you to specify a variable number of attributes that should all be renamed to
|
288
|
+
* <code>{attributeName}_attributes</code> to work with the Rails nested_attributes feature.
|
289
|
+
* @param attributeNames... {string} Variable number of attribute name parameters
|
290
|
+
* @returns {Serializer} this for chaining support
|
291
|
+
*/
|
292
|
+
Serializer.prototype.nestedAttribute = function () {
|
293
|
+
var self = this;
|
294
|
+
|
295
|
+
angular.forEach(arguments, function (attributeName) {
|
296
|
+
self.rename(attributeName, attributeName + '_attributes');
|
297
|
+
});
|
298
|
+
|
299
|
+
return this;
|
300
|
+
};
|
301
|
+
|
302
|
+
/**
|
303
|
+
* Specifies an attribute that is a nested resource within the parent object.
|
304
|
+
* Nested resources do not imply nested attributes, if you want both you still have to specify call <code>nestedAttribute</code> as well.
|
305
|
+
*
|
306
|
+
* A nested resource serves two purposes. First, it defines the resource that should be used when constructing resources from the server.
|
307
|
+
* Second, it specifies how the nested object should be serialized.
|
308
|
+
*
|
309
|
+
* An optional third parameter <code>serializer</code> is available to override the serialization logic
|
310
|
+
* of the resource in case you need to serialize it differently in multiple contexts.
|
311
|
+
*
|
312
|
+
* @param attributeName {string} The name of the attribute that is a nested resource
|
313
|
+
* @param resource {string | Resource} A reference to the resource that the attribute is a type of.
|
314
|
+
* @param serializer {string | Serializer} (optional) An optional serializer reference to override the nested resource's default serializer
|
315
|
+
* @returns {Serializer} this for chaining support
|
316
|
+
*/
|
317
|
+
Serializer.prototype.resource = function (attributeName, resource, serializer) {
|
318
|
+
this.nestedResources[attributeName] = resource;
|
319
|
+
|
320
|
+
if (serializer) {
|
321
|
+
this.serializeWith(attributeName, serializer);
|
322
|
+
}
|
323
|
+
|
324
|
+
return this;
|
325
|
+
};
|
326
|
+
|
327
|
+
/**
|
328
|
+
* Specifies a polymorphic association according to Rails' standards.
|
329
|
+
* Polymorphic associations have a <code>{name}_id</code> and <code>{name}_type</code> columns in the database.
|
330
|
+
*
|
331
|
+
* The <code>{name}_type</code> attribute will specify which resource will be used to serialize and deserialize the data.
|
332
|
+
*
|
333
|
+
* @param names... {string} Variable number of name parameters
|
334
|
+
* @returns {Serializer} this for chaining support
|
335
|
+
*/
|
336
|
+
Serializer.prototype.polymorphic = function () {
|
337
|
+
var polymorphics = this.polymorphics;
|
338
|
+
|
339
|
+
angular.forEach(arguments, function(attributeName) {
|
340
|
+
polymorphics[attributeName] = true;
|
341
|
+
});
|
342
|
+
|
343
|
+
return this;
|
344
|
+
};
|
345
|
+
|
346
|
+
/**
|
347
|
+
* Specifies a custom name mapping for an attribute.
|
348
|
+
* On serializing to JSON the jsonName will be used.
|
349
|
+
* On deserialization, if jsonName is seen then it will be renamed as javascriptName in the resulting resource.
|
350
|
+
*
|
351
|
+
* @param javascriptName {string} The attribute name as it appears in the JavaScript object
|
352
|
+
* @param jsonName {string} The attribute name as it should appear in JSON
|
353
|
+
* @param bidirectional {boolean} (optional) Allows turning off the bidirectional renaming, defaults to true.
|
354
|
+
* @returns {Serializer} this for chaining support
|
355
|
+
*/
|
356
|
+
Serializer.prototype.rename = function (javascriptName, jsonName, bidirectional) {
|
357
|
+
this.serializeMappings[javascriptName] = jsonName;
|
358
|
+
|
359
|
+
if (bidirectional || bidirectional === undefined) {
|
360
|
+
this.deserializeMappings[jsonName] = javascriptName;
|
361
|
+
}
|
362
|
+
return this;
|
363
|
+
};
|
364
|
+
|
365
|
+
/**
|
366
|
+
* Allows custom attribute creation as part of the serialization to JSON.
|
367
|
+
*
|
368
|
+
* @param attributeName {string} The name of the attribute to add
|
369
|
+
* @param value {*} The value to add, if specified as a function then the function will be called during serialization
|
370
|
+
* and should return the value to add.
|
371
|
+
* @returns {Serializer} this for chaining support
|
372
|
+
*/
|
373
|
+
Serializer.prototype.add = function (attributeName, value) {
|
374
|
+
this.customSerializedAttributes[attributeName] = value;
|
375
|
+
return this;
|
376
|
+
};
|
377
|
+
|
378
|
+
|
379
|
+
/**
|
380
|
+
* Allows the attribute to be preserved unmodified in the resulting object.
|
381
|
+
*
|
382
|
+
* @param attributeName {string} The name of the attribute to add
|
383
|
+
* @returns {Serializer} this for chaining support
|
384
|
+
*/
|
385
|
+
Serializer.prototype.preserve = function(attributeName) {
|
386
|
+
this.preservedAttributes[attributeName] = true;
|
387
|
+
return this;
|
388
|
+
};
|
389
|
+
|
390
|
+
/**
|
391
|
+
* Specify a custom serializer to use for an attribute.
|
392
|
+
*
|
393
|
+
* @param attributeName {string} The name of the attribute
|
394
|
+
* @param serializer {string | function} A reference to the custom serializer to use for the attribute.
|
395
|
+
* @returns {Serializer} this for chaining support
|
396
|
+
*/
|
397
|
+
Serializer.prototype.serializeWith = function (attributeName, serializer) {
|
398
|
+
this.customSerializers[attributeName] = serializer;
|
399
|
+
return this;
|
400
|
+
};
|
401
|
+
|
402
|
+
/**
|
403
|
+
* Determines whether or not an attribute should be excluded.
|
404
|
+
*
|
405
|
+
* If the option excludeByDefault has been set then attributes will default to excluded and will only
|
406
|
+
* be included if they have been included using the "only" customization function.
|
407
|
+
*
|
408
|
+
* If the option excludeByDefault has not been set then attributes must be explicitly excluded using the "exclude"
|
409
|
+
* customization function or must be matched by one of the exclusionMatchers.
|
410
|
+
*
|
411
|
+
* @param attributeName The name of the attribute to check for exclusion
|
412
|
+
* @returns {boolean} true if excluded, false otherwise
|
413
|
+
*/
|
414
|
+
Serializer.prototype.isExcludedFromSerialization = function (attributeName) {
|
415
|
+
if ((this.options.excludeByDefault && !this.inclusions.hasOwnProperty(attributeName)) || this.exclusions.hasOwnProperty(attributeName)) {
|
416
|
+
return true;
|
417
|
+
}
|
418
|
+
|
419
|
+
if (this.options.exclusionMatchers) {
|
420
|
+
var excluded = false;
|
421
|
+
|
422
|
+
angular.forEach(this.options.exclusionMatchers, function (matcher) {
|
423
|
+
if (angular.isString(matcher)) {
|
424
|
+
excluded = excluded || attributeName.indexOf(matcher) === 0;
|
425
|
+
} else if (angular.isFunction(matcher)) {
|
426
|
+
excluded = excluded || matcher.call(undefined, attributeName);
|
427
|
+
} else if (matcher instanceof RegExp) {
|
428
|
+
excluded = excluded || matcher.test(attributeName);
|
429
|
+
}
|
430
|
+
});
|
431
|
+
|
432
|
+
return excluded;
|
433
|
+
}
|
434
|
+
|
435
|
+
return false;
|
436
|
+
};
|
437
|
+
|
438
|
+
/**
|
439
|
+
* Remaps the attribute name to the serialized form which includes:
|
440
|
+
* - checking for exclusion
|
441
|
+
* - remapping to a custom value specified by the rename customization function
|
442
|
+
* - underscoring the name
|
443
|
+
*
|
444
|
+
* @param attributeName The current attribute name
|
445
|
+
* @returns {*} undefined if the attribute should be excluded or the mapped attribute name
|
446
|
+
*/
|
447
|
+
Serializer.prototype.getSerializedAttributeName = function (attributeName) {
|
448
|
+
var mappedName = this.serializeMappings[attributeName] || attributeName;
|
449
|
+
|
450
|
+
var mappedNameExcluded = this.isExcludedFromSerialization(mappedName),
|
451
|
+
attributeNameExcluded = this.isExcludedFromSerialization(attributeName);
|
452
|
+
|
453
|
+
if(this.options.excludeByDefault) {
|
454
|
+
if(mappedNameExcluded && attributeNameExcluded) {
|
455
|
+
return undefined;
|
456
|
+
}
|
457
|
+
} else {
|
458
|
+
if (mappedNameExcluded || attributeNameExcluded) {
|
459
|
+
return undefined;
|
460
|
+
}
|
461
|
+
}
|
462
|
+
|
463
|
+
return this.underscore(mappedName);
|
464
|
+
};
|
465
|
+
|
466
|
+
/**
|
467
|
+
* Determines whether or not an attribute should be excluded from deserialization.
|
468
|
+
*
|
469
|
+
* By default, we do not exclude any attributes from deserialization.
|
470
|
+
*
|
471
|
+
* @param attributeName The name of the attribute to check for exclusion
|
472
|
+
* @returns {boolean} true if excluded, false otherwise
|
473
|
+
*/
|
474
|
+
Serializer.prototype.isExcludedFromDeserialization = function (attributeName) {
|
475
|
+
return false;
|
476
|
+
};
|
477
|
+
|
478
|
+
/**
|
479
|
+
* Remaps the attribute name to the deserialized form which includes:
|
480
|
+
* - camelizing the name
|
481
|
+
* - checking for exclusion
|
482
|
+
* - remapping to a custom value specified by the rename customization function
|
483
|
+
*
|
484
|
+
* @param attributeName The current attribute name
|
485
|
+
* @returns {*} undefined if the attribute should be excluded or the mapped attribute name
|
486
|
+
*/
|
487
|
+
Serializer.prototype.getDeserializedAttributeName = function (attributeName) {
|
488
|
+
var camelizedName = this.camelize(attributeName);
|
489
|
+
|
490
|
+
camelizedName = this.deserializeMappings[attributeName] ||
|
491
|
+
this.deserializeMappings[camelizedName] ||
|
492
|
+
camelizedName;
|
493
|
+
|
494
|
+
if (this.isExcludedFromDeserialization(attributeName) || this.isExcludedFromDeserialization(camelizedName)) {
|
495
|
+
return undefined;
|
496
|
+
}
|
497
|
+
|
498
|
+
return camelizedName;
|
499
|
+
};
|
500
|
+
|
501
|
+
/**
|
502
|
+
* Returns a reference to the nested resource that has been specified for the attribute.
|
503
|
+
* @param attributeName The attribute name
|
504
|
+
* @param data the entire object being serialized
|
505
|
+
* @returns {*} undefined if no nested resource has been specified or a reference to the nested resource class
|
506
|
+
*/
|
507
|
+
Serializer.prototype.getNestedResource = function (attributeName, data) {
|
508
|
+
var resourceName;
|
509
|
+
if (!this.polymorphics[attributeName]) {
|
510
|
+
resourceName = this.nestedResources[attributeName];
|
511
|
+
} else {
|
512
|
+
resourceName = data[attributeName + '_type'];
|
513
|
+
}
|
514
|
+
return RailsResourceInjector.getDependency(resourceName);
|
515
|
+
};
|
516
|
+
|
517
|
+
/**
|
518
|
+
* Returns a custom serializer for the attribute if one has been specified. Custom serializers can be specified
|
519
|
+
* in one of two ways. The serializeWith customization method allows specifying a custom serializer for any attribute.
|
520
|
+
* Or an attribute could have been specified as a nested resource in which case the nested resource's serializer
|
521
|
+
* is used. Custom serializers specified using serializeWith take precedence over the nested resource serializer.
|
522
|
+
*
|
523
|
+
* @param attributeName The attribute name
|
524
|
+
* @param data the entire object being serialized
|
525
|
+
* @returns {*} undefined if no custom serializer has been specified or an instance of the Serializer
|
526
|
+
*/
|
527
|
+
Serializer.prototype.getAttributeSerializer = function (attributeName, data) {
|
528
|
+
var resource = this.getNestedResource(attributeName, data),
|
529
|
+
serializer = this.customSerializers[attributeName];
|
530
|
+
|
531
|
+
// custom serializer takes precedence over resource serializer
|
532
|
+
if (serializer) {
|
533
|
+
return RailsResourceInjector.createService(serializer);
|
534
|
+
} else if (resource) {
|
535
|
+
return resource.config.serializer;
|
536
|
+
}
|
537
|
+
|
538
|
+
return undefined;
|
539
|
+
};
|
540
|
+
|
541
|
+
|
542
|
+
/**
|
543
|
+
* Prepares the data for serialization to JSON.
|
544
|
+
*
|
545
|
+
* @param data The data to prepare
|
546
|
+
* @returns {*} A new object or array that is ready for JSON serialization
|
547
|
+
*/
|
548
|
+
Serializer.prototype.serializeData = function (data) {
|
549
|
+
var result = data,
|
550
|
+
self = this;
|
551
|
+
|
552
|
+
if (angular.isArray(data)) {
|
553
|
+
result = [];
|
554
|
+
|
555
|
+
angular.forEach(data, function (value) {
|
556
|
+
result.push(self.serializeData(value));
|
557
|
+
});
|
558
|
+
} else if (angular.isObject(data)) {
|
559
|
+
if (angular.isDate(data)) {
|
560
|
+
return data;
|
561
|
+
}
|
562
|
+
result = {};
|
563
|
+
|
564
|
+
this.serializeObject(result, data);
|
565
|
+
|
566
|
+
}
|
567
|
+
|
568
|
+
return result;
|
569
|
+
};
|
570
|
+
|
571
|
+
Serializer.prototype.serializeObject = function(result, data) {
|
572
|
+
|
573
|
+
|
574
|
+
var tthis = this;
|
575
|
+
angular.forEach(data, function (value, key) {
|
576
|
+
// if the value is a function then it can't be serialized to JSON so we'll just skip it
|
577
|
+
if (!angular.isFunction(value)) {
|
578
|
+
tthis.serializeAttribute(result, key, value, data);
|
579
|
+
}
|
580
|
+
});
|
581
|
+
return data;
|
582
|
+
};
|
583
|
+
|
584
|
+
/**
|
585
|
+
* Transforms an attribute and its value and stores it on the parent data object. The attribute will be
|
586
|
+
* renamed as needed and the value itself will be serialized as well.
|
587
|
+
*
|
588
|
+
* @param result The object that the attribute will be added to
|
589
|
+
* @param attribute The attribute to transform
|
590
|
+
* @param value The current value of the attribute
|
591
|
+
* @param data the entire object being serialized
|
592
|
+
*/
|
593
|
+
Serializer.prototype.serializeAttribute = function (result, attribute, value, data) {
|
594
|
+
var serializer = this.getAttributeSerializer(attribute, data),
|
595
|
+
serializedAttributeName = this.getSerializedAttributeName(attribute);
|
596
|
+
|
597
|
+
// undefined means the attribute should be excluded from serialization
|
598
|
+
if (serializedAttributeName === undefined) {
|
599
|
+
return;
|
600
|
+
}
|
601
|
+
|
602
|
+
result[serializedAttributeName] = serializer ? serializer.serialize(value) : this.serializeData(value);
|
603
|
+
};
|
604
|
+
|
605
|
+
/**
|
606
|
+
* Serializes the data by applying various transformations such as:
|
607
|
+
* - Underscoring attribute names
|
608
|
+
* - attribute renaming
|
609
|
+
* - attribute exclusion
|
610
|
+
* - custom attribute addition
|
611
|
+
*
|
612
|
+
* @param data The data to prepare
|
613
|
+
* @returns {*} A new object or array that is ready for JSON serialization
|
614
|
+
*/
|
615
|
+
Serializer.prototype.serialize = function (data) {
|
616
|
+
var result = angular.copy(data),
|
617
|
+
self = this;
|
618
|
+
|
619
|
+
if (angular.isObject(result)) {
|
620
|
+
angular.forEach(this.customSerializedAttributes, function (value, key) {
|
621
|
+
if (angular.isArray(result)) {
|
622
|
+
angular.forEach(result, function (item, index) {
|
623
|
+
var itemValue = value;
|
624
|
+
if (angular.isFunction(value)) {
|
625
|
+
itemValue = itemValue.call(item, item);
|
626
|
+
}
|
627
|
+
|
628
|
+
self.serializeAttribute(item, key, itemValue, data);
|
629
|
+
});
|
630
|
+
} else {
|
631
|
+
if (angular.isFunction(value)) {
|
632
|
+
value = value.call(data, data);
|
633
|
+
}
|
634
|
+
|
635
|
+
self.serializeAttribute(result, key, value, data);
|
636
|
+
}
|
637
|
+
});
|
638
|
+
}
|
639
|
+
|
640
|
+
result = this.serializeData(result);
|
641
|
+
|
642
|
+
return result;
|
643
|
+
};
|
644
|
+
|
645
|
+
/**
|
646
|
+
* Iterates over the data deserializing each entry on arrays and each key/value on objects.
|
647
|
+
*
|
648
|
+
* @param data The object to deserialize
|
649
|
+
* @param Resource (optional) The resource type to deserialize the result into
|
650
|
+
* @param triggerPhase (optional) Whether to trigger the afterDeserialize phase
|
651
|
+
* @returns {*} A new object or an instance of Resource populated with deserialized data.
|
652
|
+
*/
|
653
|
+
Serializer.prototype.deserializeData = function (data, Resource, triggerPhase) {
|
654
|
+
var result = data,
|
655
|
+
self = this;
|
656
|
+
|
657
|
+
if (angular.isArray(data)) {
|
658
|
+
result = [];
|
659
|
+
|
660
|
+
angular.forEach(data, function (value) {
|
661
|
+
result.push(self.deserializeData(value, Resource, triggerPhase));
|
662
|
+
});
|
663
|
+
} else if (angular.isObject(data)) {
|
664
|
+
if (angular.isDate(data)) {
|
665
|
+
return data;
|
666
|
+
}
|
667
|
+
result = {};
|
668
|
+
|
669
|
+
if (Resource) {
|
670
|
+
result = new Resource.config.resourceConstructor();
|
671
|
+
}
|
672
|
+
|
673
|
+
this.deserializeObject(result, data, triggerPhase);
|
674
|
+
|
675
|
+
}
|
676
|
+
|
677
|
+
return result;
|
678
|
+
};
|
679
|
+
|
680
|
+
Serializer.prototype.deserializeObject = function (result, data, triggerPhase) {
|
681
|
+
|
682
|
+
var tthis = this;
|
683
|
+
angular.forEach(data, function (value, key) {
|
684
|
+
tthis.deserializeAttribute(result, key, value, data);
|
685
|
+
});
|
686
|
+
if (triggerPhase && result.constructor.runInterceptorPhase) {
|
687
|
+
result.constructor.runInterceptorPhase('afterDeserialize', result);
|
688
|
+
}
|
689
|
+
|
690
|
+
return data;
|
691
|
+
};
|
692
|
+
|
693
|
+
|
694
|
+
/**
|
695
|
+
* Transforms an attribute and its value and stores it on the parent data object. The attribute will be
|
696
|
+
* renamed as needed and the value itself will be deserialized as well.
|
697
|
+
*
|
698
|
+
* @param result The object that the attribute will be added to
|
699
|
+
* @param attribute The attribute to transform
|
700
|
+
* @param value The current value of the attribute
|
701
|
+
* @param data the entire object being deserialized
|
702
|
+
*/
|
703
|
+
Serializer.prototype.deserializeAttribute = function (result, attribute, value, data) {
|
704
|
+
var serializer,
|
705
|
+
NestedResource,
|
706
|
+
attributeName = this.getDeserializedAttributeName(attribute);
|
707
|
+
|
708
|
+
// undefined means the attribute should be excluded from serialization
|
709
|
+
if (attributeName === undefined) {
|
710
|
+
return;
|
711
|
+
}
|
712
|
+
|
713
|
+
serializer = this.getAttributeSerializer(attributeName, data);
|
714
|
+
NestedResource = this.getNestedResource(attributeName, data);
|
715
|
+
|
716
|
+
// preserved attributes are assigned unmodified
|
717
|
+
if (this.preservedAttributes[attributeName]) {
|
718
|
+
result[attributeName] = value;
|
719
|
+
} else {
|
720
|
+
result[attributeName] = serializer ? serializer.deserialize(value, NestedResource, true) : this.deserializeData(value, NestedResource, true);
|
721
|
+
}
|
722
|
+
};
|
723
|
+
|
724
|
+
/**
|
725
|
+
* Deserializes the data by applying various transformations such as:
|
726
|
+
* - Camelizing attribute names
|
727
|
+
* - attribute renaming
|
728
|
+
* - attribute exclusion
|
729
|
+
* - nested resource creation
|
730
|
+
*
|
731
|
+
* @param data The object to deserialize
|
732
|
+
* @param Resource (optional) The resource type to deserialize the result into
|
733
|
+
* @param triggerPhase (optional) Whether to trigger the afterDeserialize phase
|
734
|
+
* @returns {*} A new object or an instance of Resource populated with deserialized data
|
735
|
+
*/
|
736
|
+
Serializer.prototype.deserialize = function (data, Resource, triggerPhase) {
|
737
|
+
// just calls deserializeValue for now so we can more easily add on custom attribute logic for deserialize too
|
738
|
+
return this.deserializeData(data, Resource, triggerPhase);
|
739
|
+
};
|
740
|
+
|
741
|
+
Serializer.prototype.pluralize = function (value) {
|
742
|
+
if (this.options.pluralize) {
|
743
|
+
return this.options.pluralize(value);
|
744
|
+
}
|
745
|
+
return value;
|
746
|
+
};
|
747
|
+
|
748
|
+
Serializer.prototype.underscore = function (value) {
|
749
|
+
if (this.options.underscore) {
|
750
|
+
return this.options.underscore(value);
|
751
|
+
}
|
752
|
+
return value;
|
753
|
+
};
|
754
|
+
|
755
|
+
Serializer.prototype.camelize = function (value) {
|
756
|
+
if (this.options.camelize) {
|
757
|
+
return this.options.camelize(value);
|
758
|
+
}
|
759
|
+
return value;
|
760
|
+
};
|
761
|
+
|
762
|
+
return Serializer;
|
763
|
+
}
|
764
|
+
|
765
|
+
railsSerializer.defaultOptions = defaultOptions;
|
766
|
+
return railsSerializer;
|
767
|
+
}];
|
768
|
+
});
|
769
|
+
}());
|
770
|
+
|
771
|
+
(function (undefined) {
|
772
|
+
angular.module('rails').factory('railsRootWrapper', function () {
|
773
|
+
return {
|
774
|
+
wrap: function (data, resource) {
|
775
|
+
var result = {};
|
776
|
+
result[angular.isArray(data) ? resource.config.pluralName : resource.config.name] = data;
|
777
|
+
return result;
|
778
|
+
},
|
779
|
+
unwrap: function (response, resource, isObject) {
|
780
|
+
if (response.data && response.data.hasOwnProperty(resource.config.name)) {
|
781
|
+
response.data = response.data[resource.config.name];
|
782
|
+
} else if (response.data && response.data.hasOwnProperty(resource.config.pluralName) && !isObject) {
|
783
|
+
response.data = response.data[resource.config.pluralName];
|
784
|
+
}
|
785
|
+
|
786
|
+
return response;
|
787
|
+
}
|
788
|
+
};
|
789
|
+
});
|
790
|
+
|
791
|
+
angular.module('rails').provider('RailsResource', function () {
|
792
|
+
var defaultOptions = {
|
793
|
+
rootWrapping: true,
|
794
|
+
updateMethod: 'put',
|
795
|
+
httpConfig: {},
|
796
|
+
defaultParams: undefined,
|
797
|
+
underscoreParams: true,
|
798
|
+
fullResponse: false,
|
799
|
+
singular: false,
|
800
|
+
extensions: []
|
801
|
+
};
|
802
|
+
|
803
|
+
/**
|
804
|
+
* Enables or disables root wrapping by default for RailsResources
|
805
|
+
* Defaults to true.
|
806
|
+
* @param {boolean} value true to enable root wrapping, false to disable
|
807
|
+
* @returns {RailsResourceProvider} The provider instance
|
808
|
+
*/
|
809
|
+
this.rootWrapping = function (value) {
|
810
|
+
defaultOptions.rootWrapping = value;
|
811
|
+
return this;
|
812
|
+
};
|
813
|
+
|
814
|
+
/**
|
815
|
+
* Configures what HTTP operation should be used for update by default for RailsResources.
|
816
|
+
* Defaults to 'put'
|
817
|
+
* @param value
|
818
|
+
* @returns {RailsResourceProvider} The provider instance
|
819
|
+
*/
|
820
|
+
this.updateMethod = function (value) {
|
821
|
+
defaultOptions.updateMethod = value;
|
822
|
+
return this;
|
823
|
+
};
|
824
|
+
|
825
|
+
/**
|
826
|
+
* Configures default HTTP configuration operations for all RailsResources.
|
827
|
+
*
|
828
|
+
* @param {Object} value See $http for available configuration options.
|
829
|
+
* @returns {RailsResourceProvider} The provider instance
|
830
|
+
*/
|
831
|
+
this.httpConfig = function (value) {
|
832
|
+
defaultOptions.httpConfig = value;
|
833
|
+
return this;
|
834
|
+
};
|
835
|
+
|
836
|
+
/**
|
837
|
+
* Configures default HTTP query parameters for all RailsResources.
|
838
|
+
*
|
839
|
+
* @param {Object} value Object of key/value pairs representing the HTTP query parameters for all HTTP operations.
|
840
|
+
* @returns {RailsResourceProvider} The provider instance
|
841
|
+
*/
|
842
|
+
this.defaultParams = function (value) {
|
843
|
+
defaultOptions.defaultParams = value;
|
844
|
+
return this;
|
845
|
+
};
|
846
|
+
|
847
|
+
/**
|
848
|
+
* Configures whether or not underscore query parameters
|
849
|
+
* @param {boolean} value true to underscore. Defaults to true.
|
850
|
+
* @returns {RailsResourceProvider} The provider instance
|
851
|
+
*/
|
852
|
+
this.underscoreParams = function (value) {
|
853
|
+
defaultOptions.underscoreParams = value;
|
854
|
+
return this;
|
855
|
+
};
|
856
|
+
|
857
|
+
/**
|
858
|
+
* Configures whether the full response from $http is returned or just the result data.
|
859
|
+
* @param {boolean} value true to return full $http response. Defaults to false.
|
860
|
+
* @returns {RailsResourceProvider} The provider instance
|
861
|
+
*/
|
862
|
+
this.fullResponse = function (value) {
|
863
|
+
defaultOptions.fullResponse = value;
|
864
|
+
return this;
|
865
|
+
};
|
866
|
+
|
867
|
+
/**
|
868
|
+
* List of RailsResource extensions to include by default.
|
869
|
+
*
|
870
|
+
* @param {...string} extensions One or more extension names to include
|
871
|
+
* @returns {*}
|
872
|
+
*/
|
873
|
+
this.extensions = function () {
|
874
|
+
defaultOptions.extensions = [];
|
875
|
+
angular.forEach(arguments, function (value) {
|
876
|
+
defaultOptions.extensions = defaultOptions.extensions.concat(value);
|
877
|
+
});
|
878
|
+
return this;
|
879
|
+
};
|
880
|
+
|
881
|
+
this.$get = ['$http', '$q', '$timeout', 'railsUrlBuilder', 'railsSerializer', 'railsRootWrapper', 'RailsResourceInjector',
|
882
|
+
function ($http, $q, $timeout, railsUrlBuilder, railsSerializer, railsRootWrapper, RailsResourceInjector) {
|
883
|
+
|
884
|
+
function RailsResource(value) {
|
885
|
+
if (value) {
|
886
|
+
var response = this.constructor.deserialize({data: value});
|
887
|
+
if (this.constructor.config.rootWrapping) {
|
888
|
+
response = railsRootWrapper.unwrap(response, this.constructor, true);
|
889
|
+
}
|
890
|
+
angular.extend(this, response.data);
|
891
|
+
this.constructor.runInterceptorPhase('afterDeserialize', this);
|
892
|
+
}
|
893
|
+
}
|
894
|
+
|
895
|
+
/**
|
896
|
+
* Extends the RailsResource to the child constructor function making the child constructor a subclass of
|
897
|
+
* RailsResource. This is modeled off of CoffeeScript's class extend function. All RailsResource
|
898
|
+
* class properties defined are copied to the child class and the child's prototype chain is configured
|
899
|
+
* to allow instances of the child class to have all of the instance methods of RailsResource.
|
900
|
+
*
|
901
|
+
* Like CoffeeScript, a __super__ property is set on the child class to the parent resource's prototype chain.
|
902
|
+
* This is done to allow subclasses to extend the functionality of instance methods and still
|
903
|
+
* call back to the original method using:
|
904
|
+
*
|
905
|
+
* Class.__super__.method.apply(this, arguments);
|
906
|
+
*
|
907
|
+
* @param {function} child Child constructor function
|
908
|
+
* @returns {function} Child constructor function
|
909
|
+
*/
|
910
|
+
RailsResource.extendTo = function (child) {
|
911
|
+
angular.forEach(this, function (value, key) {
|
912
|
+
child[key] = value;
|
913
|
+
});
|
914
|
+
|
915
|
+
if (angular.isArray(this.$modules)) {
|
916
|
+
child.$modules = this.$modules.slice(0);
|
917
|
+
}
|
918
|
+
|
919
|
+
function ctor() {
|
920
|
+
this.constructor = child;
|
921
|
+
}
|
922
|
+
|
923
|
+
ctor.prototype = this.prototype;
|
924
|
+
child.prototype = new ctor();
|
925
|
+
child.__super__ = this.prototype;
|
926
|
+
return child;
|
927
|
+
};
|
928
|
+
|
929
|
+
/**
|
930
|
+
* Copies a mixin's properties to the resource.
|
931
|
+
*
|
932
|
+
* If module is a String then we it will be loaded using Angular's dependency injection. If the name is
|
933
|
+
* not valid then Angular will throw an error.
|
934
|
+
*
|
935
|
+
* @param {...String|function|Object} mixins The mixin or name of the mixin to add.
|
936
|
+
* @returns {RailsResource} this
|
937
|
+
*/
|
938
|
+
RailsResource.extend = function () {
|
939
|
+
angular.forEach(arguments, function (mixin) {
|
940
|
+
addMixin(this, this, mixin, function (Resource, mixin) {
|
941
|
+
if (angular.isFunction(mixin.extended)) {
|
942
|
+
mixin.extended(Resource);
|
943
|
+
}
|
944
|
+
});
|
945
|
+
}, this);
|
946
|
+
|
947
|
+
return this;
|
948
|
+
};
|
949
|
+
|
950
|
+
/**
|
951
|
+
* Copies a mixin's properties to the resource's prototype chain.
|
952
|
+
*
|
953
|
+
* If module is a String then we it will be loaded using Angular's dependency injection. If the name is
|
954
|
+
* not valid then Angular will throw an error.
|
955
|
+
*
|
956
|
+
* @param {...String|function|Object} mixins The mixin or name of the mixin to add
|
957
|
+
* @returns {RailsResource} this
|
958
|
+
*/
|
959
|
+
RailsResource.include = function () {
|
960
|
+
angular.forEach(arguments, function (mixin) {
|
961
|
+
addMixin(this, this.prototype, mixin, function (Resource, mixin) {
|
962
|
+
if (angular.isFunction(mixin.included)) {
|
963
|
+
mixin.included(Resource);
|
964
|
+
}
|
965
|
+
});
|
966
|
+
}, this);
|
967
|
+
|
968
|
+
return this;
|
969
|
+
};
|
970
|
+
|
971
|
+
/**
|
972
|
+
* Sets configuration options. This method may be called multiple times to set additional options or to
|
973
|
+
* override previous values (such as the case with inherited resources).
|
974
|
+
* @param cfg
|
975
|
+
*/
|
976
|
+
RailsResource.configure = function (cfg) {
|
977
|
+
cfg = cfg || {};
|
978
|
+
|
979
|
+
if (this.config) {
|
980
|
+
cfg = angular.extend({}, this.config, cfg);
|
981
|
+
}
|
982
|
+
|
983
|
+
this.config = {};
|
984
|
+
this.config.idAttribute = cfg.idAttribute || 'id';
|
985
|
+
this.config.url = cfg.url;
|
986
|
+
this.config.rootWrapping = booleanParam(cfg.rootWrapping, defaultOptions.rootWrapping); // using undefined check because config.rootWrapping || true would be true when config.rootWrapping === false
|
987
|
+
this.config.httpConfig = cfg.httpConfig || defaultOptions.httpConfig;
|
988
|
+
this.config.httpConfig.headers = angular.extend({'Accept': 'application/json', 'Content-Type': 'application/json'}, this.config.httpConfig.headers || {});
|
989
|
+
this.config.defaultParams = cfg.defaultParams || defaultOptions.defaultParams;
|
990
|
+
this.config.underscoreParams = booleanParam(cfg.underscoreParams, defaultOptions.underscoreParams);
|
991
|
+
this.config.updateMethod = (cfg.updateMethod || defaultOptions.updateMethod).toLowerCase();
|
992
|
+
this.config.fullResponse = booleanParam(cfg.fullResponse, defaultOptions.fullResponse);
|
993
|
+
this.config.singular = cfg.singular || defaultOptions.singular;
|
994
|
+
|
995
|
+
this.config.requestTransformers = cfg.requestTransformers ? cfg.requestTransformers.slice(0) : [];
|
996
|
+
this.config.responseInterceptors = cfg.responseInterceptors ? cfg.responseInterceptors.slice(0) : [];
|
997
|
+
this.config.afterResponseInterceptors = cfg.afterResponseInterceptors ? cfg.afterResponseInterceptors.slice(0) : [];
|
998
|
+
this.config.interceptors = cfg.interceptors ? cfg.interceptors.slice(0) : [];
|
999
|
+
|
1000
|
+
this.config.serializer = RailsResourceInjector.getService(cfg.serializer || railsSerializer());
|
1001
|
+
|
1002
|
+
this.config.name = this.config.serializer.underscore(cfg.name);
|
1003
|
+
|
1004
|
+
// we don't want to turn undefined name into "undefineds" then the plural name won't update when the name is set
|
1005
|
+
if (this.config.name) {
|
1006
|
+
this.config.pluralName = this.config.serializer.underscore(cfg.pluralName || this.config.serializer.pluralize(this.config.name));
|
1007
|
+
}
|
1008
|
+
|
1009
|
+
this.config.urlBuilder = railsUrlBuilder(this.config);
|
1010
|
+
this.config.resourceConstructor = this;
|
1011
|
+
|
1012
|
+
this.extend.apply(this, loadExtensions((cfg.extensions || []).concat(defaultOptions.extensions)));
|
1013
|
+
|
1014
|
+
angular.forEach(this.$mixins, function (mixin) {
|
1015
|
+
if (angular.isFunction(mixin.configure)) {
|
1016
|
+
mixin.configure(this.config, cfg);
|
1017
|
+
}
|
1018
|
+
}, this);
|
1019
|
+
|
1020
|
+
return this.config;
|
1021
|
+
};
|
1022
|
+
|
1023
|
+
/**
|
1024
|
+
* Configures the URL for the resource.
|
1025
|
+
* @param {String|function} url The url string or function.
|
1026
|
+
*/
|
1027
|
+
RailsResource.setUrl = function (url) {
|
1028
|
+
this.configure({url: url});
|
1029
|
+
};
|
1030
|
+
|
1031
|
+
RailsResource.buildUrl = function (context) {
|
1032
|
+
return this.config.urlBuilder(context);
|
1033
|
+
};
|
1034
|
+
|
1035
|
+
/**
|
1036
|
+
* Interceptors utilize $q promises to allow for both synchronous and asynchronous processing during
|
1037
|
+
* a request / response cycle.
|
1038
|
+
*
|
1039
|
+
* Interceptors can be added as a service factory name or as an object with properties matching one
|
1040
|
+
* or more of the phases. Each property should have a value of a function to be called during that phase.
|
1041
|
+
*
|
1042
|
+
* There are multiple phases for both request and response. In addition, each phase has a corresponding
|
1043
|
+
* error phase to handle promise rejections.
|
1044
|
+
*
|
1045
|
+
* Each request phase interceptor is called with the $http config object, the resource constructor, and if
|
1046
|
+
* applicable the resource instance. The interceptor is free to modify the config or create a new one.
|
1047
|
+
* The interceptor function must return a valid $http config or a promise that will eventually resolve
|
1048
|
+
* to a config object.
|
1049
|
+
*
|
1050
|
+
* The valid request phases are:
|
1051
|
+
*
|
1052
|
+
* * beforeRequest: Interceptors are called prior to any data serialization or root wrapping.
|
1053
|
+
* * beforeRequestError: Interceptors get called when a previous interceptor threw an error or
|
1054
|
+
* resolved with a rejection.
|
1055
|
+
* * beforeRequestWrapping: Interceptors are called after data serialization but before root wrapping.
|
1056
|
+
* * beforeRequestWrappingError: Interceptors get called when a previous interceptor threw an error or
|
1057
|
+
* resolved with a rejection.
|
1058
|
+
* * request: Interceptors are called after any data serialization and root wrapping have been performed.
|
1059
|
+
* * requestError: Interceptors get called when a previous interceptor threw an error or
|
1060
|
+
* resolved with a rejection.
|
1061
|
+
*
|
1062
|
+
* The beforeResponse and response interceptors are called with the $http response object,
|
1063
|
+
* the resource constructor, and if applicable the resource instance. The afterResponse interceptors
|
1064
|
+
* are typically called with the response data instead of the full response object unless the config option
|
1065
|
+
* fullResponse has been set to true. Like the request interceptor callbacks the response callbacks can
|
1066
|
+
* manipulate the data or return new data. The interceptor function must return
|
1067
|
+
*
|
1068
|
+
* The valid response phases are:
|
1069
|
+
*
|
1070
|
+
* * beforeResponse: Interceptors are called prior to any data processing.
|
1071
|
+
* * beforeResponseError: Interceptors get called when a previous interceptor threw an error or
|
1072
|
+
* resolved with a rejection.
|
1073
|
+
* * beforeResponseDeserialize: Interceptors are called after root unwrapping but prior to data deserializing.
|
1074
|
+
* * beforeResponseDeserializeError: Interceptors get called when a previous interceptor threw an error or
|
1075
|
+
* resolved with a rejection.
|
1076
|
+
* * response: Interceptors are called after the data has been deserialized and root unwrapped but
|
1077
|
+
* prior to the data being copied to the resource instance if applicable.
|
1078
|
+
* * responseError: Interceptors get called when a previous interceptor threw an error or
|
1079
|
+
* resolved with a rejection.
|
1080
|
+
* * afterResponse: Interceptors are called at the very end of the response chain after all processing
|
1081
|
+
* has been completed. The value of the first parameter is one of the following:
|
1082
|
+
* - resource instance: When fullResponse is false and the operation was called on a resource instance.
|
1083
|
+
* - response data: When fullResponse is false and the operation was called on the resource class.
|
1084
|
+
* - $http response: When fullResponse is true
|
1085
|
+
* * afterResponseError: Interceptors get called when a previous interceptor threw an error or
|
1086
|
+
* resolved with a rejection.
|
1087
|
+
*
|
1088
|
+
* Finally, for each deserialized resource including associations, deserialization phases are called.
|
1089
|
+
*
|
1090
|
+
* The valid deserialization phases are:
|
1091
|
+
*
|
1092
|
+
* * afterDeserialize: Interceptors are called after a resource has been deserialized.
|
1093
|
+
*
|
1094
|
+
* @param {String | Object} interceptor
|
1095
|
+
*/
|
1096
|
+
RailsResource.addInterceptor = function (interceptor) {
|
1097
|
+
this.config.interceptors.push(interceptor);
|
1098
|
+
};
|
1099
|
+
|
1100
|
+
/**
|
1101
|
+
* Adds an interceptor callback function for the specified phase.
|
1102
|
+
* @param {String} phase The interceptor phase, one of:
|
1103
|
+
* beforeRequest, request, beforeResponse, response, afterResponse, afterDeserialize
|
1104
|
+
* @param fn The function to call.
|
1105
|
+
*/
|
1106
|
+
RailsResource.intercept = function (phase, fn) {
|
1107
|
+
var interceptor = {};
|
1108
|
+
fn = RailsResourceInjector.getDependency(fn);
|
1109
|
+
|
1110
|
+
interceptor[phase] = function (value, resourceConstructor, context) {
|
1111
|
+
return fn(value, resourceConstructor, context) || value;
|
1112
|
+
};
|
1113
|
+
|
1114
|
+
this.addInterceptor(interceptor);
|
1115
|
+
};
|
1116
|
+
|
1117
|
+
/**
|
1118
|
+
* Adds interceptor on 'beforeRequest' phase.
|
1119
|
+
* @param fn(httpConfig, constructor, context) - httpConfig is the config object to pass to $http,
|
1120
|
+
* constructor is the resource class calling the function,
|
1121
|
+
* context is the resource instance of the calling method (create, update, delete) or undefined if the method was a class method (get, query)
|
1122
|
+
*/
|
1123
|
+
RailsResource.interceptBeforeRequest = function (fn) {
|
1124
|
+
this.intercept('beforeRequest', fn);
|
1125
|
+
};
|
1126
|
+
|
1127
|
+
/**
|
1128
|
+
* Adds interceptor on 'beforeRequestWrapping' phase.
|
1129
|
+
* @param fn(httpConfig, constructor, context) - httpConfig is the config object to pass to $http,
|
1130
|
+
* constructor is the resource class calling the function,
|
1131
|
+
* context is the resource instance of the calling method (create, update, delete) or undefined if the method was a class method (get, query)
|
1132
|
+
*/
|
1133
|
+
RailsResource.interceptBeforeRequestWrapping = function (fn) {
|
1134
|
+
this.intercept('beforeRequestWrapping', fn);
|
1135
|
+
};
|
1136
|
+
|
1137
|
+
/**
|
1138
|
+
* Adds interceptor on 'request' phase.
|
1139
|
+
* @param fn(httpConfig, constructor, context) - httpConfig is the config object to pass to $http,
|
1140
|
+
* constructor is the resource class calling the function,
|
1141
|
+
* context is the resource instance of the calling method (create, update, delete) or undefined if the method was a class method (get, query)
|
1142
|
+
*/
|
1143
|
+
RailsResource.interceptRequest = function (fn) {
|
1144
|
+
this.intercept('request', fn);
|
1145
|
+
};
|
1146
|
+
|
1147
|
+
/**
|
1148
|
+
* Adds interceptor on 'beforeResponse' phase.
|
1149
|
+
* @param fn(response data, constructor, context) - response data is either the resource instance returned or an array of resource instances,
|
1150
|
+
* constructor is the resource class calling the function,
|
1151
|
+
* context is the resource instance of the calling method (create, update, delete) or undefined if the method was a class method (get, query)
|
1152
|
+
*/
|
1153
|
+
RailsResource.interceptBeforeResponse = function (fn) {
|
1154
|
+
this.intercept('beforeResponse', fn);
|
1155
|
+
};
|
1156
|
+
|
1157
|
+
/**
|
1158
|
+
* Adds interceptor on 'beforeResponseDeserialize' phase.
|
1159
|
+
* @param fn(response data, constructor, context) - response data is either the resource instance returned or an array of resource instances,
|
1160
|
+
* constructor is the resource class calling the function,
|
1161
|
+
* context is the resource instance of the calling method (create, update, delete) or undefined if the method was a class method (get, query)
|
1162
|
+
*/
|
1163
|
+
RailsResource.interceptBeforeResponseDeserialize = function (fn) {
|
1164
|
+
this.intercept('beforeResponseDeserialize', fn);
|
1165
|
+
};
|
1166
|
+
|
1167
|
+
/**
|
1168
|
+
* Adds interceptor on 'response' phase.
|
1169
|
+
* @param fn(response data, constructor, context) - response data is either the resource instance returned or an array of resource instances,
|
1170
|
+
* constructor is the resource class calling the function,
|
1171
|
+
* context is the resource instance of the calling method (create, update, delete) or undefined if the method was a class method (get, query)
|
1172
|
+
*/
|
1173
|
+
RailsResource.interceptResponse = function (fn) {
|
1174
|
+
this.intercept('response', fn);
|
1175
|
+
};
|
1176
|
+
|
1177
|
+
/**
|
1178
|
+
* Adds interceptor on 'afterResponse' phase.
|
1179
|
+
* @param fn(response data, constructor, context) - response data is either the resource instance returned or an array of resource instances,
|
1180
|
+
* constructor is the resource class calling the function,
|
1181
|
+
* context is the resource instance of the calling method (create, update, delete) or undefined if the method was a class method (get, query)
|
1182
|
+
*/
|
1183
|
+
RailsResource.interceptAfterResponse = function (fn) {
|
1184
|
+
this.intercept('afterResponse', fn);
|
1185
|
+
};
|
1186
|
+
|
1187
|
+
/**
|
1188
|
+
* Adds interceptor on 'afterDeserialize' phase.
|
1189
|
+
* @param fn(response data, constructor, context) - response data is either the resource instance returned or an array of resource instances,
|
1190
|
+
* constructor is the resource class calling the function,
|
1191
|
+
* context is the resource instance of the calling method (create, update, delete) or undefined if the method was a class method (get, query)
|
1192
|
+
*/
|
1193
|
+
RailsResource.interceptAfterDeserialize = function (fn) {
|
1194
|
+
this.intercept('afterDeserialize', fn);
|
1195
|
+
};
|
1196
|
+
|
1197
|
+
/**
|
1198
|
+
* Deprecated, see interceptors
|
1199
|
+
* Add a callback to run on response.
|
1200
|
+
* @deprecated since version 1.0.0, use interceptResponse instead
|
1201
|
+
* @param fn(response data, constructor, context) - response data is either the resource instance returned or an array of resource instances,
|
1202
|
+
* constructor is the resource class calling the function,
|
1203
|
+
* context is the resource instance of the calling method (create, update, delete) or undefined if the method was a class method (get, query)
|
1204
|
+
*/
|
1205
|
+
RailsResource.beforeResponse = function (fn) {
|
1206
|
+
fn = RailsResourceInjector.getDependency(fn);
|
1207
|
+
this.interceptResponse(function (response, resource, context) {
|
1208
|
+
fn(response.data, resource.config.resourceConstructor, context);
|
1209
|
+
return response;
|
1210
|
+
});
|
1211
|
+
};
|
1212
|
+
|
1213
|
+
/**
|
1214
|
+
* Deprecated, see interceptors
|
1215
|
+
* Add a callback to run after response has been processed. These callbacks are not called on object construction.
|
1216
|
+
* @deprecated since version 1.0.0, use interceptAfterResponse instead
|
1217
|
+
* @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
|
1218
|
+
*/
|
1219
|
+
RailsResource.afterResponse = function (fn) {
|
1220
|
+
fn = RailsResourceInjector.getDependency(fn);
|
1221
|
+
this.interceptAfterResponse(function (response, resource, context) {
|
1222
|
+
fn(response, resource.config.resourceConstructor, context);
|
1223
|
+
return response;
|
1224
|
+
});
|
1225
|
+
};
|
1226
|
+
|
1227
|
+
/**
|
1228
|
+
* Deprecated, see interceptors
|
1229
|
+
* Adds a function to run after serializing the data to send to the server, but before root-wrapping it.
|
1230
|
+
* @deprecated since version 1.0.0, use interceptBeforeRequestWrapping instead
|
1231
|
+
* @param fn (data, constructor) - data object is the serialized resource instance, and constructor the resource class calling the function
|
1232
|
+
*/
|
1233
|
+
RailsResource.beforeRequest = function (fn) {
|
1234
|
+
fn = RailsResourceInjector.getDependency(fn);
|
1235
|
+
this.interceptBeforeRequestWrapping(function (httpConfig, resource) {
|
1236
|
+
httpConfig.data = fn(httpConfig.data, resource.config.resourceConstructor) || httpConfig.data;
|
1237
|
+
return httpConfig;
|
1238
|
+
});
|
1239
|
+
};
|
1240
|
+
|
1241
|
+
RailsResource.serialize = function (httpConfig) {
|
1242
|
+
if (httpConfig.data) {
|
1243
|
+
httpConfig.data = this.config.serializer.serialize(httpConfig.data);
|
1244
|
+
}
|
1245
|
+
|
1246
|
+
return httpConfig;
|
1247
|
+
};
|
1248
|
+
|
1249
|
+
/**
|
1250
|
+
* Deserializes the response data on the $http response. Stores the original version of the data
|
1251
|
+
* on the response as "originalData" and sets the deserialized data in the "data" property.
|
1252
|
+
* @param response The $http response object
|
1253
|
+
* @returns {*} The $http response
|
1254
|
+
*/
|
1255
|
+
RailsResource.deserialize = function (response) {
|
1256
|
+
response.data = this.config.serializer.deserialize(response.data, this.config.resourceConstructor);
|
1257
|
+
return response;
|
1258
|
+
};
|
1259
|
+
|
1260
|
+
/**
|
1261
|
+
* Deprecated, see interceptors
|
1262
|
+
* Transform data after response has been converted to a resource instance
|
1263
|
+
* @deprecated
|
1264
|
+
* @param promise
|
1265
|
+
* @param context
|
1266
|
+
*/
|
1267
|
+
RailsResource.callResponseInterceptors = function (promise, context) {
|
1268
|
+
var config = this.config;
|
1269
|
+
forEachDependency(config.responseInterceptors, function (interceptor) {
|
1270
|
+
promise.resource = config.resourceConstructor;
|
1271
|
+
promise.context = context;
|
1272
|
+
promise = interceptor(promise);
|
1273
|
+
});
|
1274
|
+
return promise;
|
1275
|
+
};
|
1276
|
+
|
1277
|
+
/**
|
1278
|
+
* Deprecated, see interceptors
|
1279
|
+
* Transform data after response has been converted to a resource instance
|
1280
|
+
* @deprecated
|
1281
|
+
* @param promise
|
1282
|
+
* @param context
|
1283
|
+
*/
|
1284
|
+
RailsResource.callAfterResponseInterceptors = function (promise) {
|
1285
|
+
var config = this.config;
|
1286
|
+
// data is now deserialized. call response interceptors including afterResponse
|
1287
|
+
forEachDependency(config.afterResponseInterceptors, function (interceptor) {
|
1288
|
+
promise.resource = config.resourceConstructor;
|
1289
|
+
promise = interceptor(promise);
|
1290
|
+
});
|
1291
|
+
|
1292
|
+
return promise;
|
1293
|
+
};
|
1294
|
+
|
1295
|
+
RailsResource.runInterceptorPhase = function (phase, context, promise) {
|
1296
|
+
promise = promise || $q.resolve(context);
|
1297
|
+
var config = this.config, chain = [];
|
1298
|
+
|
1299
|
+
forEachDependency(config.interceptors, function (interceptor) {
|
1300
|
+
if (interceptor[phase] || interceptor[phase + 'Error']) {
|
1301
|
+
chain.push(interceptor[phase], interceptor[phase + 'Error']);
|
1302
|
+
}
|
1303
|
+
});
|
1304
|
+
|
1305
|
+
while (chain.length) {
|
1306
|
+
var thenFn = chain.shift();
|
1307
|
+
var rejectFn = chain.shift();
|
1308
|
+
|
1309
|
+
promise = promise.then(createInterceptorSuccessCallback(thenFn, config.resourceConstructor, context),
|
1310
|
+
createInterceptorRejectionCallback(rejectFn, config.resourceConstructor, context));
|
1311
|
+
}
|
1312
|
+
|
1313
|
+
return promise;
|
1314
|
+
};
|
1315
|
+
|
1316
|
+
/**
|
1317
|
+
* Executes an HTTP request using $http.
|
1318
|
+
*
|
1319
|
+
* This method is used by all RailsResource operations that execute HTTP requests. Handles serializing
|
1320
|
+
* the request data using the resource serializer, root wrapping (if enabled), deserializing the response
|
1321
|
+
* data using the resource serializer, root unwrapping (if enabled), and copying the result back into the
|
1322
|
+
* resource context if applicable. Executes interceptors at each phase of the request / response to allow
|
1323
|
+
* users to build synchronous & asynchronous customizations to manipulate the data as necessary.
|
1324
|
+
*
|
1325
|
+
* @param httpConfig The config to pass to $http, see $http docs for details
|
1326
|
+
* @param context An optional reference to the resource instance that is the context for the operation.
|
1327
|
+
* If specified, the result data will be copied into the context during the response handling.
|
1328
|
+
* @param resourceConfigOverrides An optional set of RailsResource configuration options overrides.
|
1329
|
+
* These overrides allow users to build custom operations more easily with different resource settings.
|
1330
|
+
* @returns {Promise} The promise that will eventually be resolved after all request / response handling
|
1331
|
+
* has completed.
|
1332
|
+
*/
|
1333
|
+
RailsResource.$http = function (httpConfig, context, resourceConfigOverrides) {
|
1334
|
+
var timeoutPromise, promise,
|
1335
|
+
config = angular.extend(angular.copy(this.config), resourceConfigOverrides || {}),
|
1336
|
+
resourceConstructor = config.resourceConstructor,
|
1337
|
+
abortDeferred = $q.defer();
|
1338
|
+
|
1339
|
+
function abortRequest() {
|
1340
|
+
abortDeferred.resolve();
|
1341
|
+
}
|
1342
|
+
|
1343
|
+
if (httpConfig && httpConfig.timeout) {
|
1344
|
+
if (httpConfig.timeout > 0) {
|
1345
|
+
timeoutPromise = $timeout(abortDeferred.resolve, httpConfig.timeout);
|
1346
|
+
} else if (angular.isFunction(httpConfig.timeout.then)) {
|
1347
|
+
httpConfig.timeout.then(abortDeferred.resolve);
|
1348
|
+
}
|
1349
|
+
}
|
1350
|
+
|
1351
|
+
httpConfig = angular.extend({}, httpConfig, {timeout: abortDeferred.promise});
|
1352
|
+
promise = $q.when(httpConfig);
|
1353
|
+
|
1354
|
+
if (!config.skipRequestProcessing) {
|
1355
|
+
|
1356
|
+
promise = this.runInterceptorPhase('beforeRequest', context, promise).then(function (httpConfig) {
|
1357
|
+
httpConfig = resourceConstructor.serialize(httpConfig);
|
1358
|
+
|
1359
|
+
forEachDependency(config.requestTransformers, function (transformer) {
|
1360
|
+
httpConfig.data = transformer(httpConfig.data, config.resourceConstructor);
|
1361
|
+
});
|
1362
|
+
|
1363
|
+
return httpConfig;
|
1364
|
+
});
|
1365
|
+
|
1366
|
+
promise = this.runInterceptorPhase('beforeRequestWrapping', context, promise);
|
1367
|
+
|
1368
|
+
if (config.rootWrapping) {
|
1369
|
+
promise = promise.then(function (httpConfig) {
|
1370
|
+
httpConfig.data = railsRootWrapper.wrap(httpConfig.data, config.resourceConstructor);
|
1371
|
+
return httpConfig;
|
1372
|
+
});
|
1373
|
+
}
|
1374
|
+
|
1375
|
+
promise = this.runInterceptorPhase('request', context, promise).then(function (httpConfig) {
|
1376
|
+
return $http(httpConfig);
|
1377
|
+
});
|
1378
|
+
|
1379
|
+
} else {
|
1380
|
+
promise = $http(httpConfig);
|
1381
|
+
}
|
1382
|
+
|
1383
|
+
// After the request has completed we need to cancel any pending timeout
|
1384
|
+
if (timeoutPromise) {
|
1385
|
+
// not using finally here to stay compatible with angular 1.0
|
1386
|
+
promise = promise.then(function (result) {
|
1387
|
+
$timeout.cancel(timeoutPromise);
|
1388
|
+
return result;
|
1389
|
+
}, function (error) {
|
1390
|
+
$timeout.cancel(timeoutPromise);
|
1391
|
+
return $q.reject(error);
|
1392
|
+
});
|
1393
|
+
}
|
1394
|
+
|
1395
|
+
promise = this.runInterceptorPhase('beforeResponse', context, promise).then(function (response) {
|
1396
|
+
// store off the data so we don't lose access to it after deserializing and unwrapping
|
1397
|
+
response.originalData = response.data;
|
1398
|
+
return response;
|
1399
|
+
});
|
1400
|
+
|
1401
|
+
if (config.rootWrapping) {
|
1402
|
+
promise = promise.then(function (response) {
|
1403
|
+
return railsRootWrapper.unwrap(response, config.resourceConstructor, false);
|
1404
|
+
});
|
1405
|
+
}
|
1406
|
+
|
1407
|
+
promise = this.runInterceptorPhase('beforeResponseDeserialize', context, promise).then(function (response) {
|
1408
|
+
return resourceConstructor.deserialize(response);
|
1409
|
+
});
|
1410
|
+
|
1411
|
+
promise = this.callResponseInterceptors(promise, context);
|
1412
|
+
promise = this.runInterceptorPhase('response', context, promise).then(function (response) {
|
1413
|
+
if (context) {
|
1414
|
+
// we may not have response data
|
1415
|
+
if (response.hasOwnProperty('data') && angular.isObject(response.data)) {
|
1416
|
+
angular.extend(context, response.data);
|
1417
|
+
}
|
1418
|
+
}
|
1419
|
+
|
1420
|
+
return config.fullResponse ? response : (context || response.data);
|
1421
|
+
});
|
1422
|
+
|
1423
|
+
promise = this.callAfterResponseInterceptors(promise, context);
|
1424
|
+
promise = this.runInterceptorPhase('afterResponse', context, promise);
|
1425
|
+
promise = this.runInterceptorPhase('afterDeserialize', context, promise);
|
1426
|
+
return extendPromise(promise, {
|
1427
|
+
resource: config.resourceConstructor,
|
1428
|
+
context: context,
|
1429
|
+
abort: abortRequest
|
1430
|
+
});
|
1431
|
+
};
|
1432
|
+
|
1433
|
+
/**
|
1434
|
+
* Processes query parameters before request. You can override to modify
|
1435
|
+
* the query params or return a new object.
|
1436
|
+
*
|
1437
|
+
* @param {Object} queryParams - The query parameters for the request
|
1438
|
+
* @returns {Object} The query parameters for the request
|
1439
|
+
*/
|
1440
|
+
RailsResource.processParameters = function (queryParams) {
|
1441
|
+
var newParams = {};
|
1442
|
+
|
1443
|
+
if (angular.isObject(queryParams) && this.config.underscoreParams) {
|
1444
|
+
angular.forEach(queryParams, function (v, k) {
|
1445
|
+
newParams[this.config.serializer.underscore(k)] = v;
|
1446
|
+
}, this);
|
1447
|
+
|
1448
|
+
return newParams;
|
1449
|
+
}
|
1450
|
+
|
1451
|
+
return queryParams;
|
1452
|
+
};
|
1453
|
+
|
1454
|
+
RailsResource.getParameters = function (queryParams) {
|
1455
|
+
var params;
|
1456
|
+
|
1457
|
+
if (this.config.defaultParams) {
|
1458
|
+
// we need to clone it so we don't modify it when we add the additional
|
1459
|
+
// query params below
|
1460
|
+
params = angular.copy(this.config.defaultParams);
|
1461
|
+
}
|
1462
|
+
|
1463
|
+
if (angular.isObject(queryParams)) {
|
1464
|
+
params = angular.extend(params || {}, queryParams);
|
1465
|
+
}
|
1466
|
+
|
1467
|
+
return this.processParameters(params);
|
1468
|
+
};
|
1469
|
+
|
1470
|
+
RailsResource.getHttpConfig = function (queryParams) {
|
1471
|
+
var params = this.getParameters(queryParams);
|
1472
|
+
|
1473
|
+
if (params) {
|
1474
|
+
return angular.extend({params: params}, this.config.httpConfig);
|
1475
|
+
}
|
1476
|
+
|
1477
|
+
return angular.copy(this.config.httpConfig);
|
1478
|
+
};
|
1479
|
+
|
1480
|
+
/**
|
1481
|
+
* Returns a URL from the given parameters. You can override this method on your resource definitions to provide
|
1482
|
+
* custom logic for building your URLs or you can utilize the parameterized url strings to substitute values in the
|
1483
|
+
* URL string.
|
1484
|
+
*
|
1485
|
+
* The parameters in the URL string follow the normal Angular binding expression using {{ and }} for the start/end symbols.
|
1486
|
+
*
|
1487
|
+
* If the context is a number and the URL string does not contain an id parameter then the number is appended
|
1488
|
+
* to the URL string.
|
1489
|
+
*
|
1490
|
+
* If the context is a number and the URL string does
|
1491
|
+
* @param context
|
1492
|
+
* @param path {string} (optional) An additional path to append to the URL
|
1493
|
+
* @return {string}
|
1494
|
+
*/
|
1495
|
+
RailsResource.$url = RailsResource.resourceUrl = function (ctxt, path) {
|
1496
|
+
var context = ctxt;
|
1497
|
+
if (!angular.isObject(ctxt)) {
|
1498
|
+
context = {};
|
1499
|
+
context[this.config.idAttribute] = ctxt;
|
1500
|
+
}
|
1501
|
+
|
1502
|
+
return appendPath(this.buildUrl(context || {}), path);
|
1503
|
+
};
|
1504
|
+
|
1505
|
+
RailsResource.$get = function (url, queryParams) {
|
1506
|
+
return this.$http(angular.extend({method: 'get', url: url}, this.getHttpConfig(queryParams)));
|
1507
|
+
};
|
1508
|
+
|
1509
|
+
RailsResource.query = function (queryParams, context) {
|
1510
|
+
return this.$get(this.resourceUrl(context), queryParams);
|
1511
|
+
};
|
1512
|
+
|
1513
|
+
RailsResource.get = function (context, queryParams) {
|
1514
|
+
return this.$get(this.resourceUrl(context), queryParams);
|
1515
|
+
};
|
1516
|
+
|
1517
|
+
/**
|
1518
|
+
* Returns the URL for this resource.
|
1519
|
+
*
|
1520
|
+
* @param path {string} (optional) An additional path to append to the URL
|
1521
|
+
* @returns {string} The URL for the resource
|
1522
|
+
*/
|
1523
|
+
RailsResource.prototype.$url = function (path) {
|
1524
|
+
return appendPath(this.constructor.resourceUrl(this), path);
|
1525
|
+
};
|
1526
|
+
|
1527
|
+
/**
|
1528
|
+
* Executes $http with the resource instance as the context.
|
1529
|
+
*
|
1530
|
+
* @param httpConfig The config to pass to $http, see $http docs for details
|
1531
|
+
* @param context An optional reference to the resource instance that is the context for the operation.
|
1532
|
+
* If specified, the result data will be copied into the context during the response handling.
|
1533
|
+
* @param resourceConfigOverrides An optional set of RailsResource configuration options overrides.
|
1534
|
+
* These overrides allow users to build custom operations more easily with different resource settings.
|
1535
|
+
* @returns {Promise} The promise that will eventually be resolved after all request / response handling
|
1536
|
+
* has completed.
|
1537
|
+
*/
|
1538
|
+
RailsResource.prototype.$http = function (httpConfig, resourceConfigOverrides) {
|
1539
|
+
return this.constructor.$http(httpConfig, this, resourceConfigOverrides);
|
1540
|
+
};
|
1541
|
+
|
1542
|
+
angular.forEach(['post', 'put', 'patch'], function (method) {
|
1543
|
+
RailsResource['$' + method] = function (url, data, resourceConfigOverrides, queryParams) {
|
1544
|
+
// clone so we can manipulate w/o modifying the actual instance
|
1545
|
+
data = angular.copy(data);
|
1546
|
+
return this.$http(angular.extend({method: method, url: url, data: data}, this.getHttpConfig(queryParams)), null, resourceConfigOverrides);
|
1547
|
+
};
|
1548
|
+
|
1549
|
+
RailsResource.prototype['$' + method] = function (url, context, queryParams) {
|
1550
|
+
// clone so we can manipulate w/o modifying the actual instance
|
1551
|
+
var data = angular.copy(this, {});
|
1552
|
+
return this.constructor.$http(angular.extend({method: method, url: url, data: data}, this.constructor.getHttpConfig(queryParams)), this);
|
1553
|
+
};
|
1554
|
+
});
|
1555
|
+
|
1556
|
+
RailsResource.prototype.create = function () {
|
1557
|
+
return this.$post(this.$url(), this);
|
1558
|
+
};
|
1559
|
+
|
1560
|
+
RailsResource.prototype.update = function () {
|
1561
|
+
return this['$' + this.constructor.config.updateMethod](this.$url(), this);
|
1562
|
+
};
|
1563
|
+
|
1564
|
+
RailsResource.prototype.get = function () {
|
1565
|
+
return this.constructor.$http(angular.extend({method: 'GET', url: this.$url()}, this.constructor.getHttpConfig()), this);
|
1566
|
+
};
|
1567
|
+
|
1568
|
+
RailsResource.prototype.isNew = function () {
|
1569
|
+
var idAttribute = this.constructor.config.idAttribute;
|
1570
|
+
return angular.isUndefined(this[idAttribute]) ||
|
1571
|
+
this[idAttribute] === null;
|
1572
|
+
};
|
1573
|
+
|
1574
|
+
RailsResource.prototype.save = function () {
|
1575
|
+
if (this.isNew()) {
|
1576
|
+
return this.create();
|
1577
|
+
} else {
|
1578
|
+
return this.update();
|
1579
|
+
}
|
1580
|
+
};
|
1581
|
+
|
1582
|
+
RailsResource.$delete = function (url, queryParams) {
|
1583
|
+
return this.$http(angular.extend({method: 'delete', url: url}, this.getHttpConfig(queryParams)));
|
1584
|
+
};
|
1585
|
+
|
1586
|
+
RailsResource.prototype.$delete = function (url, queryParams) {
|
1587
|
+
return this.constructor.$http(angular.extend({method: 'delete', url: url}, this.constructor.getHttpConfig(queryParams)), this);
|
1588
|
+
};
|
1589
|
+
|
1590
|
+
//using ['delete'] instead of .delete for IE7/8 compatibility
|
1591
|
+
RailsResource.prototype.remove = RailsResource.prototype['delete'] = function () {
|
1592
|
+
return this.$delete(this.$url());
|
1593
|
+
};
|
1594
|
+
|
1595
|
+
return RailsResource;
|
1596
|
+
|
1597
|
+
function appendPath(url, path) {
|
1598
|
+
if (path) {
|
1599
|
+
if (path[0] !== '/') {
|
1600
|
+
url += '/';
|
1601
|
+
}
|
1602
|
+
|
1603
|
+
url += path;
|
1604
|
+
}
|
1605
|
+
|
1606
|
+
return url;
|
1607
|
+
}
|
1608
|
+
|
1609
|
+
function forEachDependency(list, callback) {
|
1610
|
+
var dependency;
|
1611
|
+
|
1612
|
+
for (var i = 0, len = list.length; i < len; i++) {
|
1613
|
+
dependency = list[i];
|
1614
|
+
|
1615
|
+
if (angular.isString(dependency)) {
|
1616
|
+
dependency = list[i] = RailsResourceInjector.getDependency(dependency);
|
1617
|
+
}
|
1618
|
+
|
1619
|
+
callback(dependency);
|
1620
|
+
}
|
1621
|
+
}
|
1622
|
+
|
1623
|
+
function addMixin(Resource, destination, mixin, callback) {
|
1624
|
+
var excludedKeys = ['included', 'extended,', 'configure'];
|
1625
|
+
|
1626
|
+
if (!Resource.$mixins) {
|
1627
|
+
Resource.$mixins = [];
|
1628
|
+
}
|
1629
|
+
|
1630
|
+
if (angular.isString(mixin)) {
|
1631
|
+
mixin = RailsResourceInjector.getDependency(mixin);
|
1632
|
+
}
|
1633
|
+
|
1634
|
+
if (mixin && Resource.$mixins.indexOf(mixin) === -1) {
|
1635
|
+
angular.forEach(mixin, function (value, key) {
|
1636
|
+
if (excludedKeys.indexOf(key) === -1) {
|
1637
|
+
destination[key] = value;
|
1638
|
+
}
|
1639
|
+
});
|
1640
|
+
|
1641
|
+
Resource.$mixins.push(mixin);
|
1642
|
+
|
1643
|
+
if (angular.isFunction(callback)) {
|
1644
|
+
callback(Resource, mixin);
|
1645
|
+
}
|
1646
|
+
}
|
1647
|
+
}
|
1648
|
+
|
1649
|
+
function loadExtensions(extensions) {
|
1650
|
+
var modules = [];
|
1651
|
+
|
1652
|
+
angular.forEach(extensions, function (extensionName) {
|
1653
|
+
extensionName = 'RailsResource' + extensionName.charAt(0).toUpperCase() + extensionName.slice(1) + 'Mixin';
|
1654
|
+
|
1655
|
+
modules.push(RailsResourceInjector.getDependency(extensionName));
|
1656
|
+
});
|
1657
|
+
|
1658
|
+
return modules;
|
1659
|
+
}
|
1660
|
+
|
1661
|
+
function booleanParam(value, defaultValue) {
|
1662
|
+
return angular.isUndefined(value) ? defaultValue : value;
|
1663
|
+
}
|
1664
|
+
|
1665
|
+
function createInterceptorSuccessCallback(thenFn, resourceConstructor, context) {
|
1666
|
+
return function (data) {
|
1667
|
+
return (thenFn || angular.identity)(data, resourceConstructor, context);
|
1668
|
+
};
|
1669
|
+
}
|
1670
|
+
|
1671
|
+
function createInterceptorRejectionCallback(rejectFn, resourceConstructor, context) {
|
1672
|
+
return function (rejection) {
|
1673
|
+
// can't use identity because we need to return a rejected promise to keep the error chain going
|
1674
|
+
return rejectFn ? rejectFn(rejection, resourceConstructor, context) : $q.reject(rejection);
|
1675
|
+
};
|
1676
|
+
}
|
1677
|
+
|
1678
|
+
function extendPromise(promise, attributes) {
|
1679
|
+
var oldThen = promise.then;
|
1680
|
+
promise.then = function (onFulfilled, onRejected, progressBack) {
|
1681
|
+
var chainedPromise = oldThen.apply(this, arguments);
|
1682
|
+
return extendPromise(chainedPromise, attributes);
|
1683
|
+
};
|
1684
|
+
angular.extend(promise, attributes);
|
1685
|
+
return promise;
|
1686
|
+
}
|
1687
|
+
}];
|
1688
|
+
});
|
1689
|
+
|
1690
|
+
angular.module('rails').factory('railsResourceFactory', ['RailsResource', function (RailsResource) {
|
1691
|
+
return function (config) {
|
1692
|
+
function Resource() {
|
1693
|
+
Resource.__super__.constructor.apply(this, arguments);
|
1694
|
+
}
|
1695
|
+
|
1696
|
+
RailsResource.extendTo(Resource);
|
1697
|
+
Resource.configure(config);
|
1698
|
+
|
1699
|
+
return Resource;
|
1700
|
+
};
|
1701
|
+
}]);
|
1702
|
+
|
1703
|
+
}());
|