angularjs-rails-resource 1.0.0.pre.2 → 1.0.0.pre.3

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.
@@ -303,9 +303,9 @@
303
303
  Serializer.prototype.getDeserializedAttributeName = function (attributeName) {
304
304
  var camelizedName = this.camelize(attributeName);
305
305
 
306
- camelizedName = this.deserializeMappings[attributeName]
307
- || this.deserializeMappings[camelizedName]
308
- || camelizedName;
306
+ camelizedName = this.deserializeMappings[attributeName] ||
307
+ this.deserializeMappings[camelizedName] ||
308
+ camelizedName;
309
309
 
310
310
  if (this.isExcludedFromDeserialization(attributeName) || this.isExcludedFromDeserialization(camelizedName)) {
311
311
  return undefined;
@@ -527,7 +527,7 @@
527
527
  return this.options.camelize(value);
528
528
  }
529
529
  return value;
530
- }
530
+ };
531
531
 
532
532
  return Serializer;
533
533
  }
@@ -31,6 +31,6 @@
31
31
  camelize: camelize,
32
32
  underscore: underscore,
33
33
  pluralize: pluralize
34
- }
34
+ };
35
35
  });
36
36
  }());
@@ -9,7 +9,7 @@
9
9
  */
10
10
  function getDependency(dependency) {
11
11
  if (dependency) {
12
- return angular.isString(dependency) ? $injector.get(dependency) : dependency
12
+ return angular.isString(dependency) ? $injector.get(dependency) : dependency;
13
13
  }
14
14
 
15
15
  return undefined;
@@ -17,9 +17,9 @@
17
17
 
18
18
  /**
19
19
  * Looks up and instantiates an instance of the requested service. If the service is not a string then it is
20
- * assumed to be a constuctor function.
20
+ * assumed to be a constructor function.
21
21
  *
22
- * @param service (string | function) The service to instantiate
22
+ * @param {String|function|Object} service The service to instantiate
23
23
  * @returns {*} A new instance of the requested service
24
24
  */
25
25
  function createService(service) {
@@ -30,9 +30,26 @@
30
30
  return undefined;
31
31
  }
32
32
 
33
+ /**
34
+ * Looks up and instantiates an instance of the requested service if .
35
+ * @param {String|function|Object} service The service to instantiate
36
+ * @returns {*}
37
+ */
38
+ function getService(service) {
39
+ // strings and functions are not considered objects by angular.isObject()
40
+ if (angular.isObject(service)) {
41
+ return service;
42
+ } else if (service) {
43
+ return createService(service);
44
+ }
45
+
46
+ return undefined;
47
+ }
48
+
33
49
  return {
34
50
  createService: createService,
51
+ getService: getService,
35
52
  getDependency: getDependency
36
- }
53
+ };
37
54
  }]);
38
55
  }());
@@ -53,6 +53,5 @@
53
53
  return url;
54
54
  };
55
55
  };
56
-
57
- }])
56
+ }]);
58
57
  }());
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: angularjs-rails-resource
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.pre.2
4
+ version: 1.0.0.pre.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tommy Odom
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-11-24 00:00:00.000000000 Z
12
+ date: 2013-12-22 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: A small AngularJS add-on for integrating with Rails via JSON more easily.
15
15
  email:
@@ -29,10 +29,8 @@ files:
29
29
  - README.md
30
30
  - Rakefile
31
31
  - angularjs-rails-resource.gemspec
32
- - angularjs-rails-resource.js
33
- - angularjs-rails-resource.min.js
34
- - angularjs-rails-resource.zip
35
32
  - bower.json
33
+ - changelog.js
36
34
  - karma.conf.js
37
35
  - lib/angularjs-rails-resource.rb
38
36
  - lib/angularjs-rails-resource/version.rb
@@ -48,6 +46,7 @@ files:
48
46
  - test/lib/angular/angular-sanitize.js
49
47
  - test/lib/angular/angular-scenario.js
50
48
  - test/lib/angular/angular.js
49
+ - test/unit/angularjs/rails/extensions/snapshotsSpec.js
51
50
  - test/unit/angularjs/rails/httpSettingsSpec.js
52
51
  - test/unit/angularjs/rails/interceptorsSpec.js
53
52
  - test/unit/angularjs/rails/nestedUrlsSpec.js
@@ -59,6 +58,7 @@ files:
59
58
  - test/unit/angularjs/rails/transformersSpec.js
60
59
  - test/unit/angularjs/rails/utils/urlBuilderSpec.js
61
60
  - test/unit/helpers/spec_helper.js
61
+ - vendor/assets/javascripts/angularjs/rails/resource/extensions/snapshots.js
62
62
  - vendor/assets/javascripts/angularjs/rails/resource/index.js
63
63
  - vendor/assets/javascripts/angularjs/rails/resource/resource.js
64
64
  - vendor/assets/javascripts/angularjs/rails/resource/serialization.js
@@ -85,7 +85,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
85
85
  version: 1.3.1
86
86
  requirements: []
87
87
  rubyforge_project:
88
- rubygems_version: 2.0.6
88
+ rubygems_version: 2.1.9
89
89
  signing_key:
90
90
  specification_version: 4
91
91
  summary: AngularJS add-on resource add-on for integrating with Rails
@@ -101,6 +101,7 @@ test_files:
101
101
  - test/lib/angular/angular-sanitize.js
102
102
  - test/lib/angular/angular-scenario.js
103
103
  - test/lib/angular/angular.js
104
+ - test/unit/angularjs/rails/extensions/snapshotsSpec.js
104
105
  - test/unit/angularjs/rails/httpSettingsSpec.js
105
106
  - test/unit/angularjs/rails/interceptorsSpec.js
106
107
  - test/unit/angularjs/rails/nestedUrlsSpec.js
@@ -1,1117 +0,0 @@
1
- /**
2
- * A resource factory inspired by $resource from AngularJS
3
- * @version v1.0.0-pre.2 - 2013-11-24
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 constuctor function.
70
- *
71
- * @param service (string | function) 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
- return {
83
- createService: createService,
84
- getDependency: getDependency
85
- }
86
- }]);
87
- }());
88
- /**
89
- * @ngdoc function
90
- * @name rails.railsUrlBuilder
91
- * @function
92
- * @requires $interpolate
93
- *
94
- * @description
95
- *
96
- * Compiles a URL template string into an interpolation function using $interpolate. If no interpolation bindings
97
- * found then {{id}} is appended to the url string.
98
- *
99
- <pre>
100
- expect(railsUrlBuilder('/books')()).toEqual('/books')
101
- expect(railsUrlBuilder('/books')({id: 1})).toEqual('/books/1')
102
- expect(railsUrlBuilder('/authors/{{authorId}}/books/{{id}}')({id: 1, authorId: 2})).toEqual('/authors/2/books/1')
103
- </pre>
104
- *
105
- * If the $interpolate startSymbol and endSymbol have been customized those values should be used instead of {{ and }}
106
- *
107
- * @param {string|function} url If the url is a function then that function is returned. Otherwise the url string
108
- * is passed to $interpolate as an expression.
109
- *
110
- * @returns {function(context)} As stated by $interpolate documentation:
111
- * An interpolation function which is used to compute the interpolated
112
- * string. The function has these parameters:
113
- *
114
- * * `context`: an object against which any expressions embedded in the strings are evaluated
115
- * against.
116
- *
117
- */
118
- (function (undefined) {
119
- angular.module('rails').factory('railsUrlBuilder', ['$interpolate', function($interpolate) {
120
- return function (url) {
121
- var expression;
122
-
123
- if (angular.isFunction(url) || angular.isUndefined(url)) {
124
- return url;
125
- }
126
-
127
- if (url.indexOf($interpolate.startSymbol()) === -1) {
128
- url = url + '/' + $interpolate.startSymbol() + 'id' + $interpolate.endSymbol();
129
- }
130
-
131
- expression = $interpolate(url);
132
-
133
- return function (params) {
134
- url = expression(params);
135
-
136
- if (url.charAt(url.length - 1) === '/') {
137
- url = url.substr(0, url.length - 1);
138
- }
139
-
140
- return url;
141
- };
142
- };
143
-
144
- }])
145
- }());
146
- (function (undefined) {
147
- angular.module('rails').provider('railsSerializer', function() {
148
- var defaultOptions = {
149
- underscore: undefined,
150
- camelize: undefined,
151
- pluralize: undefined,
152
- exclusionMatchers: []
153
- };
154
-
155
- /**
156
- * Configures the underscore method used by the serializer. If not defined then <code>RailsInflector.underscore</code>
157
- * will be used.
158
- *
159
- * @param {function(string):string} fn The function to use for underscore conversion
160
- * @returns {railsSerializerProvider} The provider for chaining
161
- */
162
- this.underscore = function(fn) {
163
- defaultOptions.underscore = fn;
164
- return this;
165
- };
166
-
167
- /**
168
- * Configures the camelize method used by the serializer. If not defined then <code>RailsInflector.camelize</code>
169
- * will be used.
170
- *
171
- * @param {function(string):string} fn The function to use for camelize conversion
172
- * @returns {railsSerializerProvider} The provider for chaining
173
- */
174
- this.camelize = function(fn) {
175
- defaultOptions.camelize = fn;
176
- return this;
177
- };
178
-
179
- /**
180
- * Configures the pluralize method used by the serializer. If not defined then <code>RailsInflector.pluralize</code>
181
- * will be used.
182
- *
183
- * @param {function(string):string} fn The function to use for pluralizing strings.
184
- * @returns {railsSerializerProvider} The provider for chaining
185
- */
186
- this.pluralize = function(fn) {
187
- defaultOptions.pluralize = fn;
188
- return this;
189
- };
190
-
191
- /**
192
- * Configures the array exclusion matchers by the serializer. Exclusion matchers can be one of the following:
193
- * * string - Defines a prefix that is used to test for exclusion
194
- * * RegExp - A custom regular expression that is tested against the attribute name
195
- * * function - A custom function that accepts a string argument and returns a boolean with true indicating exclusion.
196
- *
197
- * @param {Array.<string|function(string):boolean|RegExp} exclusions An array of exclusion matchers
198
- * @returns {railsSerializerProvider} The provider for chaining
199
- */
200
- this.exclusionMatchers = function(exclusions) {
201
- defaultOptions.exclusionMatchers = exclusions;
202
- return this;
203
- };
204
-
205
- this.$get = ['$injector', 'RailsInflector', 'RailsResourceInjector', function ($injector, RailsInflector, RailsResourceInjector) {
206
- defaultOptions.underscore = defaultOptions.underscore || RailsInflector.underscore;
207
- defaultOptions.camelize = defaultOptions.camelize || RailsInflector.camelize;
208
- defaultOptions.pluralize = defaultOptions.pluralize || RailsInflector.pluralize;
209
-
210
- function railsSerializer(options, customizer) {
211
-
212
- function Serializer() {
213
- if (angular.isFunction(options)) {
214
- customizer = options;
215
- options = {};
216
- }
217
-
218
- this.exclusions = {};
219
- this.inclusions = {};
220
- this.serializeMappings = {};
221
- this.deserializeMappings = {};
222
- this.customSerializedAttributes = {};
223
- this.preservedAttributes = {};
224
- this.customSerializers = {};
225
- this.nestedResources = {};
226
- this.options = angular.extend({excludeByDefault: false}, defaultOptions, options || {});
227
-
228
- if (customizer) {
229
- customizer.call(this, this);
230
- }
231
- }
232
-
233
- /**
234
- * Accepts a variable list of attribute names to exclude from JSON serialization.
235
- *
236
- * @param attributeNames... {string} Variable number of attribute name parameters
237
- * @returns {Serializer} this for chaining support
238
- */
239
- Serializer.prototype.exclude = function () {
240
- var exclusions = this.exclusions;
241
-
242
- angular.forEach(arguments, function (attributeName) {
243
- exclusions[attributeName] = false;
244
- });
245
-
246
- return this;
247
- };
248
-
249
- /**
250
- * Accepts a variable list of attribute names that should be included in JSON serialization.
251
- * Using this method will by default exclude all other attributes and only the ones explicitly included using <code>only</code> will be serialized.
252
- * @param attributeNames... {string} Variable number of attribute name parameters
253
- * @returns {Serializer} this for chaining support
254
- */
255
- Serializer.prototype.only = function () {
256
- var inclusions = this.inclusions;
257
- this.options.excludeByDefault = true;
258
-
259
- angular.forEach(arguments, function (attributeName) {
260
- inclusions[attributeName] = true;
261
- });
262
-
263
- return this;
264
- };
265
-
266
- /**
267
- * This is a shortcut for rename that allows you to specify a variable number of attributes that should all be renamed to
268
- * <code>{attributeName}_attributes</code> to work with the Rails nested_attributes feature.
269
- * @param attributeNames... {string} Variable number of attribute name parameters
270
- * @returns {Serializer} this for chaining support
271
- */
272
- Serializer.prototype.nestedAttribute = function () {
273
- var self = this;
274
-
275
- angular.forEach(arguments, function (attributeName) {
276
- self.rename(attributeName, attributeName + '_attributes');
277
- });
278
-
279
- return this;
280
- };
281
-
282
- /**
283
- * Specifies an attribute that is a nested resource within the parent object.
284
- * Nested resources do not imply nested attributes, if you want both you still have to specify call <code>nestedAttribute</code> as well.
285
- *
286
- * A nested resource serves two purposes. First, it defines the resource that should be used when constructing resources from the server.
287
- * Second, it specifies how the nested object should be serialized.
288
- *
289
- * An optional third parameter <code>serializer</code> is available to override the serialization logic
290
- * of the resource in case you need to serialize it differently in multiple contexts.
291
- *
292
- * @param attributeName {string} The name of the attribute that is a nested resource
293
- * @param resource {string | Resource} A reference to the resource that the attribute is a type of.
294
- * @param serializer {string | Serializer} (optional) An optional serializer reference to override the nested resource's default serializer
295
- * @returns {Serializer} this for chaining support
296
- */
297
- Serializer.prototype.resource = function (attributeName, resource, serializer) {
298
- this.nestedResources[attributeName] = resource;
299
-
300
- if (serializer) {
301
- this.serializeWith(attributeName, serializer);
302
- }
303
-
304
- return this;
305
- };
306
-
307
- /**
308
- * Specifies a custom name mapping for an attribute.
309
- * On serializing to JSON the jsonName will be used.
310
- * On deserialization, if jsonName is seen then it will be renamed as javascriptName in the resulting resource.
311
- *
312
- * @param javascriptName {string} The attribute name as it appears in the JavaScript object
313
- * @param jsonName {string} The attribute name as it should appear in JSON
314
- * @param bidirectional {boolean} (optional) Allows turning off the bidirectional renaming, defaults to true.
315
- * @returns {Serializer} this for chaining support
316
- */
317
- Serializer.prototype.rename = function (javascriptName, jsonName, bidirectional) {
318
- this.serializeMappings[javascriptName] = jsonName;
319
-
320
- if (bidirectional || bidirectional === undefined) {
321
- this.deserializeMappings[jsonName] = javascriptName;
322
- }
323
- return this;
324
- };
325
-
326
- /**
327
- * Allows custom attribute creation as part of the serialization to JSON.
328
- *
329
- * @param attributeName {string} The name of the attribute to add
330
- * @param value {*} The value to add, if specified as a function then the function will be called during serialization
331
- * and should return the value to add.
332
- * @returns {Serializer} this for chaining support
333
- */
334
- Serializer.prototype.add = function (attributeName, value) {
335
- this.customSerializedAttributes[attributeName] = value;
336
- return this;
337
- };
338
-
339
-
340
- /**
341
- * Allows the attribute to be preserved unmodified in the resulting object.
342
- *
343
- * @param attributeName {string} The name of the attribute to add
344
- * @returns {Serializer} this for chaining support
345
- */
346
- Serializer.prototype.preserve = function(attributeName) {
347
- this.preservedAttributes[attributeName] = true;
348
- return this;
349
- };
350
-
351
- /**
352
- * Specify a custom serializer to use for an attribute.
353
- *
354
- * @param attributeName {string} The name of the attribute
355
- * @param serializer {string | function} A reference to the custom serializer to use for the attribute.
356
- * @returns {Serializer} this for chaining support
357
- */
358
- Serializer.prototype.serializeWith = function (attributeName, serializer) {
359
- this.customSerializers[attributeName] = serializer;
360
- return this;
361
- };
362
-
363
- /**
364
- * Determines whether or not an attribute should be excluded.
365
- *
366
- * If the option excludeByDefault has been set then attributes will default to excluded and will only
367
- * be included if they have been included using the "only" customization function.
368
- *
369
- * If the option excludeByDefault has not been set then attributes must be explicitly excluded using the "exclude"
370
- * customization function or must be matched by one of the exclusionMatchers.
371
- *
372
- * @param attributeName The name of the attribute to check for exclusion
373
- * @returns {boolean} true if excluded, false otherwise
374
- */
375
- Serializer.prototype.isExcludedFromSerialization = function (attributeName) {
376
- if ((this.options.excludeByDefault && !this.inclusions.hasOwnProperty(attributeName)) || this.exclusions.hasOwnProperty(attributeName)) {
377
- return true;
378
- }
379
-
380
- if (this.options.exclusionMatchers) {
381
- var excluded = false;
382
-
383
- angular.forEach(this.options.exclusionMatchers, function (matcher) {
384
- if (angular.isString(matcher)) {
385
- excluded = excluded || attributeName.indexOf(matcher) === 0;
386
- } else if (angular.isFunction(matcher)) {
387
- excluded = excluded || matcher.call(undefined, attributeName);
388
- } else if (matcher instanceof RegExp) {
389
- excluded = excluded || matcher.test(attributeName);
390
- }
391
- });
392
-
393
- return excluded;
394
- }
395
-
396
- return false;
397
- };
398
-
399
- /**
400
- * Remaps the attribute name to the serialized form which includes:
401
- * - checking for exclusion
402
- * - remapping to a custom value specified by the rename customization function
403
- * - underscoring the name
404
- *
405
- * @param attributeName The current attribute name
406
- * @returns {*} undefined if the attribute should be excluded or the mapped attribute name
407
- */
408
- Serializer.prototype.getSerializedAttributeName = function (attributeName) {
409
- var mappedName = this.serializeMappings[attributeName] || attributeName;
410
-
411
- var mappedNameExcluded = this.isExcludedFromSerialization(mappedName),
412
- attributeNameExcluded = this.isExcludedFromSerialization(attributeName);
413
-
414
- if(this.options.excludeByDefault) {
415
- if(mappedNameExcluded && attributeNameExcluded) {
416
- return undefined;
417
- }
418
- } else {
419
- if (mappedNameExcluded || attributeNameExcluded) {
420
- return undefined;
421
- }
422
- }
423
-
424
- return this.underscore(mappedName);
425
- };
426
-
427
- /**
428
- * Determines whether or not an attribute should be excluded from deserialization.
429
- *
430
- * By default, we do not exclude any attributes from deserialization.
431
- *
432
- * @param attributeName The name of the attribute to check for exclusion
433
- * @returns {boolean} true if excluded, false otherwise
434
- */
435
- Serializer.prototype.isExcludedFromDeserialization = function (attributeName) {
436
- return false;
437
- };
438
-
439
- /**
440
- * Remaps the attribute name to the deserialized form which includes:
441
- * - camelizing the name
442
- * - checking for exclusion
443
- * - remapping to a custom value specified by the rename customization function
444
- *
445
- * @param attributeName The current attribute name
446
- * @returns {*} undefined if the attribute should be excluded or the mapped attribute name
447
- */
448
- Serializer.prototype.getDeserializedAttributeName = function (attributeName) {
449
- var camelizedName = this.camelize(attributeName);
450
-
451
- camelizedName = this.deserializeMappings[attributeName]
452
- || this.deserializeMappings[camelizedName]
453
- || camelizedName;
454
-
455
- if (this.isExcludedFromDeserialization(attributeName) || this.isExcludedFromDeserialization(camelizedName)) {
456
- return undefined;
457
- }
458
-
459
- return camelizedName;
460
- };
461
-
462
- /**
463
- * Returns a reference to the nested resource that has been specified for the attribute.
464
- * @param attributeName The attribute name
465
- * @returns {*} undefined if no nested resource has been specified or a reference to the nested resource class
466
- */
467
- Serializer.prototype.getNestedResource = function (attributeName) {
468
- return RailsResourceInjector.getDependency(this.nestedResources[attributeName]);
469
- };
470
-
471
- /**
472
- * Returns a custom serializer for the attribute if one has been specified. Custom serializers can be specified
473
- * in one of two ways. The serializeWith customization method allows specifying a custom serializer for any attribute.
474
- * Or an attribute could have been specified as a nested resource in which case the nested resource's serializer
475
- * is used. Custom serializers specified using serializeWith take precedence over the nested resource serializer.
476
- *
477
- * @param attributeName The attribute name
478
- * @returns {*} undefined if no custom serializer has been specified or an instance of the Serializer
479
- */
480
- Serializer.prototype.getAttributeSerializer = function (attributeName) {
481
- var resource = this.getNestedResource(attributeName),
482
- serializer = this.customSerializers[attributeName];
483
-
484
- // custom serializer takes precedence over resource serializer
485
- if (serializer) {
486
- return RailsResourceInjector.createService(serializer);
487
- } else if (resource) {
488
- return resource.config.serializer;
489
- }
490
-
491
- return undefined;
492
- };
493
-
494
-
495
- /**
496
- * Prepares the data for serialization to JSON.
497
- *
498
- * @param data The data to prepare
499
- * @returns {*} A new object or array that is ready for JSON serialization
500
- */
501
- Serializer.prototype.serializeValue = function (data) {
502
- var result = data,
503
- self = this;
504
-
505
- if (angular.isArray(data)) {
506
- result = [];
507
-
508
- angular.forEach(data, function (value) {
509
- result.push(self.serializeValue(value));
510
- });
511
- } else if (angular.isObject(data)) {
512
- if (angular.isDate(data)) {
513
- return data;
514
- }
515
- result = {};
516
-
517
- angular.forEach(data, function (value, key) {
518
- // if the value is a function then it can't be serialized to JSON so we'll just skip it
519
- if (!angular.isFunction(value)) {
520
- self.serializeAttribute(result, key, value);
521
- }
522
- });
523
- }
524
-
525
- return result;
526
- };
527
-
528
- /**
529
- * Transforms an attribute and its value and stores it on the parent data object. The attribute will be
530
- * renamed as needed and the value itself will be serialized as well.
531
- *
532
- * @param data The object that the attribute will be added to
533
- * @param attribute The attribute to transform
534
- * @param value The current value of the attribute
535
- */
536
- Serializer.prototype.serializeAttribute = function (data, attribute, value) {
537
- var serializer = this.getAttributeSerializer(attribute),
538
- serializedAttributeName = this.getSerializedAttributeName(attribute);
539
-
540
- // undefined means the attribute should be excluded from serialization
541
- if (serializedAttributeName === undefined) {
542
- return;
543
- }
544
-
545
- data[serializedAttributeName] = serializer ? serializer.serialize(value) : this.serializeValue(value);
546
- };
547
-
548
- /**
549
- * Serializes the data by applying various transformations such as:
550
- * - Underscoring attribute names
551
- * - attribute renaming
552
- * - attribute exclusion
553
- * - custom attribute addition
554
- *
555
- * @param data The data to prepare
556
- * @returns {*} A new object or array that is ready for JSON serialization
557
- */
558
- Serializer.prototype.serialize = function (data) {
559
- var result = this.serializeValue(data),
560
- self = this;
561
-
562
- if (angular.isObject(result)) {
563
- angular.forEach(this.customSerializedAttributes, function (value, key) {
564
- if (angular.isFunction(value)) {
565
- value = value.call(data, data);
566
- }
567
-
568
- self.serializeAttribute(result, key, value);
569
- });
570
- }
571
-
572
- return result;
573
- };
574
-
575
- /**
576
- * Iterates over the data deserializing each entry on arrays and each key/value on objects.
577
- *
578
- * @param data The object to deserialize
579
- * @param Resource (optional) The resource type to deserialize the result into
580
- * @returns {*} A new object or an instance of Resource populated with deserialized data.
581
- */
582
- Serializer.prototype.deserializeValue = function (data, Resource) {
583
- var result = data,
584
- self = this;
585
-
586
- if (angular.isArray(data)) {
587
- result = [];
588
-
589
- angular.forEach(data, function (value) {
590
- result.push(self.deserializeValue(value, Resource));
591
- });
592
- } else if (angular.isObject(data)) {
593
- if (angular.isDate(data)) {
594
- return data;
595
- }
596
-
597
- result = {};
598
-
599
- if (Resource) {
600
- result = new Resource.config.resourceConstructor();
601
- }
602
-
603
- angular.forEach(data, function (value, key) {
604
- self.deserializeAttribute(result, key, value);
605
- });
606
- }
607
-
608
- return result;
609
- };
610
-
611
- /**
612
- * Transforms an attribute and its value and stores it on the parent data object. The attribute will be
613
- * renamed as needed and the value itself will be deserialized as well.
614
- *
615
- * @param data The object that the attribute will be added to
616
- * @param attribute The attribute to transform
617
- * @param value The current value of the attribute
618
- */
619
- Serializer.prototype.deserializeAttribute = function (data, attribute, value) {
620
- var serializer,
621
- NestedResource,
622
- attributeName = this.getDeserializedAttributeName(attribute);
623
-
624
- // undefined means the attribute should be excluded from serialization
625
- if (attributeName === undefined) {
626
- return;
627
- }
628
-
629
- serializer = this.getAttributeSerializer(attributeName);
630
- NestedResource = this.getNestedResource(attributeName);
631
-
632
- // preserved attributes are assigned unmodified
633
- if (this.preservedAttributes[attributeName]) {
634
- data[attributeName] = value;
635
- } else {
636
- data[attributeName] = serializer ? serializer.deserialize(value, NestedResource) : this.deserializeValue(value, NestedResource);
637
- }
638
- };
639
-
640
- /**
641
- * Deserializes the data by applying various transformations such as:
642
- * - Camelizing attribute names
643
- * - attribute renaming
644
- * - attribute exclusion
645
- * - nested resource creation
646
- *
647
- * @param data The object to deserialize
648
- * @param Resource (optional) The resource type to deserialize the result into
649
- * @returns {*} A new object or an instance of Resource populated with deserialized data
650
- */
651
- Serializer.prototype.deserialize = function (data, Resource) {
652
- // just calls deserializeValue for now so we can more easily add on custom attribute logic for deserialize too
653
- return this.deserializeValue(data, Resource);
654
- };
655
-
656
- Serializer.prototype.pluralize = function (value) {
657
- if (this.options.pluralize) {
658
- return this.options.pluralize(value);
659
- }
660
- return value;
661
- };
662
-
663
- Serializer.prototype.underscore = function (value) {
664
- if (this.options.underscore) {
665
- return this.options.underscore(value);
666
- }
667
- return value;
668
- };
669
-
670
- Serializer.prototype.camelize = function (value) {
671
- if (this.options.camelize) {
672
- return this.options.camelize(value);
673
- }
674
- return value;
675
- }
676
-
677
- return Serializer;
678
- }
679
-
680
- railsSerializer.defaultOptions = defaultOptions;
681
- return railsSerializer;
682
- }];
683
- });
684
- }());
685
-
686
- (function (undefined) {
687
- angular.module('rails').factory('railsRootWrappingTransformer', function () {
688
- return function (data, resource) {
689
- var result = {};
690
- result[angular.isArray(data) ? resource.config.pluralName : resource.config.name] = data;
691
- return result;
692
- };
693
- });
694
-
695
- angular.module('rails').factory('railsRootWrappingInterceptor', function () {
696
- return function (promise) {
697
- var resource = promise.resource;
698
-
699
- if (!resource) {
700
- return promise;
701
- }
702
-
703
- return promise.then(function (response) {
704
- if (response.data && response.data.hasOwnProperty(resource.config.name)) {
705
- response.data = response.data[resource.config.name];
706
- } else if (response.data && response.data.hasOwnProperty(resource.config.pluralName)) {
707
- response.data = response.data[resource.config.pluralName];
708
- }
709
-
710
- return response;
711
- });
712
- };
713
- });
714
-
715
- angular.module('rails').provider('RailsResource', function () {
716
- var defaultOptions = {
717
- rootWrapping: true,
718
- updateMethod: 'put',
719
- httpConfig: {},
720
- defaultParams: undefined
721
- };
722
-
723
- this.rootWrapping = function (value) {
724
- defaultOptions.rootWrapping = value;
725
- return this;
726
- };
727
-
728
- this.updateMethod = function (value) {
729
- defaultOptions.updateMethod = value;
730
- return this;
731
- };
732
-
733
- this.httpConfig = function (value) {
734
- defaultOptions.httpConfig = value;
735
- return this;
736
- };
737
-
738
- this.defaultParams = function (value) {
739
- defaultOptions.defaultParams = value;
740
- return this;
741
- };
742
-
743
- this.$get = ['$http', '$q', 'railsUrlBuilder', 'railsSerializer', 'railsRootWrappingTransformer', 'railsRootWrappingInterceptor', 'RailsResourceInjector',
744
- function ($http, $q, railsUrlBuilder, railsSerializer, railsRootWrappingTransformer, railsRootWrappingInterceptor, RailsResourceInjector) {
745
-
746
- function appendPath(url, path) {
747
- if (path) {
748
- if (path[0] !== '/') {
749
- url += '/';
750
- }
751
-
752
- url += path;
753
- }
754
-
755
- return url;
756
- }
757
-
758
- function forEachDependency(list, callback) {
759
- var dependency;
760
-
761
- for (var i = 0, len = list.length; i < len; i++) {
762
- dependency = list[i];
763
-
764
- if (angular.isString(dependency)) {
765
- dependency = list[i] = RailsResourceInjector.getDependency(dependency);
766
- }
767
-
768
- callback(dependency);
769
- }
770
- }
771
-
772
- function RailsResource(value) {
773
- var instance = this;
774
- if (value) {
775
- var immediatePromise = function (data) {
776
- return {
777
- resource: RailsResource,
778
- context: instance,
779
- response: data,
780
- then: function (callback) {
781
- this.response = callback(this.response, this.resource, this.context);
782
- return immediatePromise(this.response);
783
- }
784
- }
785
- };
786
-
787
- var data = this.constructor.callInterceptors(immediatePromise({data: value}), this).response.data;
788
- angular.extend(this, data);
789
- }
790
- }
791
-
792
- RailsResource.extend = function (child) {
793
- // Extend logic copied from CoffeeScript generated code
794
- var __hasProp = {}.hasOwnProperty, parent = this;
795
- for (var key in parent) {
796
- if (__hasProp.call(parent, key)) child[key] = parent[key];
797
- }
798
-
799
- function ctor() {
800
- this.constructor = child;
801
- }
802
-
803
- ctor.prototype = parent.prototype;
804
- child.prototype = new ctor();
805
- child.__super__ = parent.prototype;
806
- return child;
807
- };
808
-
809
- // allow calling configure multiple times to set configuration options and override values from inherited resources
810
- RailsResource.configure = function (cfg) {
811
- cfg = cfg || {};
812
-
813
- if (this.config) {
814
- cfg = angular.extend({}, this.config, cfg);
815
- }
816
-
817
- this.config = {};
818
- this.config.url = cfg.url;
819
- this.config.rootWrapping = cfg.rootWrapping === undefined ? defaultOptions.rootWrapping : cfg.rootWrapping; // using undefined check because config.rootWrapping || true would be true when config.rootWrapping === false
820
- this.config.httpConfig = cfg.httpConfig || defaultOptions.httpConfig;
821
- this.config.httpConfig.headers = angular.extend({'Accept': 'application/json', 'Content-Type': 'application/json'}, this.config.httpConfig.headers || {});
822
- this.config.defaultParams = cfg.defaultParams || defaultOptions.defaultParams;
823
- this.config.updateMethod = (cfg.updateMethod || defaultOptions.updateMethod).toLowerCase();
824
-
825
- this.config.requestTransformers = cfg.requestTransformers ? cfg.requestTransformers.slice(0) : [];
826
- this.config.responseInterceptors = cfg.responseInterceptors ? cfg.responseInterceptors.slice(0) : [];
827
- this.config.afterResponseInterceptors = cfg.afterResponseInterceptors ? cfg.afterResponseInterceptors.slice(0) : [];
828
-
829
- // strings and functions are not considered objects by angular.isObject()
830
- if (angular.isObject(cfg.serializer)) {
831
- this.config.serializer = cfg.serializer;
832
- } else {
833
- this.config.serializer = RailsResourceInjector.createService(cfg.serializer || railsSerializer());
834
- }
835
-
836
- this.config.name = this.config.serializer.underscore(cfg.name);
837
-
838
- // we don't want to turn undefined name into "undefineds" then the plural name won't update when the name is set
839
- if (this.config.name) {
840
- this.config.pluralName = this.config.serializer.underscore(cfg.pluralName || this.config.serializer.pluralize(this.config.name));
841
- }
842
-
843
- this.config.urlBuilder = railsUrlBuilder(this.config.url);
844
- this.config.resourceConstructor = this;
845
- };
846
-
847
- RailsResource.configure({});
848
-
849
- RailsResource.setUrl = function (url) {
850
- this.configure({url: url});
851
- };
852
-
853
- RailsResource.buildUrl = function (context) {
854
- return this.config.urlBuilder(context);
855
- };
856
-
857
- /**
858
- * Add a callback to run on response and construction.
859
- * @param fn(response data, constructor, context) - response data is either the resource instance returned or an array of resource instances,
860
- * constructor is the resource class calling the function,
861
- * context is the resource instance of the calling method (create, update, delete) or undefined if the method was a class method (get, query)
862
- */
863
- RailsResource.beforeResponse = function (fn) {
864
- fn = RailsResourceInjector.getDependency(fn);
865
- this.config.responseInterceptors.push(function (promise) {
866
- return promise.then(function (response) {
867
- fn(response.data, promise.resource.config.resourceConstructor, promise.context);
868
- return response;
869
- });
870
- });
871
- };
872
-
873
- /**
874
- * Add a callback to run after response has been processed. These callbacks are not called on object construction.
875
- * @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
876
- */
877
- RailsResource.afterResponse = function (fn) {
878
- fn = RailsResourceInjector.getDependency(fn);
879
- this.config.afterResponseInterceptors.push(function (promise) {
880
- return promise.then(function (response) {
881
- fn(response, promise.resource.config.resourceConstructor);
882
- return response;
883
- });
884
- });
885
- };
886
-
887
- /**
888
- * Adds a function to run after serializing the data to send to the server, but before root-wrapping it.
889
- * @param fn (data, constructor) - data object is the serialized resource instance, and constructor the resource class calling the function
890
- */
891
- RailsResource.beforeRequest = function (fn) {
892
- fn = RailsResourceInjector.getDependency(fn);
893
- this.config.requestTransformers.push(function (data, resource) {
894
- return fn(data, resource.config.resourceConstructor) || data;
895
- });
896
- };
897
-
898
- // transform data for request:
899
- RailsResource.transformData = function (data) {
900
- var config = this.config;
901
- data = config.serializer.serialize(data);
902
-
903
- forEachDependency(this.config.requestTransformers, function (transformer) {
904
- data = transformer(data, config.resourceConstructor);
905
- });
906
-
907
- if (config.rootWrapping) {
908
- data = railsRootWrappingTransformer(data, config.resourceConstructor);
909
- }
910
-
911
- return data;
912
- };
913
-
914
- // transform data on response:
915
- RailsResource.callInterceptors = function (promise, context) {
916
- var config = this.config;
917
-
918
- promise = promise.then(function (response) {
919
- // store off the data in case something (like our root unwrapping) assigns data as a new object
920
- response.originalData = response.data;
921
- return response;
922
- });
923
-
924
- if (config.rootWrapping) {
925
- promise.resource = config.resourceConstructor;
926
- promise = railsRootWrappingInterceptor(promise);
927
- }
928
-
929
- promise.then(function (response) {
930
- response.data = config.serializer.deserialize(response.data, config.resourceConstructor);
931
- return response;
932
- });
933
-
934
- // data is now deserialized. call response interceptors including beforeResponse
935
- forEachDependency(config.responseInterceptors, function (interceptor) {
936
- promise.resource = config.resourceConstructor;
937
- promise.context = context;
938
- promise = interceptor(promise);
939
- });
940
-
941
- return promise;
942
- };
943
-
944
- // transform data after response has been converted to a resource instance:
945
- RailsResource.callAfterInterceptors = function (promise) {
946
- var config = this.config;
947
- // data is now deserialized. call response interceptors including afterResponse
948
- forEachDependency(config.afterResponseInterceptors, function (interceptor) {
949
- promise.resource = config.resourceConstructor;
950
- promise = interceptor(promise);
951
- });
952
-
953
- return promise;
954
- };
955
-
956
- RailsResource.processResponse = function (promise) {
957
- promise = this.callInterceptors(promise).then(function (response) {
958
- return response.data;
959
- });
960
-
961
- return this.callAfterInterceptors(promise);
962
- };
963
-
964
- RailsResource.getParameters = function (queryParams) {
965
- var params;
966
-
967
- if (this.config.defaultParams) {
968
- params = this.config.defaultParams;
969
- }
970
-
971
- if (angular.isObject(queryParams)) {
972
- params = angular.extend(params || {}, queryParams);
973
- }
974
-
975
- return params;
976
- };
977
-
978
- RailsResource.getHttpConfig = function (queryParams) {
979
- var params = this.getParameters(queryParams);
980
-
981
- if (params) {
982
- return angular.extend({params: params}, this.config.httpConfig);
983
- }
984
-
985
- return angular.copy(this.config.httpConfig);
986
- };
987
-
988
- /**
989
- * Returns a URL from the given parameters. You can override this method on your resource definitions to provide
990
- * custom logic for building your URLs or you can utilize the parameterized url strings to substitute values in the
991
- * URL string.
992
- *
993
- * The parameters in the URL string follow the normal Angular binding expression using {{ and }} for the start/end symbols.
994
- *
995
- * If the context is a number and the URL string does not contain an id parameter then the number is appended
996
- * to the URL string.
997
- *
998
- * If the context is a number and the URL string does
999
- * @param context
1000
- * @param path {string} (optional) An additional path to append to the URL
1001
- * @return {string}
1002
- */
1003
- RailsResource.$url = RailsResource.resourceUrl = function (context, path) {
1004
- if (!angular.isObject(context)) {
1005
- context = {id: context};
1006
- }
1007
-
1008
- return appendPath(this.buildUrl(context || {}), path);
1009
- };
1010
-
1011
- RailsResource.$get = function (url, queryParams) {
1012
- return this.processResponse($http.get(url, this.getHttpConfig(queryParams)));
1013
- };
1014
-
1015
- RailsResource.query = function (queryParams, context) {
1016
- return this.$get(this.resourceUrl(context), queryParams);
1017
- };
1018
-
1019
- RailsResource.get = function (context, queryParams) {
1020
- return this.$get(this.resourceUrl(context), queryParams);
1021
- };
1022
-
1023
- /**
1024
- * Returns the URL for this resource.
1025
- *
1026
- * @param path {string} (optional) An additional path to append to the URL
1027
- * @returns {string} The URL for the resource
1028
- */
1029
- RailsResource.prototype.$url = function (path) {
1030
- return appendPath(this.constructor.resourceUrl(this), path);
1031
- };
1032
-
1033
- RailsResource.prototype.processResponse = function (promise) {
1034
- promise = this.constructor.callInterceptors(promise, this);
1035
-
1036
- promise = promise.then(angular.bind(this, function (response) {
1037
- // we may not have response data
1038
- if (response.hasOwnProperty('data') && angular.isObject(response.data)) {
1039
- angular.extend(this, response.data);
1040
- }
1041
-
1042
- return this;
1043
- }));
1044
-
1045
- return this.constructor.callAfterInterceptors(promise);
1046
- };
1047
-
1048
- angular.forEach(['post', 'put', 'patch'], function (method) {
1049
- RailsResource['$' + method] = function (url, data) {
1050
- var config;
1051
- // clone so we can manipulate w/o modifying the actual instance
1052
- data = this.transformData(angular.copy(data));
1053
- config = angular.extend({method: method, url: url, data: data}, this.getHttpConfig());
1054
- return this.processResponse($http(config));
1055
- };
1056
-
1057
- RailsResource.prototype['$' + method] = function (url) {
1058
- var data, config;
1059
- // clone so we can manipulate w/o modifying the actual instance
1060
- data = this.constructor.transformData(angular.copy(this, {}));
1061
- config = angular.extend({method: method, url: url, data: data}, this.constructor.getHttpConfig());
1062
- return this.processResponse($http(config));
1063
-
1064
- };
1065
- });
1066
-
1067
- RailsResource.prototype.create = function () {
1068
- return this.$post(this.$url(), this);
1069
- };
1070
-
1071
- RailsResource.prototype.update = function () {
1072
- return this['$' + this.constructor.config.updateMethod](this.$url(), this);
1073
- };
1074
-
1075
- RailsResource.prototype.isNew = function () {
1076
- return this.id == null;
1077
- };
1078
-
1079
- RailsResource.prototype.save = function () {
1080
- if (this.isNew()) {
1081
- return this.create();
1082
- } else {
1083
- return this.update();
1084
- }
1085
- };
1086
-
1087
- RailsResource['$delete'] = function (url) {
1088
- return this.processResponse($http['delete'](url, this.getHttpConfig()));
1089
- };
1090
-
1091
- RailsResource.prototype['$delete'] = function (url) {
1092
- return this.processResponse($http['delete'](url, this.constructor.getHttpConfig()));
1093
- };
1094
-
1095
- //using ['delete'] instead of .delete for IE7/8 compatibility
1096
- RailsResource.prototype.remove = RailsResource.prototype['delete'] = function () {
1097
- return this.$delete(this.$url());
1098
- };
1099
-
1100
- return RailsResource;
1101
- }];
1102
- });
1103
-
1104
- angular.module('rails').factory('railsResourceFactory', ['RailsResource', function (RailsResource) {
1105
- return function (config) {
1106
- function Resource() {
1107
- Resource.__super__.constructor.apply(this, arguments);
1108
- }
1109
-
1110
- RailsResource.extend(Resource);
1111
- Resource.configure(config);
1112
-
1113
- return Resource;
1114
- }
1115
- }]);
1116
-
1117
- }());