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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 64cc55475c184d63ac0829b883be99a9aefc079c
4
- data.tar.gz: 471381f4a57eeb8020860913e631f9a0ca4825f9
3
+ metadata.gz: 02ee284de9ae42fafae6f2d8f5e102fddeb1e511
4
+ data.tar.gz: 1e51b537318f682dcc4ca6ab6fabf12a330268f6
5
5
  SHA512:
6
- metadata.gz: a57e02d6c154624b87ffe8ab1a73ff82a33ff1a8735350ca26fea790a377f3d1f020ba2d2568ab8433d2f86f1c0f575540ee4d3987955986e6a9545abf7a8f06
7
- data.tar.gz: db534a8034b262cd3cf43818d7c2f83bc739fc4020cf36b41520365f853954bae5d1773939a1def2e41ba1f00c12f349875b5b90b6f5ac78f59ea377f93099ef
6
+ metadata.gz: 2690f964e3f318dc91bc22185a221754f764c802c1fc2c6956c585300b14fef3c12b5ba7ccb39b506c6cad29ee670ede7480c3b3fc7627f4f8867f8c610f5833
7
+ data.tar.gz: 027b2c3e8ae30b98710f392cfa0c890128032aa851e132e8f0cf7f688d384b6f650adc183fec6b6fbdffccc7cf2479d95688cb184051d3c68966d3b96bec06b5
data/.gitignore CHANGED
@@ -19,5 +19,6 @@ test_out
19
19
  atlassian-ide-plugin.xml
20
20
  node_modules
21
21
  build/
22
+ dist/*.zip
22
23
  .idea/
23
24
 
@@ -1,3 +1,8 @@
1
+ <a name="2.3.1"></a>
2
+ # 2.3.1
3
+ ## Bug Fixes
4
+ - Updated bower and NPM distributions to fix invalid main reference for NPM package
5
+
1
6
  <a name="2.3.0"></a>
2
7
  # 2.3.0
3
8
  ## Features
@@ -22,7 +22,7 @@ module.exports = function(grunt) {
22
22
  },
23
23
 
24
24
  dirs: {
25
- dest: 'build'
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.0",
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/dist-angularjs-rails-resource.git"
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
- "node_modules",
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
+ }());