FineLinePrototyping/angularjs-rails-resource — pull request #28 by tpodom (2013-05-19 23:53:31+00:00)
Title: NOT READY FOR MERGE: Using serializers for customizing JSON serialization

This is a start to a new way to perform serialization instead of the basic field renaming that is supported now. I haven't done anything on the deserialization side yet and haven't added any documentation. I did want to put it out here so people could take a look and provide feedback to help drive the direction of the work before it gets finalized.

All tests are passing currently and with the current design this change would not affect users that aren't customizing the transformers / interceptors.

Root wrapping has changed to a configuration option named enableRootWrapping on the resource. To turn off wrapping you would just pass in enableRootWrapping: false instead of mucking with transformers / interceptors.

With this change users can now pass in a serializer configuration option to railsResourceFactory to configure custom serialization logic. The serializer will be used for both directions with some serializer options only being applicable to one direction or the other.

# TODO
* Document changes
* Finalize available serialization customizations and syntax
* Implement deserialize

# Serializers
## Example
To exclude birthDate and books attributes from an Author resource you would specify a custom serializer and in the customization function specify which attributes you want excluded.
### Inline Serializer
````javascript
Author = railsResourceFactory({
url: '/authors',
name: 'author',
serializer: railsSerializer(function () {
this.exclude('birthDate', 'books');
this.nestedAttribute('books');
this.nestedResource('books', 'Book');
})
});
````
### Factoried Serializer
````javascript
angular.module('rails').factory('BookSerializer', function(railsSerializer) {
return railsSerializer(function () {
this.exclude('publicationDate', 'relatedBooks');
this.rename('ISBN', 'isbn');
this.nestedAttribute('chapters', 'notes');
this.serializeWith('chapters', 'ChapterSerializer');
this.add('numChapters', function (book) {
return book.chapters.length;
});
});
});

Book = railsResourceFactory({
url: '/books',
name: 'book',
serializer: 'BookSerializer'
});

````

## railsSerializer
````javascript
function railsSerializer(options, customizer)
````
Calling the railsSerializer function will return a new Serializer instance. That instance can be returned as part of an AngularJS factory, or it can be passed directly to the railsResourceFactory serializer config option.

## Configuration
The railsSerializer function takes a customizer function that is called on create within the context of the constructed Serializer. From within the customizer function you can call customization functions that affect what gets serialized and how or override the default options.

### Configuration Options
Serializers have the following available configuration options:
* underscore - (function) Allows users to supply their own custom underscore conversion logic.
* default: RailsInflector.underscore
* parameters
* attribute - The current name of the attribute
* returns - (string) The name as it should appear in the JSON
* camelize - (function) Allows users to supply their own custom underscore conversion logic.
* default: RailsInflector.camelize
* parameters
* attribute - The name as it appeared in the JSON
* returns - (string) The name as it should appear in the resource
* exclusionMatchers - (array) An list of rules that should be applied to determine whether or not an attribute should be excluded. For instance, $resource excludes all variables that start with $. The values in the array can be one of the following types:
* string - Defines a prefix that is used to test for exclusion
* RegExp - A custom regular expression that is tested against the attribute name
* function - A custom function that accepts a string argument and returns a boolean with true indicating exclusion.

### Customization API
The customizer function passed to the railsSerializer has available to it the following methods for altering the serialization of an object. None of these methods support nested attribute names (e.g. 'books.publicationDate'), in order to customize the serialization of the books objects you would need to specify a custom serializer for the books attribute.

* exclude (attributeName...) - Accepts a variable list of attribute names to exclude JSON serialization. To exclude values coming from the server, specify the underscored name as it appears in the JSON. For values being sent to the server specify camelized attribute name.

* rename (javascriptName, jsonName) - Specifies a custom name mapping for an attribute. On serializing to JSON the jsonName will be used. On deserialization, if jsonName is seen then it will be renamed as javascriptName in the resulting resource. Right now it is still passed to underscore so you could do 'publicationDate' -> 'releaseDate' and it will still underscore as release_date. However, that may be changed to prevent underscore from breaking some custom name that it doesn't handle properly.

* nestedAttribute (attributeName...) - This is a shortcut for rename that allows you to specify a variable number of attributes that should all be renamed to _attributes to work with the Rails nested_attributes feature. This does not perform any additional logic to accomodate specifying the _destroy property.

* resource (attributeName, resource, serializer) - Specifies an attribute that is a nested resource within the parent object. Nested resources do not imply nested attributes, if you want both you still have to specify call nestedAttribute as well. A nested resource serves two purposes. First, it defines the resource that should be used when constructing resources from the server. Second, it specifies how the nested object should be serialized. An optional third parameter serializer is available to override the serialization logic of the resource in case you need to serialize it differently in multiple contexts.

* add (attributeName, value) - Allows custom attribute creation as part of the serialization to JSON. This method may be renamed to allow for specifying different custom attributes during serialization to allow for custom attributes during deserialization as well. The parameter value can be defined as function that takes a parameter of the containing object and returns a value that should be included in the JSON.

## Serializer Methods
There are quite a few methods on Serializer, all of which can be overridden to customize functionality. I'm not documenting those methods here as they very likely could change before this is finalized.