angularjs-rails-resource 0.2.1 → 0.2.2

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.
@@ -1,471 +1,526 @@
1
1
  (function (undefined) {
2
- angular.module('rails').factory('railsSerializer', ['$injector', 'RailsInflector', 'RailsResourceInjector', function ($injector, RailsInflector, RailsResourceInjector) {
2
+ angular.module('rails').provider('railsSerializer', function() {
3
3
  var defaultOptions = {
4
- underscore: RailsInflector.underscore,
5
- camelize: RailsInflector.camelize,
6
- pluralize: RailsInflector.pluralize,
7
- exclusionMatchers: [],
8
- excludeByDefault: false
4
+ underscore: undefined,
5
+ camelize: undefined,
6
+ pluralize: undefined,
7
+ exclusionMatchers: []
9
8
  };
10
9
 
11
- function railsSerializer(options, customizer) {
10
+ /**
11
+ * Configures the underscore method used by the serializer. If not defined then <code>RailsInflector.underscore</code>
12
+ * will be used.
13
+ *
14
+ * @param {function(string):string} fn The function to use for underscore conversion
15
+ * @returns {railsSerializerProvider} The provider for chaining
16
+ */
17
+ this.underscore = function(fn) {
18
+ defaultOptions.underscore = fn;
19
+ return this;
20
+ };
12
21
 
13
- function Serializer() {
14
- if (angular.isFunction(options)) {
15
- customizer = options;
16
- options = {};
17
- }
22
+ /**
23
+ * Configures the camelize method used by the serializer. If not defined then <code>RailsInflector.camelize</code>
24
+ * will be used.
25
+ *
26
+ * @param {function(string):string} fn The function to use for camelize conversion
27
+ * @returns {railsSerializerProvider} The provider for chaining
28
+ */
29
+ this.camelize = function(fn) {
30
+ defaultOptions.camelize = fn;
31
+ return this;
32
+ };
18
33
 
19
- this.exclusions = {};
20
- this.inclusions = {};
21
- this.serializeMappings = {};
22
- this.deserializeMappings = {};
23
- this.customSerializedAttributes = {};
24
- this.preservedAttributes = {};
25
- this.customSerializers = {};
26
- this.nestedResources = {};
27
- this.options = angular.extend({}, defaultOptions, options || {});
28
-
29
- if (customizer) {
30
- customizer.call(this, this);
31
- }
32
- }
34
+ /**
35
+ * Configures the pluralize method used by the serializer. If not defined then <code>RailsInflector.pluralize</code>
36
+ * will be used.
37
+ *
38
+ * @param {function(string):string} fn The function to use for pluralizing strings.
39
+ * @returns {railsSerializerProvider} The provider for chaining
40
+ */
41
+ this.pluralize = function(fn) {
42
+ defaultOptions.pluralize = fn;
43
+ return this;
44
+ };
33
45
 
34
- /**
35
- * Accepts a variable list of attribute names to exclude from JSON serialization.
36
- *
37
- * @param attributeNames... {string} Variable number of attribute name parameters
38
- * @returns {Serializer} this for chaining support
39
- */
40
- Serializer.prototype.exclude = function () {
41
- var exclusions = this.exclusions;
42
-
43
- angular.forEach(arguments, function (attributeName) {
44
- exclusions[attributeName] = false;
45
- });
46
-
47
- return this;
48
- };
49
-
50
- /**
51
- * Accepts a variable list of attribute names that should be included in JSON serialization.
52
- * Using this method will by default exclude all other attributes and only the ones explicitly included using <code>only</code> will be serialized.
53
- * @param attributeNames... {string} Variable number of attribute name parameters
54
- * @returns {Serializer} this for chaining support
55
- */
56
- Serializer.prototype.only = function () {
57
- var inclusions = this.inclusions;
58
- this.options.excludeByDefault = true;
59
-
60
- angular.forEach(arguments, function (attributeName) {
61
- inclusions[attributeName] = true;
62
- });
63
-
64
- return this;
65
- };
66
-
67
- /**
68
- * This is a shortcut for rename that allows you to specify a variable number of attributes that should all be renamed to
69
- * <code>{attributeName}_attributes</code> to work with the Rails nested_attributes feature.
70
- * @param attributeNames... {string} Variable number of attribute name parameters
71
- * @returns {Serializer} this for chaining support
72
- */
73
- Serializer.prototype.nestedAttribute = function () {
74
- var self = this;
75
-
76
- angular.forEach(arguments, function (attributeName) {
77
- self.rename(attributeName, attributeName + '_attributes');
78
- });
79
-
80
- return this;
81
- };
82
-
83
- /**
84
- * Specifies an attribute that is a nested resource within the parent object.
85
- * Nested resources do not imply nested attributes, if you want both you still have to specify call <code>nestedAttribute</code> as well.
86
- *
87
- * A nested resource serves two purposes. First, it defines the resource that should be used when constructing resources from the server.
88
- * Second, it specifies how the nested object should be serialized.
89
- *
90
- * An optional third parameter <code>serializer</code> is available to override the serialization logic
91
- * of the resource in case you need to serialize it differently in multiple contexts.
92
- *
93
- * @param attributeName {string} The name of the attribute that is a nested resource
94
- * @param resource {string | Resource} A reference to the resource that the attribute is a type of.
95
- * @param serializer {string | Serializer} (optional) An optional serializer reference to override the nested resource's default serializer
96
- * @returns {Serializer} this for chaining support
97
- */
98
- Serializer.prototype.resource = function (attributeName, resource, serializer) {
99
- this.nestedResources[attributeName] = resource;
100
-
101
- if (serializer) {
102
- this.serializeWith(attributeName, serializer);
103
- }
46
+ /**
47
+ * Configures the array exclusion matchers by the serializer. Exclusion matchers can be one of the following:
48
+ * * string - Defines a prefix that is used to test for exclusion
49
+ * * RegExp - A custom regular expression that is tested against the attribute name
50
+ * * function - A custom function that accepts a string argument and returns a boolean with true indicating exclusion.
51
+ *
52
+ * @param {Array.<string|function(string):boolean|RegExp} exclusions An array of exclusion matchers
53
+ * @returns {railsSerializerProvider} The provider for chaining
54
+ */
55
+ this.exclusionMatchers = function(exclusions) {
56
+ defaultOptions.exclusionMatchers = exclusions;
57
+ return this;
58
+ };
104
59
 
105
- return this;
106
- };
107
-
108
- /**
109
- * Specifies a custom name mapping for an attribute.
110
- * On serializing to JSON the jsonName will be used.
111
- * On deserialization, if jsonName is seen then it will be renamed as javascriptName in the resulting resource.
112
- *
113
- * @param javascriptName {string} The attribute name as it appears in the JavaScript object
114
- * @param jsonName {string} The attribute name as it should appear in JSON
115
- * @param bidirectional {boolean} (optional) Allows turning off the bidirectional renaming, defaults to true.
116
- * @returns {Serializer} this for chaining support
117
- */
118
- Serializer.prototype.rename = function (javascriptName, jsonName, bidirectional) {
119
- this.serializeMappings[javascriptName] = jsonName;
120
-
121
- if (bidirectional || bidirectional === undefined) {
122
- this.deserializeMappings[jsonName] = javascriptName;
123
- }
124
- return this;
125
- };
126
-
127
- /**
128
- * Allows custom attribute creation as part of the serialization to JSON.
129
- *
130
- * @param attributeName {string} The name of the attribute to add
131
- * @param value {*} The value to add, if specified as a function then the function will be called during serialization
132
- * and should return the value to add.
133
- * @returns {Serializer} this for chaining support
134
- */
135
- Serializer.prototype.add = function (attributeName, value) {
136
- this.customSerializedAttributes[attributeName] = value;
137
- return this;
138
- };
139
-
140
-
141
- /**
142
- * Allows the attribute to be preserved unmodified in the resulting object.
143
- *
144
- * @param attributeName {string} The name of the attribute to add
145
- * @returns {Serializer} this for chaining support
146
- */
147
- Serializer.prototype.preserve = function(attributeName) {
148
- this.preservedAttributes[attributeName] = true;
149
- return this;
150
- };
151
-
152
- /**
153
- * Specify a custom serializer to use for an attribute.
154
- *
155
- * @param attributeName {string} The name of the attribute
156
- * @param serializer {string | constructor} A reference to the custom serializer to use for the attribute.
157
- * @returns {Serializer} this for chaining support
158
- */
159
- Serializer.prototype.serializeWith = function (attributeName, serializer) {
160
- this.customSerializers[attributeName] = serializer;
161
- return this;
162
- };
163
-
164
- /**
165
- * Determines whether or not an attribute should be excluded.
166
- *
167
- * If the option excludeByDefault has been set then attributes will default to excluded and will only
168
- * be included if they have been included using the "only" customization function.
169
- *
170
- * If the option excludeByDefault has not been set then attributes must be explicitly excluded using the "exclude"
171
- * customization function or must be matched by one of the exclusionMatchers.
172
- *
173
- * @param attributeName The name of the attribute to check for exclusion
174
- * @returns {boolean} true if excluded, false otherwise
175
- */
176
- Serializer.prototype.isExcludedFromSerialization = function (attributeName) {
177
- if ((this.options.excludeByDefault && !this.inclusions.hasOwnProperty(attributeName)) || this.exclusions.hasOwnProperty(attributeName)) {
178
- return true;
60
+ this.$get = ['$injector', 'RailsInflector', 'RailsResourceInjector', function ($injector, RailsInflector, RailsResourceInjector) {
61
+ defaultOptions.underscore = defaultOptions.underscore || RailsInflector.underscore;
62
+ defaultOptions.camelize = defaultOptions.camelize || RailsInflector.camelize;
63
+ defaultOptions.pluralize = defaultOptions.pluralize || RailsInflector.pluralize;
64
+
65
+ function railsSerializer(options, customizer) {
66
+
67
+ function Serializer() {
68
+ if (angular.isFunction(options)) {
69
+ customizer = options;
70
+ options = {};
71
+ }
72
+
73
+ this.exclusions = {};
74
+ this.inclusions = {};
75
+ this.serializeMappings = {};
76
+ this.deserializeMappings = {};
77
+ this.customSerializedAttributes = {};
78
+ this.preservedAttributes = {};
79
+ this.customSerializers = {};
80
+ this.nestedResources = {};
81
+ this.options = angular.extend({excludeByDefault: false}, defaultOptions, options || {});
82
+
83
+ if (customizer) {
84
+ customizer.call(this, this);
85
+ }
179
86
  }
180
87
 
181
- if (this.options.exclusionMatchers) {
182
- var excluded = false;
88
+ /**
89
+ * Accepts a variable list of attribute names to exclude from JSON serialization.
90
+ *
91
+ * @param attributeNames... {string} Variable number of attribute name parameters
92
+ * @returns {Serializer} this for chaining support
93
+ */
94
+ Serializer.prototype.exclude = function () {
95
+ var exclusions = this.exclusions;
96
+
97
+ angular.forEach(arguments, function (attributeName) {
98
+ exclusions[attributeName] = false;
99
+ });
183
100
 
184
- angular.forEach(this.options.exclusionMatchers, function (matcher) {
185
- if (angular.isString(matcher)) {
186
- excluded = excluded || attributeName.indexOf(matcher) === 0;
187
- } else if (angular.isFunction(matcher)) {
188
- excluded = excluded || matcher.call(undefined, attributeName);
189
- } else if (matcher instanceof RegExp) {
190
- excluded = excluded || matcher.test(attributeName);
191
- }
101
+ return this;
102
+ };
103
+
104
+ /**
105
+ * Accepts a variable list of attribute names that should be included in JSON serialization.
106
+ * Using this method will by default exclude all other attributes and only the ones explicitly included using <code>only</code> will be serialized.
107
+ * @param attributeNames... {string} Variable number of attribute name parameters
108
+ * @returns {Serializer} this for chaining support
109
+ */
110
+ Serializer.prototype.only = function () {
111
+ var inclusions = this.inclusions;
112
+ this.options.excludeByDefault = true;
113
+
114
+ angular.forEach(arguments, function (attributeName) {
115
+ inclusions[attributeName] = true;
192
116
  });
193
117
 
194
- return excluded;
195
- }
118
+ return this;
119
+ };
196
120
 
197
- return false;
198
- };
199
-
200
- /**
201
- * Remaps the attribute name to the serialized form which includes:
202
- * - checking for exclusion
203
- * - remapping to a custom value specified by the rename customization function
204
- * - underscoring the name
205
- *
206
- * @param attributeName The current attribute name
207
- * @returns {*} undefined if the attribute should be excluded or the mapped attribute name
208
- */
209
- Serializer.prototype.getSerializedAttributeName = function (attributeName) {
210
- var mappedName = this.serializeMappings[attributeName] || attributeName;
211
-
212
- if (this.isExcludedFromSerialization(attributeName) || this.isExcludedFromSerialization(mappedName)) {
213
- return undefined;
214
- }
121
+ /**
122
+ * This is a shortcut for rename that allows you to specify a variable number of attributes that should all be renamed to
123
+ * <code>{attributeName}_attributes</code> to work with the Rails nested_attributes feature.
124
+ * @param attributeNames... {string} Variable number of attribute name parameters
125
+ * @returns {Serializer} this for chaining support
126
+ */
127
+ Serializer.prototype.nestedAttribute = function () {
128
+ var self = this;
215
129
 
216
- return this.underscore(mappedName);
217
- };
218
-
219
- /**
220
- * Determines whether or not an attribute should be excluded from deserialization.
221
- *
222
- * By default, we do not exclude any attributes from deserialization.
223
- *
224
- * @param attributeName The name of the attribute to check for exclusion
225
- * @returns {boolean} true if excluded, false otherwise
226
- */
227
- Serializer.prototype.isExcludedFromDeserialization = function (attributeName) {
228
- return false;
229
- };
230
-
231
- /**
232
- * Remaps the attribute name to the deserialized form which includes:
233
- * - camelizing the name
234
- * - checking for exclusion
235
- * - remapping to a custom value specified by the rename customization function
236
- *
237
- * @param attributeName The current attribute name
238
- * @returns {*} undefined if the attribute should be excluded or the mapped attribute name
239
- */
240
- Serializer.prototype.getDeserializedAttributeName = function (attributeName) {
241
- var camelizedName = this.camelize(attributeName);
242
-
243
- camelizedName = this.deserializeMappings[attributeName]
244
- || this.deserializeMappings[camelizedName]
245
- || camelizedName;
246
-
247
- if (this.isExcludedFromDeserialization(attributeName) || this.isExcludedFromDeserialization(camelizedName)) {
248
- return undefined;
249
- }
130
+ angular.forEach(arguments, function (attributeName) {
131
+ self.rename(attributeName, attributeName + '_attributes');
132
+ });
250
133
 
251
- return camelizedName;
252
- };
253
-
254
- /**
255
- * Returns a reference to the nested resource that has been specified for the attribute.
256
- * @param attributeName The attribute name
257
- * @returns {*} undefined if no nested resource has been specified or a reference to the nested resource class
258
- */
259
- Serializer.prototype.getNestedResource = function (attributeName) {
260
- return RailsResourceInjector.getDependency(this.nestedResources[attributeName]);
261
- };
262
-
263
- /**
264
- * Returns a custom serializer for the attribute if one has been specified. Custom serializers can be specified
265
- * in one of two ways. The serializeWith customization method allows specifying a custom serializer for any attribute.
266
- * Or an attribute could have been specified as a nested resource in which case the nested resource's serializer
267
- * is used. Custom serializers specified using serializeWith take precedence over the nested resource serializer.
268
- *
269
- * @param attributeName The attribute name
270
- * @returns {*} undefined if no custom serializer has been specified or an instance of the Serializer
271
- */
272
- Serializer.prototype.getAttributeSerializer = function (attributeName) {
273
- var resource = this.getNestedResource(attributeName),
274
- serializer = this.customSerializers[attributeName];
275
-
276
- // custom serializer takes precedence over resource serializer
277
- if (serializer) {
278
- return RailsResourceInjector.createService(serializer)
279
- } else if (resource) {
280
- return resource.serializer;
281
- }
134
+ return this;
135
+ };
136
+
137
+ /**
138
+ * Specifies an attribute that is a nested resource within the parent object.
139
+ * Nested resources do not imply nested attributes, if you want both you still have to specify call <code>nestedAttribute</code> as well.
140
+ *
141
+ * A nested resource serves two purposes. First, it defines the resource that should be used when constructing resources from the server.
142
+ * Second, it specifies how the nested object should be serialized.
143
+ *
144
+ * An optional third parameter <code>serializer</code> is available to override the serialization logic
145
+ * of the resource in case you need to serialize it differently in multiple contexts.
146
+ *
147
+ * @param attributeName {string} The name of the attribute that is a nested resource
148
+ * @param resource {string | Resource} A reference to the resource that the attribute is a type of.
149
+ * @param serializer {string | Serializer} (optional) An optional serializer reference to override the nested resource's default serializer
150
+ * @returns {Serializer} this for chaining support
151
+ */
152
+ Serializer.prototype.resource = function (attributeName, resource, serializer) {
153
+ this.nestedResources[attributeName] = resource;
154
+
155
+ if (serializer) {
156
+ this.serializeWith(attributeName, serializer);
157
+ }
282
158
 
283
- return undefined;
284
- };
159
+ return this;
160
+ };
161
+
162
+ /**
163
+ * Specifies a custom name mapping for an attribute.
164
+ * On serializing to JSON the jsonName will be used.
165
+ * On deserialization, if jsonName is seen then it will be renamed as javascriptName in the resulting resource.
166
+ *
167
+ * @param javascriptName {string} The attribute name as it appears in the JavaScript object
168
+ * @param jsonName {string} The attribute name as it should appear in JSON
169
+ * @param bidirectional {boolean} (optional) Allows turning off the bidirectional renaming, defaults to true.
170
+ * @returns {Serializer} this for chaining support
171
+ */
172
+ Serializer.prototype.rename = function (javascriptName, jsonName, bidirectional) {
173
+ this.serializeMappings[javascriptName] = jsonName;
174
+
175
+ if (bidirectional || bidirectional === undefined) {
176
+ this.deserializeMappings[jsonName] = javascriptName;
177
+ }
178
+ return this;
179
+ };
180
+
181
+ /**
182
+ * Allows custom attribute creation as part of the serialization to JSON.
183
+ *
184
+ * @param attributeName {string} The name of the attribute to add
185
+ * @param value {*} The value to add, if specified as a function then the function will be called during serialization
186
+ * and should return the value to add.
187
+ * @returns {Serializer} this for chaining support
188
+ */
189
+ Serializer.prototype.add = function (attributeName, value) {
190
+ this.customSerializedAttributes[attributeName] = value;
191
+ return this;
192
+ };
193
+
194
+
195
+ /**
196
+ * Allows the attribute to be preserved unmodified in the resulting object.
197
+ *
198
+ * @param attributeName {string} The name of the attribute to add
199
+ * @returns {Serializer} this for chaining support
200
+ */
201
+ Serializer.prototype.preserve = function(attributeName) {
202
+ this.preservedAttributes[attributeName] = true;
203
+ return this;
204
+ };
205
+
206
+ /**
207
+ * Specify a custom serializer to use for an attribute.
208
+ *
209
+ * @param attributeName {string} The name of the attribute
210
+ * @param serializer {string | constructor} A reference to the custom serializer to use for the attribute.
211
+ * @returns {Serializer} this for chaining support
212
+ */
213
+ Serializer.prototype.serializeWith = function (attributeName, serializer) {
214
+ this.customSerializers[attributeName] = serializer;
215
+ return this;
216
+ };
217
+
218
+ /**
219
+ * Determines whether or not an attribute should be excluded.
220
+ *
221
+ * If the option excludeByDefault has been set then attributes will default to excluded and will only
222
+ * be included if they have been included using the "only" customization function.
223
+ *
224
+ * If the option excludeByDefault has not been set then attributes must be explicitly excluded using the "exclude"
225
+ * customization function or must be matched by one of the exclusionMatchers.
226
+ *
227
+ * @param attributeName The name of the attribute to check for exclusion
228
+ * @returns {boolean} true if excluded, false otherwise
229
+ */
230
+ Serializer.prototype.isExcludedFromSerialization = function (attributeName) {
231
+ if ((this.options.excludeByDefault && !this.inclusions.hasOwnProperty(attributeName)) || this.exclusions.hasOwnProperty(attributeName)) {
232
+ return true;
233
+ }
285
234
 
235
+ if (this.options.exclusionMatchers) {
236
+ var excluded = false;
286
237
 
287
- /**
288
- * Prepares the data for serialization to JSON.
289
- *
290
- * @param data The data to prepare
291
- * @returns {*} A new object or array that is ready for JSON serialization
292
- */
293
- Serializer.prototype.serializeValue = function (data) {
294
- var result = data,
295
- self = this;
238
+ angular.forEach(this.options.exclusionMatchers, function (matcher) {
239
+ if (angular.isString(matcher)) {
240
+ excluded = excluded || attributeName.indexOf(matcher) === 0;
241
+ } else if (angular.isFunction(matcher)) {
242
+ excluded = excluded || matcher.call(undefined, attributeName);
243
+ } else if (matcher instanceof RegExp) {
244
+ excluded = excluded || matcher.test(attributeName);
245
+ }
246
+ });
296
247
 
297
- if (angular.isArray(data)) {
298
- result = [];
248
+ return excluded;
249
+ }
299
250
 
300
- angular.forEach(data, function (value) {
301
- result.push(self.serializeValue(value));
302
- });
303
- } else if (angular.isObject(data)) {
304
- if (angular.isDate(data)) {
305
- return data;
251
+ return false;
252
+ };
253
+
254
+ /**
255
+ * Remaps the attribute name to the serialized form which includes:
256
+ * - checking for exclusion
257
+ * - remapping to a custom value specified by the rename customization function
258
+ * - underscoring the name
259
+ *
260
+ * @param attributeName The current attribute name
261
+ * @returns {*} undefined if the attribute should be excluded or the mapped attribute name
262
+ */
263
+ Serializer.prototype.getSerializedAttributeName = function (attributeName) {
264
+ var mappedName = this.serializeMappings[attributeName] || attributeName;
265
+
266
+ if (this.isExcludedFromSerialization(attributeName) || this.isExcludedFromSerialization(mappedName)) {
267
+ return undefined;
306
268
  }
307
- result = {};
308
269
 
309
- angular.forEach(data, function (value, key) {
310
- // if the value is a function then it can't be serialized to JSON so we'll just skip it
311
- if (!angular.isFunction(value)) {
312
- self.serializeAttribute(result, key, value);
313
- }
314
- });
315
- }
270
+ return this.underscore(mappedName);
271
+ };
272
+
273
+ /**
274
+ * Determines whether or not an attribute should be excluded from deserialization.
275
+ *
276
+ * By default, we do not exclude any attributes from deserialization.
277
+ *
278
+ * @param attributeName The name of the attribute to check for exclusion
279
+ * @returns {boolean} true if excluded, false otherwise
280
+ */
281
+ Serializer.prototype.isExcludedFromDeserialization = function (attributeName) {
282
+ return false;
283
+ };
284
+
285
+ /**
286
+ * Remaps the attribute name to the deserialized form which includes:
287
+ * - camelizing the name
288
+ * - checking for exclusion
289
+ * - remapping to a custom value specified by the rename customization function
290
+ *
291
+ * @param attributeName The current attribute name
292
+ * @returns {*} undefined if the attribute should be excluded or the mapped attribute name
293
+ */
294
+ Serializer.prototype.getDeserializedAttributeName = function (attributeName) {
295
+ var camelizedName = this.camelize(attributeName);
296
+
297
+ camelizedName = this.deserializeMappings[attributeName]
298
+ || this.deserializeMappings[camelizedName]
299
+ || camelizedName;
300
+
301
+ if (this.isExcludedFromDeserialization(attributeName) || this.isExcludedFromDeserialization(camelizedName)) {
302
+ return undefined;
303
+ }
316
304
 
317
- return result;
318
- };
319
-
320
- /**
321
- * Transforms an attribute and its value and stores it on the parent data object. The attribute will be
322
- * renamed as needed and the value itself will be serialized as well.
323
- *
324
- * @param data The object that the attribute will be added to
325
- * @param attribute The attribute to transform
326
- * @param value The current value of the attribute
327
- */
328
- Serializer.prototype.serializeAttribute = function (data, attribute, value) {
329
- var serializer = this.getAttributeSerializer(attribute),
330
- serializedAttributeName = this.getSerializedAttributeName(attribute);
331
-
332
- // undefined means the attribute should be excluded from serialization
333
- if (serializedAttributeName === undefined) {
334
- return;
335
- }
305
+ return camelizedName;
306
+ };
307
+
308
+ /**
309
+ * Returns a reference to the nested resource that has been specified for the attribute.
310
+ * @param attributeName The attribute name
311
+ * @returns {*} undefined if no nested resource has been specified or a reference to the nested resource class
312
+ */
313
+ Serializer.prototype.getNestedResource = function (attributeName) {
314
+ return RailsResourceInjector.getDependency(this.nestedResources[attributeName]);
315
+ };
316
+
317
+ /**
318
+ * Returns a custom serializer for the attribute if one has been specified. Custom serializers can be specified
319
+ * in one of two ways. The serializeWith customization method allows specifying a custom serializer for any attribute.
320
+ * Or an attribute could have been specified as a nested resource in which case the nested resource's serializer
321
+ * is used. Custom serializers specified using serializeWith take precedence over the nested resource serializer.
322
+ *
323
+ * @param attributeName The attribute name
324
+ * @returns {*} undefined if no custom serializer has been specified or an instance of the Serializer
325
+ */
326
+ Serializer.prototype.getAttributeSerializer = function (attributeName) {
327
+ var resource = this.getNestedResource(attributeName),
328
+ serializer = this.customSerializers[attributeName];
329
+
330
+ // custom serializer takes precedence over resource serializer
331
+ if (serializer) {
332
+ return RailsResourceInjector.createService(serializer)
333
+ } else if (resource) {
334
+ return resource.serializer;
335
+ }
336
336
 
337
- data[serializedAttributeName] = serializer ? serializer.serialize(value) : this.serializeValue(value);
338
- };
339
-
340
- /**
341
- * Serializes the data by applying various transformations such as:
342
- * - Underscoring attribute names
343
- * - attribute renaming
344
- * - attribute exclusion
345
- * - custom attribute addition
346
- *
347
- * @param data The data to prepare
348
- * @returns {*} A new object or array that is ready for JSON serialization
349
- */
350
- Serializer.prototype.serialize = function (data) {
351
- var result = this.serializeValue(data),
352
- self = this;
353
-
354
- if (angular.isObject(result)) {
355
- angular.forEach(this.customSerializedAttributes, function (value, key) {
356
- if (angular.isFunction(value)) {
357
- value = value.call(data, data);
337
+ return undefined;
338
+ };
339
+
340
+
341
+ /**
342
+ * Prepares the data for serialization to JSON.
343
+ *
344
+ * @param data The data to prepare
345
+ * @returns {*} A new object or array that is ready for JSON serialization
346
+ */
347
+ Serializer.prototype.serializeValue = function (data) {
348
+ var result = data,
349
+ self = this;
350
+
351
+ if (angular.isArray(data)) {
352
+ result = [];
353
+
354
+ angular.forEach(data, function (value) {
355
+ result.push(self.serializeValue(value));
356
+ });
357
+ } else if (angular.isObject(data)) {
358
+ if (angular.isDate(data)) {
359
+ return data;
358
360
  }
361
+ result = {};
362
+
363
+ angular.forEach(data, function (value, key) {
364
+ // if the value is a function then it can't be serialized to JSON so we'll just skip it
365
+ if (!angular.isFunction(value)) {
366
+ self.serializeAttribute(result, key, value);
367
+ }
368
+ });
369
+ }
359
370
 
360
- self.serializeAttribute(result, key, value);
361
- });
362
- }
371
+ return result;
372
+ };
373
+
374
+ /**
375
+ * Transforms an attribute and its value and stores it on the parent data object. The attribute will be
376
+ * renamed as needed and the value itself will be serialized as well.
377
+ *
378
+ * @param data The object that the attribute will be added to
379
+ * @param attribute The attribute to transform
380
+ * @param value The current value of the attribute
381
+ */
382
+ Serializer.prototype.serializeAttribute = function (data, attribute, value) {
383
+ var serializer = this.getAttributeSerializer(attribute),
384
+ serializedAttributeName = this.getSerializedAttributeName(attribute);
385
+
386
+ // undefined means the attribute should be excluded from serialization
387
+ if (serializedAttributeName === undefined) {
388
+ return;
389
+ }
363
390
 
364
- return result;
365
- };
366
-
367
- /**
368
- * Iterates over the data deserializing each entry on arrays and each key/value on objects.
369
- *
370
- * @param data The object to deserialize
371
- * @param Resource (optional) The resource type to deserialize the result into
372
- * @returns {*} A new object or an instance of Resource populated with deserialized data.
373
- */
374
- Serializer.prototype.deserializeValue = function (data, Resource) {
375
- var result = data,
376
- self = this;
377
-
378
- if (angular.isArray(data)) {
379
- result = [];
380
-
381
- angular.forEach(data, function (value) {
382
- result.push(self.deserializeValue(value, Resource));
383
- });
384
- } else if (angular.isObject(data)) {
385
- result = {};
391
+ data[serializedAttributeName] = serializer ? serializer.serialize(value) : this.serializeValue(value);
392
+ };
393
+
394
+ /**
395
+ * Serializes the data by applying various transformations such as:
396
+ * - Underscoring attribute names
397
+ * - attribute renaming
398
+ * - attribute exclusion
399
+ * - custom attribute addition
400
+ *
401
+ * @param data The data to prepare
402
+ * @returns {*} A new object or array that is ready for JSON serialization
403
+ */
404
+ Serializer.prototype.serialize = function (data) {
405
+ var result = this.serializeValue(data),
406
+ self = this;
407
+
408
+ if (angular.isObject(result)) {
409
+ angular.forEach(this.customSerializedAttributes, function (value, key) {
410
+ if (angular.isFunction(value)) {
411
+ value = value.call(data, data);
412
+ }
386
413
 
387
- if (Resource) {
388
- result = new Resource();
414
+ self.serializeAttribute(result, key, value);
415
+ });
389
416
  }
390
417
 
391
- angular.forEach(data, function (value, key) {
392
- self.deserializeAttribute(result, key, value);
393
- });
394
- }
418
+ return result;
419
+ };
420
+
421
+ /**
422
+ * Iterates over the data deserializing each entry on arrays and each key/value on objects.
423
+ *
424
+ * @param data The object to deserialize
425
+ * @param Resource (optional) The resource type to deserialize the result into
426
+ * @returns {*} A new object or an instance of Resource populated with deserialized data.
427
+ */
428
+ Serializer.prototype.deserializeValue = function (data, Resource) {
429
+ var result = data,
430
+ self = this;
431
+
432
+ if (angular.isArray(data)) {
433
+ result = [];
434
+
435
+ angular.forEach(data, function (value) {
436
+ result.push(self.deserializeValue(value, Resource));
437
+ });
438
+ } else if (angular.isObject(data)) {
439
+ result = {};
440
+
441
+ if (Resource) {
442
+ result = new Resource();
443
+ }
395
444
 
396
- return result;
397
- };
398
-
399
- /**
400
- * Transforms an attribute and its value and stores it on the parent data object. The attribute will be
401
- * renamed as needed and the value itself will be deserialized as well.
402
- *
403
- * @param data The object that the attribute will be added to
404
- * @param attribute The attribute to transform
405
- * @param value The current value of the attribute
406
- */
407
- Serializer.prototype.deserializeAttribute = function (data, attribute, value) {
408
- var serializer,
409
- NestedResource,
410
- attributeName = this.getDeserializedAttributeName(attribute);
411
-
412
- // undefined means the attribute should be excluded from serialization
413
- if (attributeName === undefined) {
414
- return;
415
- }
445
+ angular.forEach(data, function (value, key) {
446
+ self.deserializeAttribute(result, key, value);
447
+ });
448
+ }
416
449
 
417
- serializer = this.getAttributeSerializer(attributeName);
418
- NestedResource = this.getNestedResource(attributeName);
450
+ return result;
451
+ };
452
+
453
+ /**
454
+ * Transforms an attribute and its value and stores it on the parent data object. The attribute will be
455
+ * renamed as needed and the value itself will be deserialized as well.
456
+ *
457
+ * @param data The object that the attribute will be added to
458
+ * @param attribute The attribute to transform
459
+ * @param value The current value of the attribute
460
+ */
461
+ Serializer.prototype.deserializeAttribute = function (data, attribute, value) {
462
+ var serializer,
463
+ NestedResource,
464
+ attributeName = this.getDeserializedAttributeName(attribute);
465
+
466
+ // undefined means the attribute should be excluded from serialization
467
+ if (attributeName === undefined) {
468
+ return;
469
+ }
419
470
 
420
- // preserved attributes are assigned unmodified
421
- if (this.preservedAttributes[attributeName]) {
422
- data[attributeName] = value;
423
- } else {
424
- data[attributeName] = serializer ? serializer.deserialize(value, NestedResource) : this.deserializeValue(value, NestedResource);
425
- }
426
- };
427
-
428
- /**
429
- * Deserializes the data by applying various transformations such as:
430
- * - Camelizing attribute names
431
- * - attribute renaming
432
- * - attribute exclusion
433
- * - nested resource creation
434
- *
435
- * @param data The object to deserialize
436
- * @param Resource (optional) The resource type to deserialize the result into
437
- * @returns {*} A new object or an instance of Resource populated with deserialized data
438
- */
439
- Serializer.prototype.deserialize = function (data, Resource) {
440
- // just calls deserializeValue for now so we can more easily add on custom attribute logic for deserialize too
441
- return this.deserializeValue(data, Resource);
442
- };
443
-
444
- Serializer.prototype.pluralize = function (value) {
445
- if (this.options.pluralize) {
446
- return this.options.pluralize(value);
447
- }
448
- return value;
449
- };
471
+ serializer = this.getAttributeSerializer(attributeName);
472
+ NestedResource = this.getNestedResource(attributeName);
450
473
 
451
- Serializer.prototype.underscore = function (value) {
452
- if (this.options.underscore) {
453
- return this.options.underscore(value);
454
- }
455
- return value;
456
- };
474
+ // preserved attributes are assigned unmodified
475
+ if (this.preservedAttributes[attributeName]) {
476
+ data[attributeName] = value;
477
+ } else {
478
+ data[attributeName] = serializer ? serializer.deserialize(value, NestedResource) : this.deserializeValue(value, NestedResource);
479
+ }
480
+ };
481
+
482
+ /**
483
+ * Deserializes the data by applying various transformations such as:
484
+ * - Camelizing attribute names
485
+ * - attribute renaming
486
+ * - attribute exclusion
487
+ * - nested resource creation
488
+ *
489
+ * @param data The object to deserialize
490
+ * @param Resource (optional) The resource type to deserialize the result into
491
+ * @returns {*} A new object or an instance of Resource populated with deserialized data
492
+ */
493
+ Serializer.prototype.deserialize = function (data, Resource) {
494
+ // just calls deserializeValue for now so we can more easily add on custom attribute logic for deserialize too
495
+ return this.deserializeValue(data, Resource);
496
+ };
497
+
498
+ Serializer.prototype.pluralize = function (value) {
499
+ if (this.options.pluralize) {
500
+ return this.options.pluralize(value);
501
+ }
502
+ return value;
503
+ };
504
+
505
+ Serializer.prototype.underscore = function (value) {
506
+ if (this.options.underscore) {
507
+ return this.options.underscore(value);
508
+ }
509
+ return value;
510
+ };
457
511
 
458
- Serializer.prototype.camelize = function (value) {
459
- if (this.options.camelize) {
460
- return this.options.camelize(value);
512
+ Serializer.prototype.camelize = function (value) {
513
+ if (this.options.camelize) {
514
+ return this.options.camelize(value);
515
+ }
516
+ return value;
461
517
  }
462
- return value;
463
- }
464
518
 
465
- return Serializer;
466
- }
519
+ return Serializer;
520
+ }
467
521
 
468
- railsSerializer.defaultOptions = defaultOptions;
469
- return railsSerializer;
470
- }]);
522
+ railsSerializer.defaultOptions = defaultOptions;
523
+ return railsSerializer;
524
+ }];
525
+ });
471
526
  }());