angularjs-rails-resource 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
  }());