angularjs-rails-resource 2.3.0 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ }());