angularjs-rails-resource 0.1.7 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +42 -0
- data/EXAMPLES.md +127 -0
- data/README.md +214 -222
- data/lib/angularjs-rails-resource/version.rb +1 -1
- data/test/unit/angularjs/rails/httpSettingsSpec.js +1 -1
- data/test/unit/angularjs/rails/interceptorsSpec.js +105 -0
- data/test/unit/angularjs/rails/nestedUrlsSpec.js +5 -47
- data/test/unit/angularjs/rails/resourceSerializerSpec.js +128 -0
- data/test/unit/angularjs/rails/resourceSpec.js +18 -48
- data/test/unit/angularjs/rails/rootWrappingSpec.js +1 -1
- data/test/unit/angularjs/rails/serializationSpec.js +333 -0
- data/test/unit/angularjs/rails/transformersSpec.js +96 -0
- data/test/unit/angularjs/rails/utils/urlBuilderSpec.js +40 -0
- data/vendor/assets/javascripts/angularjs/rails/resource/index.js +10 -0
- data/vendor/assets/javascripts/angularjs/rails/{resource.js → resource/resource.js} +87 -142
- data/vendor/assets/javascripts/angularjs/rails/resource/serialization.js +471 -0
- data/vendor/assets/javascripts/angularjs/rails/resource/utils/inflector.js +36 -0
- data/vendor/assets/javascripts/angularjs/rails/resource/utils/injector.js +38 -0
- data/vendor/assets/javascripts/angularjs/rails/resource/utils/url_builder.js +58 -0
- metadata +19 -4
- data/test/unit/angularjs/rails/fieldRenamingSpec.js +0 -91
data/CHANGELOG.md
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
<a name="0.2.0"></a>
|
2
|
+
# 0.2.0
|
3
|
+
## Breaking Changes
|
4
|
+
- Removed default transformers and interceptors
|
5
|
+
- railsFieldRenamingTransformer and railsFieldRenamingInterceptor have been removed completely and replaced by the serializers
|
6
|
+
- railsRootWrappingTransformer/Interceptor are no longer configured by the transformers/interceptors configuration option and is instead
|
7
|
+
configured by the <code>enableRootWrapping</code> option.
|
8
|
+
- Interceptors added using <code>beforeRequest</code> are run before any deserialization so the fields names have not been camelized.
|
9
|
+
|
10
|
+
## Bug Fixes
|
11
|
+
|
12
|
+
## Features
|
13
|
+
- Added serializers to replace old field renaming logic and to give users a lot more flexibility in customizing the (de)serialization process
|
14
|
+
- Added <code>enableRootWrapping</code> configuration option to be able to turn off root wrapping instead
|
15
|
+
- Added path option to <code>$url</code> methods to make it easier to construct a nested url.
|
16
|
+
|
17
|
+
<a name="0.1.7"></a>
|
18
|
+
# 0.1.7
|
19
|
+
## Bug Fixes
|
20
|
+
|
21
|
+
## Features
|
22
|
+
- New <code>save</code> instance method added to resources
|
23
|
+
|
24
|
+
<a name="0.1.6"></a>
|
25
|
+
# 0.1.6
|
26
|
+
## Bug Fixes
|
27
|
+
|
28
|
+
## Features
|
29
|
+
- Added beforeRequest and beforeResponse methods that wrap a given function as a transformer/interceptor to more easily add customizations
|
30
|
+
|
31
|
+
<a name="0.1.5"></a>
|
32
|
+
# 0.1.5
|
33
|
+
|
34
|
+
## Bug Fixes
|
35
|
+
|
36
|
+
## Features
|
37
|
+
- Added setUrl method to allow reconfiguring a resource's url after creation
|
38
|
+
- Added $url instance method to more easily reference the instance's URL
|
39
|
+
- Added instance and class methods for generic HTTP operations of $get, $post, $put, $delete
|
40
|
+
|
41
|
+
|
42
|
+
|
data/EXAMPLES.md
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
# Defining a nested resource
|
2
|
+
If a resource returns nested objects then those are normally treated as just basic JavaScript objects stored on the resource instance.
|
3
|
+
Using the serializers we can specify that a nested object is in fact a resouce and the object(s) will be constructed as a resource
|
4
|
+
instance during deserialization.
|
5
|
+
|
6
|
+
For example, suppose we have an Author that serializes all of the books that author has written. If we specify that the books
|
7
|
+
are a nested resource that allows us to more easily perform updates against those books without having to worry about creating
|
8
|
+
a resource instance for the book.
|
9
|
+
|
10
|
+
angular.module('book.services', ['rails']);
|
11
|
+
|
12
|
+
angular.module('book.services').factory('Book', ['railsResourceFactory', function (railsResourceFactory) {
|
13
|
+
return railsResourceFactory({url: '/books', name: 'book'});
|
14
|
+
}]);
|
15
|
+
|
16
|
+
angular.module('book.services').factory('Book', ['railsResourceFactory', function (railsResourceFactory) {
|
17
|
+
return railsResourceFactory({
|
18
|
+
url: '/authors',
|
19
|
+
name: 'author',
|
20
|
+
serializer: railsSerializer(function () {
|
21
|
+
this.nestedResource('books', 'Book');
|
22
|
+
});
|
23
|
+
});
|
24
|
+
}]);
|
25
|
+
|
26
|
+
angular.module('book.controllers').controller('AuthorCtrl', ['$scope', 'Author', function ($scope, Author) {
|
27
|
+
$scope.author = Author.get(123);
|
28
|
+
|
29
|
+
// allow the view to trigger an update to a book from $scope.author.books
|
30
|
+
$scope.updateBook = function (book) {
|
31
|
+
book.update();
|
32
|
+
}
|
33
|
+
}]);
|
34
|
+
|
35
|
+
|
36
|
+
# Adding custom methods to a resource
|
37
|
+
You can add additional "class" or "instance" methods by modifying the resource returned from the factory call.
|
38
|
+
|
39
|
+
## Custom class-level find
|
40
|
+
For instance, if you wanted to add a method that would search for Books by the title without having to construct the query params
|
41
|
+
each time you could add a new <code>findByTitle</code> class function.
|
42
|
+
|
43
|
+
angular.module('book.services', ['rails']);
|
44
|
+
angular.module('book.services').factory('Book', ['railsResourceFactory', function (railsResourceFactory) {
|
45
|
+
var resource = railsResourceFactory({url: '/books', name: 'book'});
|
46
|
+
resource.findByTitle = function (title) {
|
47
|
+
return resource.query({title: title});
|
48
|
+
};
|
49
|
+
return resource;
|
50
|
+
}]);
|
51
|
+
|
52
|
+
angular.module('book.controllers').controller('BookShelfCtrl', ['$scope', 'Book', function ($scope, Book) {
|
53
|
+
$scope.searching = true;
|
54
|
+
// Find all books matching the title
|
55
|
+
$scope.books = Book.findByTitle({title: title});
|
56
|
+
$scope.books.then(function(results) {
|
57
|
+
$scope.searching = false;
|
58
|
+
}, function (error) {
|
59
|
+
$scope.searching = false;
|
60
|
+
});
|
61
|
+
}]);
|
62
|
+
|
63
|
+
## Get related object
|
64
|
+
You can also add additional methods on the object prototype chain so all instances of the resource have that function available.
|
65
|
+
The following example exposes a <code>getAuthor</code> instance method that would be accessible on all Book instances.
|
66
|
+
|
67
|
+
angular.module('book.services', ['rails']);
|
68
|
+
angular.module('book.services').factory('Author', ['railsResourceFactory', function (railsResourceFactory) {
|
69
|
+
return railsResourceFactory({url: '/authors', name: 'author'});
|
70
|
+
}]);
|
71
|
+
angular.module('book.services').factory('Book', ['railsResourceFactory', 'Author', function (railsResourceFactory, Author) {
|
72
|
+
var resource = railsResourceFactory({url: '/books', name: 'book'});
|
73
|
+
resource.prototype.getAuthor = function () {
|
74
|
+
return Author.get(this.authorId);
|
75
|
+
};
|
76
|
+
}]);
|
77
|
+
angular.module('book.controllers').controller('BookShelfCtrl', ['$scope', 'Book', function ($scope, Book) {
|
78
|
+
$scope.getAuthorDetails = function (book) {
|
79
|
+
$scope.author = book.getAuthor();
|
80
|
+
};
|
81
|
+
}]);
|
82
|
+
|
83
|
+
## Nested URL
|
84
|
+
Or say you instead had a nested "references" service call that returned a list of referenced books for a given book instance. In that case you can add your own addition method that calls $http.get and then
|
85
|
+
passes the resulting promise to the processResponse method which will perform the same transformations and handling that the get or query would use.
|
86
|
+
|
87
|
+
angular.module('book.services', ['rails']);
|
88
|
+
angular.module('book.services').factory('Book', ['railsResourceFactory', '$http', function (railsResourceFactory, $http) {
|
89
|
+
var resource = railsResourceFactory({url: '/books', name: 'book'});
|
90
|
+
resource.prototype.getReferences = function () {
|
91
|
+
var self = this;
|
92
|
+
return resource.$get(self.$url('references'))).then(function (references) {
|
93
|
+
self.references = references;
|
94
|
+
return self.references;
|
95
|
+
});
|
96
|
+
};
|
97
|
+
}]);
|
98
|
+
|
99
|
+
# Specifying Transformer
|
100
|
+
Transformers can be by specifying an array of transformers in the configuration options passed to railsResourceFactory.
|
101
|
+
However, a cleaner eway to write it is to use the <code>beforeRequest</code> which can take a new anonymous function or
|
102
|
+
a function returned by a factory if you want to share a transformer across multiple resources.
|
103
|
+
|
104
|
+
Both of these examples can be accomplished using the serializers now.
|
105
|
+
|
106
|
+
angular.module('test').factory('excludePrivateKeysTransformer', function () {
|
107
|
+
return function (data) {
|
108
|
+
angular.forEach(data, function (value, key) {
|
109
|
+
if (key[0] === '_') {
|
110
|
+
delete data[key];
|
111
|
+
}
|
112
|
+
});
|
113
|
+
});
|
114
|
+
});
|
115
|
+
|
116
|
+
angular.module('test').factory('Book', function (railsResourceFactory, excludePrivateKeysTransformer) {
|
117
|
+
var Book = railsResourceFactory({url: '/books', name: 'book'});
|
118
|
+
Book.beforeRequest(excludePrivateKeysTransformer);
|
119
|
+
Book.beforeRequest(function (data) {
|
120
|
+
data['release_date'] = data['publicationDate'];
|
121
|
+
delete data['publicationDate'];
|
122
|
+
});
|
123
|
+
|
124
|
+
return Book;
|
125
|
+
});
|
126
|
+
|
127
|
+
|
data/README.md
CHANGED
@@ -19,11 +19,14 @@ The resource object created by this factory simplifies access to those models by
|
|
19
19
|
|
20
20
|
This module is being used for applications we are writing and we expect that over time that we will be adding additional functionality but we welcome contributions and suggestions.
|
21
21
|
|
22
|
+
## Changes
|
23
|
+
Make sure to check the [CHANGELOG](CHANGELOG.md) for any breaking changes between releases.
|
24
|
+
|
22
25
|
## Installation
|
23
26
|
|
24
27
|
Add this line to your application's Gemfile:
|
25
28
|
|
26
|
-
gem 'angularjs-rails-resource'
|
29
|
+
gem 'angularjs-rails-resource', '~> 0.2.0'
|
27
30
|
|
28
31
|
Include the javascript somewhere in your asset pipeline:
|
29
32
|
|
@@ -38,23 +41,92 @@ Since this is an [AngularJS](http://angularjs.org) module it of course depends o
|
|
38
41
|
* [$injector](http://docs.angularjs.org/api/AUTO.$injector)
|
39
42
|
* [$interpolate](http://docs.angularjs.org/api/ng.$interpolate)
|
40
43
|
|
44
|
+
## Usage
|
45
|
+
There are a lot of different ways that you can use the resources and we try not to force you into any specific pattern
|
46
|
+
|
47
|
+
There are more examples available in [EXAMPLES.md](EXAMPLES.md). We also published a complete working example (including the rails side)
|
48
|
+
at [Employee Training Tracker](https://github.com/FineLinePrototyping/employee-training-tracker) application.
|
49
|
+
|
50
|
+
|
51
|
+
### Basic Example
|
52
|
+
In order to create a Book resource, we would first define the factory within a module.
|
53
|
+
|
54
|
+
angular.module('book.services', ['rails']);
|
55
|
+
angular.module('book.services').factory('Book', ['railsResourceFactory', function (railsResourceFactory) {
|
56
|
+
return railsResourceFactory({url: '/books', name: 'book'});
|
57
|
+
}]);
|
58
|
+
|
59
|
+
We would then inject that service into a controller:
|
60
|
+
|
61
|
+
angular.module('book.controllers').controller('BookShelfCtrl', ['$scope', 'Book', function ($scope, Book) {
|
62
|
+
$scope.searching = true;
|
63
|
+
// Find all books matching the title
|
64
|
+
$scope.books = Book.query({title: title});
|
65
|
+
$scope.books.then(function(results) {
|
66
|
+
$scope.searching = false;
|
67
|
+
}, function (error) {
|
68
|
+
$scope.searching = false;
|
69
|
+
});
|
70
|
+
|
71
|
+
// Find a single book and update it
|
72
|
+
Book.get(1234).then(function (book) {
|
73
|
+
book.lastViewed = new Date();
|
74
|
+
book.update();
|
75
|
+
});
|
76
|
+
|
77
|
+
// Create a book and save it
|
78
|
+
new Book({title: 'Gardens of the Moon', author: 'Steven Erikson', isbn: '0-553-81957-7'}).create();
|
79
|
+
}]);
|
80
|
+
|
81
|
+
### Serializer
|
82
|
+
When defining a resource, you can pass a custom [serializer](#serializers) using the <code>serializer</code> configuration option.
|
83
|
+
|
84
|
+
Author = railsResourceFactory({
|
85
|
+
url: '/authors',
|
86
|
+
name: 'author',
|
87
|
+
serializer: railsSerializer(function () {
|
88
|
+
this.exclude('birthDate', 'books');
|
89
|
+
this.nestedAttribute('books');
|
90
|
+
this.nestedResource('books', 'Book');
|
91
|
+
})
|
92
|
+
});
|
93
|
+
|
94
|
+
You can also specify a serializer as a factory and inject it as a dependency.
|
95
|
+
|
96
|
+
angular.module('rails').factory('BookSerializer', function(railsSerializer) {
|
97
|
+
return railsSerializer(function () {
|
98
|
+
this.exclude('publicationDate', 'relatedBooks');
|
99
|
+
this.rename('ISBN', 'isbn');
|
100
|
+
this.nestedAttribute('chapters', 'notes');
|
101
|
+
this.serializeWith('chapters', 'ChapterSerializer');
|
102
|
+
this.add('numChapters', function (book) {
|
103
|
+
return book.chapters.length;
|
104
|
+
});
|
105
|
+
});
|
106
|
+
});
|
107
|
+
|
108
|
+
Book = railsResourceFactory({
|
109
|
+
url: '/books',
|
110
|
+
name: 'book',
|
111
|
+
serializer: 'BookSerializer'
|
112
|
+
});
|
113
|
+
|
114
|
+
|
41
115
|
## Resource Creation
|
42
|
-
Creating a resource using this factory is similar to using $resource, you just call
|
116
|
+
Creating a resource using this factory is similar to using $resource, you just call <code>railsResourceFactory</code> with the config options and it returns a new resource function.
|
43
117
|
The resource function serves two purposes. First is that you can use (or define new) "class" methods directly accessible such as query and get to retrieve
|
44
118
|
instances from the backend rails service. The second is that it allows you to use it as a constructor to create new instances of that resource giving you access
|
45
119
|
to create, update, and delete instance methods (or any others you add).
|
46
120
|
|
47
|
-
The typical use case is to define the resource as an AngularJS factory within a module and then inject that into a controller or directive.
|
48
|
-
See [Examples](#examples) below for more information on creating and injecting the resource.
|
49
|
-
|
50
121
|
|
51
122
|
### Config Options
|
52
123
|
The following options are available for the config object passed to the factory function.
|
53
124
|
|
54
125
|
* **url** - This is the url of the service. See [Resource URLs](#resource-urls) below for more information.
|
126
|
+
* **enableRootWrapping** - (Default: true) Turns on/off root wrapping on JSON (de)serialization.
|
55
127
|
* **name** - This is the name used for root wrapping when dealing with singular instances.
|
56
|
-
* **pluralName** *(optional)* - If specified this name will be used for unwrapping
|
57
|
-
|
128
|
+
* **pluralName** *(optional)* - If specified this name will be used for unwrapping array results. If not specified then the serializer's [pluralize](#serializers) method is used to calculate
|
129
|
+
the plural name from the singular name.
|
58
130
|
* **httpConfig** *(optional)* - By default we will add the following headers to ensure that the request is processed as JSON by Rails. You can specify additional http config options or override any of the defaults by setting this property. See the [AngularJS $http API](http://docs.angularjs.org/api/ng.$http) for more information.
|
59
131
|
* **headers**
|
60
132
|
* **Accept** - application/json
|
@@ -81,35 +153,6 @@ The URL can be specified as one of three ways:
|
|
81
153
|
new Item({store: 123}).create() would generate a POST to /stores/123/items
|
82
154
|
new Item({id: 1, storeId: 123}).update() would generate a PUT to /stores/123/items/1
|
83
155
|
|
84
|
-
|
85
|
-
## Transformers / Interceptors
|
86
|
-
The transformers and interceptors can be specified using an array containing transformer/interceptor functions or strings
|
87
|
-
that can be resolved using Angular's DI.
|
88
|
-
|
89
|
-
The root wrapping and snake case to camel case conversions are implemented as transformers and interceptors. So if you override
|
90
|
-
the default transformers and interceptors you will have to include those in the array as well (assuming you want that functionality).
|
91
|
-
|
92
|
-
That also means that if you don't want root wrapping and key conversions then you can just pass an emptry array for each
|
93
|
-
and no processing will be done on the data.
|
94
|
-
|
95
|
-
### Transformers
|
96
|
-
Transformer functions are called to transform the data before we send it to $http for POST/PUT.
|
97
|
-
|
98
|
-
The transformer functions will be called with the following signature
|
99
|
-
|
100
|
-
function (data, resource)
|
101
|
-
|
102
|
-
The return value of the function must be the transformed data.
|
103
|
-
|
104
|
-
### Interceptors
|
105
|
-
Interceptor functions utilize [$q promises](http://docs.angularjs.org/api/ng.$q) to process the data returned from the server.
|
106
|
-
|
107
|
-
The interceptor is called with the promise returned from $http and is expected to return a promise for chaining.
|
108
|
-
|
109
|
-
The promise passed to each interceptor contains a reference to the resource to expose the configured options of the resource.
|
110
|
-
|
111
|
-
Each interceptor promise is expected to return the response or a $q.reject. See [Promises](#promises) below for more information about the promise data.
|
112
|
-
|
113
156
|
## Promises
|
114
157
|
[$http documentation](http://docs.angularjs.org/api/ng.$http) describes the promise data very well so I highly recommend reading that.
|
115
158
|
|
@@ -119,225 +162,174 @@ that if response.data is reassigned that there's still a pointer to the original
|
|
119
162
|
|
120
163
|
|
121
164
|
## Resource Methods
|
122
|
-
Resources created using
|
123
|
-
|
124
|
-
###
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
Transforms the given data and submits it using a POST/PUT/PATCH to the given URL and returns a promise that will be resolved with a new Resource instance (or instances in the case of an array response).
|
173
|
-
|
174
|
-
####Parameters
|
175
|
-
* **url** - The url to POST/PUT/PATCH
|
176
|
-
* **data** - The data to transform and submit to the server
|
177
|
-
|
178
|
-
### $delete
|
179
|
-
***
|
180
|
-
|
181
|
-
Executes a DELETE against the given URL and returns a promise that will be resolved with a new Resource instance (if the server returns a body).
|
182
|
-
|
183
|
-
####Parameters
|
184
|
-
* **url** - The url to POST/PUT/PATCH
|
185
|
-
|
186
|
-
## Resource Instance Methods
|
165
|
+
Resources created using <code>railsResourceFactory</code> have the following class and instance methods available.
|
166
|
+
|
167
|
+
### Class Methods
|
168
|
+
* Constructor(data) - The Resource object can act as a constructor function for use with the JavaScript <code>new</code> keyword.
|
169
|
+
* **data** {object} (optional) - Optional data to set on the new instance
|
170
|
+
|
171
|
+
* $url(context, path) - Returns the resource URL using the given context with the optional path appended if provided.
|
172
|
+
* **context** {*} - The context to use when building the url. See [Resource URLs](#resource-urls) above for more information.
|
173
|
+
* **path** {string} (optional) - A path to append to the resource's URL.
|
174
|
+
* **returns** {string} - The resource URL
|
175
|
+
|
176
|
+
* query(queryParams, context) - Executes a GET request against the resource's base url (e.g. /books).
|
177
|
+
* **query params** {object} (optional) - An map of strings or objects that are passed to $http to be turned into query parameters
|
178
|
+
* **context** {*} (optional) - A context object that is used during url evaluation to resolve expression variables
|
179
|
+
* **returns** {promise} - A promise that will be resolved with an array of new Resource instances
|
180
|
+
|
181
|
+
* get(context) - Executs a GET request against the resource's url (e.g. /books/1234).
|
182
|
+
* **context** {*} - A context object that is used during url evaluation to resolve expression variables. If you are using a basic url this can be an id number to append to the url.
|
183
|
+
* **returns** {promise} A promise that will be resolved with a new instance of the Resource
|
184
|
+
|
185
|
+
* $get(customUrl, queryParams) - Executes a GET request against the given URL.
|
186
|
+
* **customUrl** {string} - The url to GET
|
187
|
+
* **queryParams** {object} (optional) - The set of query parameters to include in the GET request
|
188
|
+
* **returns** {promise} A promise that will be resolved with a new Resource instance (or instances in the case of an array response).
|
189
|
+
|
190
|
+
* $post(customUrl, data), $put(customUrl, data), $patch(customUrl, data) - Serializes the data parameter using the Resource's normal serialization process and submits the result as a POST / PUT / PATCH to the given URL.
|
191
|
+
* **customUrl** {string} - The url to POST / PUT / PATCH to
|
192
|
+
* **data** {object} - The data to serialize and POST / PUT / PATCH
|
193
|
+
* **returns** {promise} A promise that will be resolved with a new Resource instance (or instances in the case of an array response).
|
194
|
+
|
195
|
+
* $delete(customUrl) - Executes a DELETE to a custom URL. The main difference between this and $http.delete is that a server response that contains a body will be deserialized using the normal Resource deserialization process.
|
196
|
+
* **customUrl** {string} - The url to DELETE to
|
197
|
+
* **returns** {promise} A promise that will be resolved with a new Resource instance (or instances in the case of an array response) if the server includes a response body.
|
198
|
+
|
199
|
+
* beforeRequest(fn(data, resource)) - See [Transformers](#transformers) for more information. The function is called prior to the serialization process so the data
|
200
|
+
passed to the function is still a Resource instance as long as another transformation function has not returned a new object to serialize.
|
201
|
+
* fn(data, resource) {function} - The function to add as a transformer.
|
202
|
+
* **data** {object} - The data being serialized
|
203
|
+
* **resource** {Resource class} - The Resource class that is calling the function
|
204
|
+
* **returns** {object | undefined} - If the function returns a new object that object will instead be used for serialization.
|
205
|
+
|
206
|
+
* beforeResponse(fn(data, resource)) - See [Interceptors](#interceptors) for more information. The function is called prior to deserialization so the data represents the
|
207
|
+
raw data returned from the server. Since the data has not been deserialized into a Resource instance yet, none of the [Resource instance methods](#instance-methods) are available.
|
208
|
+
* fn(data, resource) {function} - The function to add as an interceptor
|
209
|
+
* **data** {object} - The data received from the server
|
210
|
+
* **resource** {Resource class} - The Resource class that is calling the function
|
211
|
+
* **returns** {object | undefined} - If the function returns a new object that object will instead be used for serialization.
|
212
|
+
|
213
|
+
### Instance Methods
|
187
214
|
The instance methods can be used on any instance (created manually or returned in a promise response) of a resource.
|
188
215
|
All of the instance methods will update the instance in-place on response and will resolve the promise with the current instance.
|
189
216
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
Returns the url for the instance.
|
217
|
+
* $url(path) - Returns this Resource instance's URL with the optional path appended if provided.
|
218
|
+
* **path** {string} (optional) - A path to append to the resource's URL.
|
194
219
|
|
195
|
-
|
220
|
+
* create() - Submits the resource instance to the resource's base URL (e.g. /books) using a POST
|
221
|
+
* **returns** {promise} - A promise that will be resolved with the instance itself
|
196
222
|
|
197
|
-
|
223
|
+
* update() - Submits the resource instance to the resource's URL (e.g. /books/1234) using a PUT
|
224
|
+
* **returns** {promise} - A promise that will be resolved with the instance itself
|
198
225
|
|
199
|
-
|
200
|
-
***
|
226
|
+
* save() - Calls <code>create</code> if <code>isNew</code> returns true, otherwise it calls <code>update</code>.
|
201
227
|
|
202
|
-
|
228
|
+
* remove(), delete() - Executes an HTTP DELETE against the resource's URL (e.g. /books/1234)
|
229
|
+
* **returns** {promise} - A promise that will be resolved with the instance itself
|
203
230
|
|
204
|
-
|
205
|
-
|
231
|
+
* $post(customUrl), $put(customUrl), $patch(customUrl) - Serializes and submits the instance using an HTTP POST/PUT/PATCH to the given URL.
|
232
|
+
* **customUrl** {string} - The url to POST / PUT / PATCH to
|
233
|
+
* **returns** {promise} - A promise that will be resolved with the instance itself
|
206
234
|
|
207
|
-
|
208
|
-
|
235
|
+
* $delete(customUrl) - Executes a DELETE to a custom URL. The main difference between this and $http.delete is that a server response that contains a body will be deserialized using the normal Resource deserialization process.
|
236
|
+
* **customUrl** {string} - The url to DELETE to
|
237
|
+
* **returns** {promise} - A promise that will be resolved with the instance itself
|
209
238
|
|
210
|
-
Transforms and submits the instance using a POST to the resource base URL.
|
211
239
|
|
212
|
-
|
240
|
+
## Serializers
|
241
|
+
Out of the box, resources serialize all available keys and transform key names between camel case and underscores to match Ruby conventions.
|
242
|
+
However, that basic serialization often isn't ideal in every situation. With the serializers users can define customizations
|
243
|
+
that dictate how serialization and deserialization is performed. Users can: rename attributes, specify extra attributes, exclude attributes
|
244
|
+
with the ability to exclude all attributes by default and only serialize ones explicitly allowed, specify other serializers to use
|
245
|
+
for an attribute and even specify that an attribute is a nested resource.
|
213
246
|
|
214
|
-
|
247
|
+
### railsSerializer
|
248
|
+
* railsSerializer(options, customizer) - Builds a Serializer constructor function using the configuration options specified.
|
249
|
+
* **options** {object} (optional) - Configuration options to alter the default operation of the serializers. This parameter can be excluded and the
|
250
|
+
customizer function specified as the first argument instead.
|
251
|
+
* **customizer** {function} (optional) - A function that will be called to customize the serialization logic.
|
252
|
+
* **returns** {Serializer} - A Serializer constructor function
|
215
253
|
|
254
|
+
### Configuration
|
255
|
+
The <code>railsSerializer</code> 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.
|
256
|
+
In addition, <code>railsSerializer</code> exposes a field <code>defaultOptions</code> that allows you to globally override the defaults for the configuration options.
|
216
257
|
|
217
|
-
|
218
|
-
|
258
|
+
#### Configuration Options
|
259
|
+
Serializers have the following available configuration options:
|
260
|
+
* underscore - (function) Allows users to supply their own custom underscore conversion logic.
|
261
|
+
* **default**: RailsInflector.underscore
|
262
|
+
* parameters
|
263
|
+
* **attribute** {string} - The current name of the attribute
|
264
|
+
* **returns** {string} - The name as it should appear in the JSON
|
265
|
+
* camelize - (function) Allows users to supply their own custom camelization logic.
|
266
|
+
* **default**: RailsInflector.camelize
|
267
|
+
* parameters
|
268
|
+
* **attribute** {string} - The name as it appeared in the JSON
|
269
|
+
* **returns** {string} - The name as it should appear in the resource
|
270
|
+
* pluralize - (function) Allows users to supply their own custom pluralization logic.
|
271
|
+
* default: RailsInflector.pluralize
|
272
|
+
* parameters
|
273
|
+
* **attribute** {string} - The name as it appeared in the JSON
|
274
|
+
* **returns** {string} - The name as it should appear in the resource
|
275
|
+
* excludeByDefault {boolean} - Specifies whether or not JSON serialization should exclude all attributes from serialization by default.
|
276
|
+
* default: false
|
277
|
+
* 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:
|
278
|
+
* string - Defines a prefix that is used to test for exclusion
|
279
|
+
* RegExp - A custom regular expression that is tested against the attribute name
|
280
|
+
* function - A custom function that accepts a string argument and returns a boolean with true indicating exclusion.
|
219
281
|
|
220
|
-
|
282
|
+
#### Customization API
|
283
|
+
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. <code>'books.publicationDate'</code>), in order to customize the serialization of the <code>books</code> objects you would need to specify a custom serializer for the <code>books</code> attribute.
|
221
284
|
|
222
|
-
|
285
|
+
* exclude (attributeName...) - Accepts a variable list of attribute names to exclude from JSON serialization. This has no impact on what is deserialized from the server.
|
223
286
|
|
224
|
-
|
287
|
+
* only (attributeName...) - Accepts a variable list of attribute names that should be included in JSON serialization. This has no impact on what is deserialized from the server. Using this method will by default exclude all other attributes and only the ones explicitly included using <code>only</code> will be serialized.
|
225
288
|
|
289
|
+
* rename (javascriptName, jsonName) - Specifies a custom name mapping for an attribute. On serializing to JSON the <code>jsonName</code> will be used. On deserialization, if <code>jsonName</code> 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.
|
226
290
|
|
227
|
-
|
228
|
-
***
|
291
|
+
* nestedAttribute (attributeName...) - This is a shortcut for rename that allows you to specify a variable number of attributes that should all be renamed to <code><name>_attributes</code> to work with the Rails nested_attributes feature. This does not perform any additional logic to accomodate specifying the <code>_destroy</code> property.
|
229
292
|
|
230
|
-
|
293
|
+
* 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 <code>nestedAttribute</code> 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 <code>serializer</code> is available to override the serialization logic of the resource in case you need to serialize it differently in multiple contexts.
|
231
294
|
|
232
|
-
|
295
|
+
* 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 <code>value</code> can be defined as function that takes a parameter of the containing object and returns a value that should be included in the JSON.
|
233
296
|
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
## Example
|
238
|
-
For a complete working example (including the rails side), check out the [Employee Training Tracker](https://github.com/FineLinePrototyping/employee-training-tracker) application
|
239
|
-
we open sourced based on an interface we created for use internally that uses this module as well as many others.
|
240
|
-
|
241
|
-
### Define Resource
|
242
|
-
In order to create a Book resource, we would first define the factory within a module.
|
243
|
-
|
244
|
-
angular.module('book.services', ['rails']);
|
245
|
-
angular.module('book.services').factory('Book', ['railsResourceFactory', function (railsResourceFactory) {
|
246
|
-
return railsResourceFactory({url: '/books', name: 'book'});
|
247
|
-
}]);
|
248
|
-
|
249
|
-
We would then inject that service into a controller:
|
250
|
-
|
251
|
-
angular.module('book.controllers').controller('BookShelfCtrl', ['$scope', 'Book', function ($scope, Book) {
|
252
|
-
// See following examples for using Book within your controller
|
253
|
-
}]);
|
297
|
+
* serializeWith (attributeName, serializer) - Specifies a custom serializer that should be used for the attribute. The serializer can be specified either as a <code>string</code> reference to a registered service or as a Serializer constructor returned from <code>railsSerializer</code>
|
254
298
|
|
255
|
-
|
299
|
+
### Serializer Methods
|
300
|
+
The serializers are defined using mostly instance prototype methods. For information on those methods please see the inline documentation. There are however a couple of class methods that
|
301
|
+
are also defined to expose underscore, camelize, and pluralize. Those functions are set to the value specified by the configuration options sent to the serializer.
|
256
302
|
|
257
|
-
#### Extending
|
258
|
-
You can add additional "class" or "instance" methods by modifying the resource returned from the factory call. For instance,
|
259
|
-
if you wanted to add a "class" method named "findByTitle" to the Book resource you would modify the service setup as follows:
|
260
303
|
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
return resource.query({title: title});
|
266
|
-
};
|
267
|
-
return resource;
|
268
|
-
}]);
|
269
|
-
|
270
|
-
If you wanted to add an "instance" method to retrieve a related object:
|
271
|
-
|
272
|
-
angular.module('book.services', ['rails']);
|
273
|
-
angular.module('book.services').factory('Author', ['railsResourceFactory', function (railsResourceFactory) {
|
274
|
-
return railsResourceFactory({url: '/authors', name: 'author'});
|
275
|
-
}]);
|
276
|
-
angular.module('book.services').factory('Book', ['railsResourceFactory', 'Author', function (railsResourceFactory, Author) {
|
277
|
-
var resource = railsResourceFactory({url: '/books', name: 'book'});
|
278
|
-
resource.prototype.getAuthor = function () {
|
279
|
-
return Author.get(this.authorId);
|
280
|
-
};
|
281
|
-
}]);
|
282
|
-
|
283
|
-
Or say you instead had a nested "references" service call that returned a list of referenced books for a given book instance. In that case you can add your own addition method that calls $http.get and then
|
284
|
-
passes the resulting promise to the processResponse method which will perform the same transformations and handling that the get or query would use.
|
285
|
-
|
286
|
-
angular.module('book.services', ['rails']);
|
287
|
-
angular.module('book.services').factory('Book', ['railsResourceFactory', '$http', function (railsResourceFactory, $http) {
|
288
|
-
var resource = railsResourceFactory({url: '/books', name: 'book'});
|
289
|
-
resource.prototype.getReferences = function () {
|
290
|
-
var self = this;
|
291
|
-
return resource.processResponse($http.get(resource.resourceUrl(this.id) + '/references')).then(function (references) {
|
292
|
-
self.references = references;
|
293
|
-
return self.references;
|
294
|
-
});
|
295
|
-
};
|
296
|
-
}]);
|
297
|
-
|
298
|
-
### Query Books
|
299
|
-
To query for a list of books with the title "The Hobbit" you would use the query method:
|
300
|
-
|
301
|
-
var books = Book.query({title: 'The Hobbit'});
|
302
|
-
|
303
|
-
We now have a promise in the books variable which we could then use within a template if we just wanted to do an ng-repeat over the books. A lot of times though you'll probably want to show some indicator
|
304
|
-
to your user that the search is executing so you'd want to use a then handler:
|
305
|
-
|
306
|
-
$scope.searching = true;
|
307
|
-
var books = Book.query({title: 'The Hobbit'});
|
308
|
-
books.then(function(results) {
|
309
|
-
$scope.searching = false;
|
310
|
-
}, function (error) {
|
311
|
-
$scope.searching = false;
|
312
|
-
// display error
|
313
|
-
});
|
304
|
+
## Transformers / Interceptors
|
305
|
+
The transformers and interceptors can be specified using an array containing transformer/interceptor functions or strings
|
306
|
+
that can be resolved using Angular's DI. The transformers / interceptors concept was prior to the [serializers](#serializers) but
|
307
|
+
we kept the API available because there may be use cases that can be accomplished with these but not the serializers.
|
314
308
|
|
309
|
+
### Transformers
|
310
|
+
Transformer functions are called to transform the data before we send it to $http for POST/PUT.
|
315
311
|
|
316
|
-
|
317
|
-
|
312
|
+
A transformer function is called with two parameters:
|
313
|
+
* data - The data that is being sent to the server
|
314
|
+
* resource - The resource class that is calling the transformer
|
318
315
|
|
319
|
-
|
316
|
+
A transformer function must return the data. This is to allow transformers to return entirely new objects in place of the current data (such as root wrapping).
|
320
317
|
|
321
|
-
|
322
|
-
|
323
|
-
|
318
|
+
The resource also exposes a class method <code>beforeRequest(fn)</code> that accepts a function to execute and automatically wraps it as a transformer and appends it
|
319
|
+
to the list of transformers for the resource class. The function passed to <code>beforeRequest</code> is called with the same two parameters. One difference
|
320
|
+
is that the functions are not required to return the data, though they still can if they need to return a new object. See [example](EXAMPLES.md#specifying-transformer).
|
324
321
|
|
325
|
-
###
|
326
|
-
|
327
|
-
book.create().then(function (result) {
|
328
|
-
// creation was successful
|
329
|
-
});
|
322
|
+
### Interceptors
|
323
|
+
Interceptor functions utilize [$q promises](http://docs.angularjs.org/api/ng.$q) to process the data returned from the server.
|
330
324
|
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
book.update();
|
335
|
-
});
|
325
|
+
The interceptor is called with the promise returned from $http and is expected to return a promise for chaining. The promise passed to each
|
326
|
+
interceptor contains a reference to the resource to expose the configured options of the resource. Each interceptor promise
|
327
|
+
is expected to return the response or a $q.reject. See [Promises](#promises) for more information about the promise data.
|
336
328
|
|
337
|
-
|
329
|
+
The resource also exposes a class method <code>beforeResponse(fn)</code> that accepts a function to execute and automatically wraps it as an interceptor and appends it
|
330
|
+
to the list of interceptors for the resource class. Functions added with beforeResponse don't need to know anything about promises since they are automatically wrapped
|
331
|
+
as an interceptor.
|
338
332
|
|
339
|
-
var book = new Book({id: $scope.bookId, author: $scope.authorName, title: $scope.bookTitle});
|
340
|
-
book.update();
|
341
333
|
|
342
334
|
## Tests
|
343
335
|
The tests are written using [Jasmine](http://pivotal.github.com/jasmine/) and are run using [Karma](https://github.com/karma-runner/karma).
|