angularjs-rails-resource 2.2.2 → 2.3.0

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.
@@ -9,8 +9,8 @@
9
9
 
10
10
  RailsResourceSnapshotsMixin.extended = function (Resource) {
11
11
  Resource.intercept('afterResponse', function (result, resource, context) {
12
- if (context && context.hasOwnProperty('$snapshots') && angular.isArray(context.$snapshots)) {
13
- context.$snapshots.length = 0;
12
+ if (context && context.hasOwnProperty('$$snapshots') && angular.isArray(context.$$snapshots)) {
13
+ context.$$snapshots.length = 0;
14
14
  }
15
15
  });
16
16
 
@@ -27,7 +27,7 @@
27
27
 
28
28
  /**
29
29
  * Prepares a copy of the resource to be stored as a snapshot
30
- * @returns {Resource} the copied resource, sans $snapshots
30
+ * @returns {Resource} the copied resource, sans $$snapshots
31
31
  */
32
32
  function _prepSnapshot() {
33
33
  var config = this.constructor.config,
@@ -35,13 +35,13 @@
35
35
 
36
36
  // we don't want to store our snapshots in the snapshots because that would make the rollback kind of funny
37
37
  // not to mention using more memory for each snapshot.
38
- delete copy.$snapshots;
38
+ delete copy.$$snapshots;
39
39
 
40
40
  return copy
41
41
  }
42
42
 
43
43
  /**
44
- * Stores a copy of this resource in the $snapshots array to allow undoing changes.
44
+ * Stores a copy of this resource in the $$snapshots array to allow undoing changes.
45
45
  * @param {function} rollbackCallback Optional callback function to be executed after the rollback.
46
46
  * @returns {Number} The version of the snapshot created (0-based index)
47
47
  */
@@ -50,12 +50,12 @@
50
50
 
51
51
  copy.$rollbackCallback = rollbackCallback;
52
52
 
53
- if (!this.$snapshots) {
54
- this.$snapshots = [];
53
+ if (!this.$$snapshots) {
54
+ this.$$snapshots = [];
55
55
  }
56
56
 
57
- this.$snapshots.push(copy);
58
- return this.$snapshots.length - 1;
57
+ this.$$snapshots.push(copy);
58
+ return this.$$snapshots.length - 1;
59
59
  }
60
60
 
61
61
  /**
@@ -76,8 +76,8 @@
76
76
  function rollbackTo(version) {
77
77
  var versions, rollbackCallback,
78
78
  config = this.constructor.config,
79
- snapshots = this.$snapshots,
80
- snapshotsLength = this.$snapshots ? this.$snapshots.length : 0;
79
+ snapshots = this.$$snapshots,
80
+ snapshotsLength = this.$$snapshots ? this.$$snapshots.length : 0;
81
81
 
82
82
  // if an invalid snapshot version was specified then don't attempt to do anything
83
83
  if (!angular.isArray(snapshots) || snapshotsLength === 0 || !angular.isNumber(version)) {
@@ -94,7 +94,7 @@
94
94
  angular.extend(this, (config.snapshotSerializer || config.serializer).deserialize(versions[0]));
95
95
 
96
96
  // restore special variables
97
- this.$snapshots = snapshots;
97
+ this.$$snapshots = snapshots;
98
98
  delete this.$rollbackCallback;
99
99
 
100
100
  if (angular.isFunction(rollbackCallback)) {
@@ -115,7 +115,7 @@
115
115
  * @returns {Boolean} true if rollback was successful, false otherwise
116
116
  */
117
117
  function rollback(numVersions) {
118
- var snapshotsLength = this.$snapshots ? this.$snapshots.length : 0;
118
+ var snapshotsLength = this.$$snapshots ? this.$$snapshots.length : 0;
119
119
  numVersions = Math.min(numVersions || 1, snapshotsLength);
120
120
 
121
121
  if (numVersions < 0) {
@@ -123,7 +123,7 @@
123
123
  }
124
124
 
125
125
  if (snapshotsLength) {
126
- this.rollbackTo(this.$snapshots.length - numVersions);
126
+ this.rollbackTo(this.$$snapshots.length - numVersions);
127
127
  }
128
128
 
129
129
  return true;
@@ -134,12 +134,12 @@
134
134
  * @returns {Boolean} true if the latest snapshot differs from resource as-is
135
135
  */
136
136
  function unsnappedChanges() {
137
- if (!this.$snapshots) {
137
+ if (!this.$$snapshots) {
138
138
  return true
139
139
  }
140
140
 
141
141
  var copy = this._prepSnapshot(),
142
- latestSnap = this.$snapshots[this.$snapshots.length - 1]
142
+ latestSnap = this.$$snapshots[this.$$snapshots.length - 1]
143
143
 
144
144
  return !angular.equals(copy, latestSnap)
145
145
  }
@@ -108,8 +108,8 @@
108
108
  return this;
109
109
  };
110
110
 
111
- this.$get = ['$http', '$q', 'railsUrlBuilder', 'railsSerializer', 'railsRootWrapper', 'RailsResourceInjector',
112
- function ($http, $q, railsUrlBuilder, railsSerializer, railsRootWrapper, RailsResourceInjector) {
111
+ this.$get = ['$http', '$q', '$timeout', 'railsUrlBuilder', 'railsSerializer', 'railsRootWrapper', 'RailsResourceInjector',
112
+ function ($http, $q, $timeout, railsUrlBuilder, railsSerializer, railsRootWrapper, RailsResourceInjector) {
113
113
 
114
114
  function RailsResource(value) {
115
115
  if (value) {
@@ -118,6 +118,7 @@
118
118
  response = railsRootWrapper.unwrap(response, this.constructor, true);
119
119
  }
120
120
  angular.extend(this, response.data);
121
+ this.constructor.runInterceptorPhase('afterDeserialize', this);
121
122
  }
122
123
  }
123
124
 
@@ -219,6 +220,7 @@
219
220
  this.config.underscoreParams = booleanParam(cfg.underscoreParams, defaultOptions.underscoreParams);
220
221
  this.config.updateMethod = (cfg.updateMethod || defaultOptions.updateMethod).toLowerCase();
221
222
  this.config.fullResponse = booleanParam(cfg.fullResponse, defaultOptions.fullResponse);
223
+ this.config.singular = cfg.singular || defaultOptions.singular;
222
224
 
223
225
  this.config.requestTransformers = cfg.requestTransformers ? cfg.requestTransformers.slice(0) : [];
224
226
  this.config.responseInterceptors = cfg.responseInterceptors ? cfg.responseInterceptors.slice(0) : [];
@@ -313,6 +315,12 @@
313
315
  * * afterResponseError: Interceptors get called when a previous interceptor threw an error or
314
316
  * resolved with a rejection.
315
317
  *
318
+ * Finally, for each deserialized resource including associations, deserialization phases are called.
319
+ *
320
+ * The valid deserialization phases are:
321
+ *
322
+ * * afterDeserialize: Interceptors are called after a resource has been deserialized.
323
+ *
316
324
  * @param {String | Object} interceptor
317
325
  */
318
326
  RailsResource.addInterceptor = function (interceptor) {
@@ -322,7 +330,7 @@
322
330
  /**
323
331
  * Adds an interceptor callback function for the specified phase.
324
332
  * @param {String} phase The interceptor phase, one of:
325
- * beforeRequest, request, beforeResponse, response, afterResponse
333
+ * beforeRequest, request, beforeResponse, response, afterResponse, afterDeserialize
326
334
  * @param fn The function to call.
327
335
  */
328
336
  RailsResource.intercept = function (phase, fn) {
@@ -406,6 +414,16 @@
406
414
  this.intercept('afterResponse', fn);
407
415
  };
408
416
 
417
+ /**
418
+ * Adds interceptor on 'afterDeserialize' phase.
419
+ * @param fn(response data, constructor, context) - response data is either the resource instance returned or an array of resource instances,
420
+ * constructor is the resource class calling the function,
421
+ * context is the resource instance of the calling method (create, update, delete) or undefined if the method was a class method (get, query)
422
+ */
423
+ RailsResource.interceptAfterDeserialize = function (fn) {
424
+ this.intercept('afterDeserialize', fn);
425
+ };
426
+
409
427
  /**
410
428
  * Deprecated, see interceptors
411
429
  * Add a callback to run on response.
@@ -505,6 +523,7 @@
505
523
  };
506
524
 
507
525
  RailsResource.runInterceptorPhase = function (phase, context, promise) {
526
+ promise = promise || $q.resolve(context);
508
527
  var config = this.config, chain = [];
509
528
 
510
529
  forEachDependency(config.interceptors, function (interceptor) {
@@ -542,9 +561,25 @@
542
561
  * has completed.
543
562
  */
544
563
  RailsResource.$http = function (httpConfig, context, resourceConfigOverrides) {
545
- var config = angular.extend(angular.copy(this.config), resourceConfigOverrides || {}),
564
+ var timeoutPromise, promise,
565
+ config = angular.extend(angular.copy(this.config), resourceConfigOverrides || {}),
546
566
  resourceConstructor = config.resourceConstructor,
547
- promise = $q.when(httpConfig);
567
+ abortDeferred = $q.defer();
568
+
569
+ function abortRequest() {
570
+ abortDeferred.resolve();
571
+ }
572
+
573
+ if (httpConfig && httpConfig.timeout) {
574
+ if (httpConfig.timeout > 0) {
575
+ timeoutPromise = $timeout(abortDeferred.resolve, httpConfig.timeout);
576
+ } else if (angular.isFunction(httpConfig.timeout.then)) {
577
+ httpConfig.timeout.then(abortDeferred.resolve);
578
+ }
579
+ }
580
+
581
+ httpConfig = angular.extend({}, httpConfig, {timeout: abortDeferred.promise});
582
+ promise = $q.when(httpConfig);
548
583
 
549
584
  if (!config.skipRequestProcessing) {
550
585
 
@@ -572,9 +607,19 @@
572
607
  });
573
608
 
574
609
  } else {
575
-
576
610
  promise = $http(httpConfig);
611
+ }
577
612
 
613
+ // After the request has completed we need to cancel any pending timeout
614
+ if (timeoutPromise) {
615
+ // not using finally here to stay compatible with angular 1.0
616
+ promise = promise.then(function (result) {
617
+ $timeout.cancel(timeoutPromise);
618
+ return result;
619
+ }, function (error) {
620
+ $timeout.cancel(timeoutPromise);
621
+ return $q.reject(error);
622
+ });
578
623
  }
579
624
 
580
625
  promise = this.runInterceptorPhase('beforeResponse', context, promise).then(function (response) {
@@ -607,9 +652,12 @@
607
652
 
608
653
  promise = this.callAfterResponseInterceptors(promise, context);
609
654
  promise = this.runInterceptorPhase('afterResponse', context, promise);
610
- promise.resource = config.resourceConstructor;
611
- promise.context = context;
612
- return promise;
655
+ promise = this.runInterceptorPhase('afterDeserialize', context, promise);
656
+ return extendPromise(promise, {
657
+ resource: config.resourceConstructor,
658
+ context: context,
659
+ abort: abortRequest
660
+ });
613
661
  };
614
662
 
615
663
  /**
@@ -856,6 +904,16 @@
856
904
  return rejectFn ? rejectFn(rejection, resourceConstructor, context) : $q.reject(rejection);
857
905
  };
858
906
  }
907
+
908
+ function extendPromise(promise, attributes) {
909
+ var oldThen = promise.then;
910
+ promise.then = function (onFulfilled, onRejected, progressBack) {
911
+ var chainedPromise = oldThen.apply(this, arguments);
912
+ return extendPromise(chainedPromise, attributes);
913
+ };
914
+ angular.extend(promise, attributes);
915
+ return promise;
916
+ }
859
917
  }];
860
918
  });
861
919
 
@@ -78,6 +78,7 @@
78
78
  this.preservedAttributes = {};
79
79
  this.customSerializers = {};
80
80
  this.nestedResources = {};
81
+ this.polymorphics = {};
81
82
  this.options = angular.extend({excludeByDefault: false}, defaultOptions, options || {});
82
83
 
83
84
  if (customizer) {
@@ -159,6 +160,25 @@
159
160
  return this;
160
161
  };
161
162
 
163
+ /**
164
+ * Specifies a polymorphic association according to Rails' standards.
165
+ * Polymorphic associations have a <code>{name}_id</code> and <code>{name}_type</code> columns in the database.
166
+ *
167
+ * The <code>{name}_type</code> attribute will specify which resource will be used to serialize and deserialize the data.
168
+ *
169
+ * @param names... {string} Variable number of name parameters
170
+ * @returns {Serializer} this for chaining support
171
+ */
172
+ Serializer.prototype.polymorphic = function () {
173
+ var polymorphics = this.polymorphics;
174
+
175
+ angular.forEach(arguments, function(attributeName) {
176
+ polymorphics[attributeName] = true;
177
+ });
178
+
179
+ return this;
180
+ };
181
+
162
182
  /**
163
183
  * Specifies a custom name mapping for an attribute.
164
184
  * On serializing to JSON the jsonName will be used.
@@ -317,10 +337,17 @@
317
337
  /**
318
338
  * Returns a reference to the nested resource that has been specified for the attribute.
319
339
  * @param attributeName The attribute name
340
+ * @param data the entire object being serialized
320
341
  * @returns {*} undefined if no nested resource has been specified or a reference to the nested resource class
321
342
  */
322
- Serializer.prototype.getNestedResource = function (attributeName) {
323
- return RailsResourceInjector.getDependency(this.nestedResources[attributeName]);
343
+ Serializer.prototype.getNestedResource = function (attributeName, data) {
344
+ var resourceName;
345
+ if (!this.polymorphics[attributeName]) {
346
+ resourceName = this.nestedResources[attributeName];
347
+ } else {
348
+ resourceName = data[attributeName + '_type'];
349
+ }
350
+ return RailsResourceInjector.getDependency(resourceName);
324
351
  };
325
352
 
326
353
  /**
@@ -330,10 +357,11 @@
330
357
  * is used. Custom serializers specified using serializeWith take precedence over the nested resource serializer.
331
358
  *
332
359
  * @param attributeName The attribute name
360
+ * @param data the entire object being serialized
333
361
  * @returns {*} undefined if no custom serializer has been specified or an instance of the Serializer
334
362
  */
335
- Serializer.prototype.getAttributeSerializer = function (attributeName) {
336
- var resource = this.getNestedResource(attributeName),
363
+ Serializer.prototype.getAttributeSerializer = function (attributeName, data) {
364
+ var resource = this.getNestedResource(attributeName, data),
337
365
  serializer = this.customSerializers[attributeName];
338
366
 
339
367
  // custom serializer takes precedence over resource serializer
@@ -376,14 +404,14 @@
376
404
  return result;
377
405
  };
378
406
 
379
- Serializer.prototype.serializeObject = function(result, data){
407
+ Serializer.prototype.serializeObject = function(result, data) {
380
408
 
381
409
 
382
410
  var tthis = this;
383
411
  angular.forEach(data, function (value, key) {
384
412
  // if the value is a function then it can't be serialized to JSON so we'll just skip it
385
413
  if (!angular.isFunction(value)) {
386
- tthis.serializeAttribute(result, key, value);
414
+ tthis.serializeAttribute(result, key, value, data);
387
415
  }
388
416
  });
389
417
  return data;
@@ -393,12 +421,13 @@
393
421
  * Transforms an attribute and its value and stores it on the parent data object. The attribute will be
394
422
  * renamed as needed and the value itself will be serialized as well.
395
423
  *
396
- * @param data The object that the attribute will be added to
424
+ * @param result The object that the attribute will be added to
397
425
  * @param attribute The attribute to transform
398
426
  * @param value The current value of the attribute
427
+ * @param data the entire object being serialized
399
428
  */
400
- Serializer.prototype.serializeAttribute = function (data, attribute, value) {
401
- var serializer = this.getAttributeSerializer(attribute),
429
+ Serializer.prototype.serializeAttribute = function (result, attribute, value, data) {
430
+ var serializer = this.getAttributeSerializer(attribute, data),
402
431
  serializedAttributeName = this.getSerializedAttributeName(attribute);
403
432
 
404
433
  // undefined means the attribute should be excluded from serialization
@@ -406,7 +435,7 @@
406
435
  return;
407
436
  }
408
437
 
409
- data[serializedAttributeName] = serializer ? serializer.serialize(value) : this.serializeData(value);
438
+ result[serializedAttributeName] = serializer ? serializer.serialize(value) : this.serializeData(value);
410
439
  };
411
440
 
412
441
  /**
@@ -432,14 +461,14 @@
432
461
  itemValue = itemValue.call(item, item);
433
462
  }
434
463
 
435
- self.serializeAttribute(item, key, itemValue);
464
+ self.serializeAttribute(item, key, itemValue, data);
436
465
  });
437
466
  } else {
438
467
  if (angular.isFunction(value)) {
439
468
  value = value.call(data, data);
440
469
  }
441
470
 
442
- self.serializeAttribute(result, key, value);
471
+ self.serializeAttribute(result, key, value, data);
443
472
  }
444
473
  });
445
474
  }
@@ -454,9 +483,10 @@
454
483
  *
455
484
  * @param data The object to deserialize
456
485
  * @param Resource (optional) The resource type to deserialize the result into
486
+ * @param triggerPhase (optional) Whether to trigger the afterDeserialize phase
457
487
  * @returns {*} A new object or an instance of Resource populated with deserialized data.
458
488
  */
459
- Serializer.prototype.deserializeData = function (data, Resource) {
489
+ Serializer.prototype.deserializeData = function (data, Resource, triggerPhase) {
460
490
  var result = data,
461
491
  self = this;
462
492
 
@@ -464,7 +494,7 @@
464
494
  result = [];
465
495
 
466
496
  angular.forEach(data, function (value) {
467
- result.push(self.deserializeData(value, Resource));
497
+ result.push(self.deserializeData(value, Resource, triggerPhase));
468
498
  });
469
499
  } else if (angular.isObject(data)) {
470
500
  if (angular.isDate(data)) {
@@ -476,19 +506,23 @@
476
506
  result = new Resource.config.resourceConstructor();
477
507
  }
478
508
 
479
- this.deserializeObject(result, data);
509
+ this.deserializeObject(result, data, triggerPhase);
480
510
 
481
511
  }
482
512
 
483
513
  return result;
484
514
  };
485
515
 
486
- Serializer.prototype.deserializeObject = function (result, data) {
516
+ Serializer.prototype.deserializeObject = function (result, data, triggerPhase) {
487
517
 
488
518
  var tthis = this;
489
519
  angular.forEach(data, function (value, key) {
490
- tthis.deserializeAttribute(result, key, value);
520
+ tthis.deserializeAttribute(result, key, value, data);
491
521
  });
522
+ if (triggerPhase && result.constructor.runInterceptorPhase) {
523
+ result.constructor.runInterceptorPhase('afterDeserialize', result);
524
+ }
525
+
492
526
  return data;
493
527
  };
494
528
 
@@ -497,11 +531,12 @@
497
531
  * Transforms an attribute and its value and stores it on the parent data object. The attribute will be
498
532
  * renamed as needed and the value itself will be deserialized as well.
499
533
  *
500
- * @param data The object that the attribute will be added to
534
+ * @param result The object that the attribute will be added to
501
535
  * @param attribute The attribute to transform
502
536
  * @param value The current value of the attribute
537
+ * @param data the entire object being deserialized
503
538
  */
504
- Serializer.prototype.deserializeAttribute = function (data, attribute, value) {
539
+ Serializer.prototype.deserializeAttribute = function (result, attribute, value, data) {
505
540
  var serializer,
506
541
  NestedResource,
507
542
  attributeName = this.getDeserializedAttributeName(attribute);
@@ -511,14 +546,14 @@
511
546
  return;
512
547
  }
513
548
 
514
- serializer = this.getAttributeSerializer(attributeName);
515
- NestedResource = this.getNestedResource(attributeName);
549
+ serializer = this.getAttributeSerializer(attributeName, data);
550
+ NestedResource = this.getNestedResource(attributeName, data);
516
551
 
517
552
  // preserved attributes are assigned unmodified
518
553
  if (this.preservedAttributes[attributeName]) {
519
- data[attributeName] = value;
554
+ result[attributeName] = value;
520
555
  } else {
521
- data[attributeName] = serializer ? serializer.deserialize(value, NestedResource) : this.deserializeData(value, NestedResource);
556
+ result[attributeName] = serializer ? serializer.deserialize(value, NestedResource, true) : this.deserializeData(value, NestedResource, true);
522
557
  }
523
558
  };
524
559
 
@@ -531,11 +566,12 @@
531
566
  *
532
567
  * @param data The object to deserialize
533
568
  * @param Resource (optional) The resource type to deserialize the result into
569
+ * @param triggerPhase (optional) Whether to trigger the afterDeserialize phase
534
570
  * @returns {*} A new object or an instance of Resource populated with deserialized data
535
571
  */
536
- Serializer.prototype.deserialize = function (data, Resource) {
572
+ Serializer.prototype.deserialize = function (data, Resource, triggerPhase) {
537
573
  // just calls deserializeValue for now so we can more easily add on custom attribute logic for deserialize too
538
- return this.deserializeData(data, Resource);
574
+ return this.deserializeData(data, Resource, triggerPhase);
539
575
  };
540
576
 
541
577
  Serializer.prototype.pluralize = function (value) {
@@ -566,4 +602,4 @@
566
602
  return railsSerializer;
567
603
  }];
568
604
  });
569
- }());
605
+ }());