angularjs-rails-resource 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +1 -1
- data/README.md +115 -30
- data/lib/angularjs-rails-resource/version.rb +1 -1
- data/test/unit/angularjs/rails/resourceSpec.js +378 -38
- data/vendor/assets/javascripts/angularjs/rails/resource.js +69 -18
- metadata +2 -2
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -6,8 +6,17 @@ When starting out with AngularJS and Rails we initially were using $resource but
|
|
6
6
|
|
7
7
|
1. $resource didn't return promises
|
8
8
|
2. Rails prefers JSON be root wrapped
|
9
|
-
3. Our JSON contained snake case keys coming from our database but we didn't want to mix snake case and camel case in our UI
|
9
|
+
3. Our JSON contained snake case (underscored) keys coming from our database but we didn't want to mix snake case and camel case in our UI
|
10
10
|
|
11
|
+
In case you are unfamiliar, the intent of the resource is to behave a bit like a remote model object. One of the nice things about AngularJS
|
12
|
+
is that it does not require you to create specific models for all of your data which gives you a lot of freedom for treating model data as basic
|
13
|
+
javascript objects. However, on the Rails side when exposing models to a javascript application you are likely going to follow the same pattern for multiple
|
14
|
+
models where you will have a controller that has your basic index (query), get, create, update, delete functionality.
|
15
|
+
|
16
|
+
The resource object created by this factory simplifies access to those models by exposing a mix of "class" methods (query, get) and
|
17
|
+
"instance" methods (create, update, delete/remove).
|
18
|
+
|
19
|
+
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.
|
11
20
|
|
12
21
|
## Installation
|
13
22
|
|
@@ -26,16 +35,22 @@ Since this is an [AngularJS](http://angularjs.org) module it of course depends o
|
|
26
35
|
* [$http](http://docs.angularjs.org/api/ng.$http)
|
27
36
|
* [$q](http://docs.angularjs.org/api/ng.$q)
|
28
37
|
* [$injector](http://docs.angularjs.org/api/AUTO.$injector)
|
38
|
+
* [$interpolate](http://docs.angularjs.org/api/ng.$interpolate)
|
29
39
|
|
30
40
|
## Resource Creation
|
31
|
-
Creating a resource using this factory is similar to using $resource, you just call the factory with the config options and
|
41
|
+
Creating a resource using this factory is similar to using $resource, you just call the factory with the config options and it returns a new resource function.
|
42
|
+
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
|
43
|
+
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
|
44
|
+
to create, update, and delete instance methods (or any others you add).
|
45
|
+
|
46
|
+
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.
|
47
|
+
See [Examples](#examples) below for more information on creating and injecting the resource.
|
32
48
|
|
33
|
-
var resource = railsResourceFactory(config);
|
34
49
|
|
35
50
|
### Config Options
|
36
51
|
The following options are available for the config object passed to the factory function.
|
37
52
|
|
38
|
-
* **url** - This is the
|
53
|
+
* **url** - This is the url of the service. See [Resource URLs](#resource-urls) below for more information.
|
39
54
|
* **name** - This is the name used for root wrapping when dealing with singular instances.
|
40
55
|
* **pluralName** *(optional)* - If specified this name will be used for unwrapping query results,
|
41
56
|
if not specified the singular name with an appended 's' will be used.
|
@@ -48,8 +63,20 @@ The following options are available for the config object passed to the factory
|
|
48
63
|
For example, you should specify "publishingCompany" and "publishingCompanies" instead of "publishing_company" and "publishing_companies".
|
49
64
|
|
50
65
|
## Resource URLs
|
51
|
-
The
|
52
|
-
|
66
|
+
The URL can be specified as one of three ways:
|
67
|
+
|
68
|
+
1. function (context) - You can pass your own custom function that converts a context variable into a url string
|
69
|
+
|
70
|
+
2. basic string - A string without any expression variables will be treated as a base URL and assumed that instance requests should append id to the end.
|
71
|
+
|
72
|
+
3. AngularJS expression - An expression url is evaluated at run time based on the given context for non-instance methods or the instance itself. For example, given the url expression: /stores/{{storeId}}/items/{{id}}
|
73
|
+
|
74
|
+
Item.query({category: 'Software'}, {storeId: 123}) would generate a GET to /stores/1234/items?category=Software
|
75
|
+
Item.get({storeId: 123, id: 1}) would generate a GET to /stores/123/items/1
|
76
|
+
|
77
|
+
new Item({store: 123}).create() would generate a POST to /stores/123/items
|
78
|
+
new Item({id: 1, storeId: 123}).update() would generate a PUT to /stores/123/items/1
|
79
|
+
|
53
80
|
|
54
81
|
## Transformers / Interceptors
|
55
82
|
The transformers and interceptors can be specified using an array containing transformer/interceptor functions or strings
|
@@ -93,7 +120,7 @@ Resources created using this factory have the following methods available and ea
|
|
93
120
|
### constructor
|
94
121
|
***
|
95
122
|
|
96
|
-
The constructor function
|
123
|
+
The constructor is the function returned by the railsResourceFactory and can be used with the "new" keyword.
|
97
124
|
|
98
125
|
####Parameters
|
99
126
|
* **data** *(optional)* - An object containing the data to be stored in the instance.
|
@@ -104,7 +131,8 @@ The constructor function is to create new instances of the resource.
|
|
104
131
|
A "class" method that executes a GET request against the base url with query parameters set via the params option.
|
105
132
|
|
106
133
|
####Parameters
|
107
|
-
* **params** - An map of strings or objects that are passed to $http to be turned into query parameters
|
134
|
+
* **query params** - An map of strings or objects that are passed to $http to be turned into query parameters
|
135
|
+
* **context** - A context object that is used during url evaluation to resolve expression variables
|
108
136
|
|
109
137
|
|
110
138
|
### get
|
@@ -113,7 +141,7 @@ A "class" method that executes a GET request against the base url with query par
|
|
113
141
|
A "class" method that executes a GET request against the resource url.
|
114
142
|
|
115
143
|
####Parameters
|
116
|
-
* **
|
144
|
+
* **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.
|
117
145
|
|
118
146
|
|
119
147
|
### create
|
@@ -147,32 +175,26 @@ None
|
|
147
175
|
|
148
176
|
|
149
177
|
## Example
|
150
|
-
|
178
|
+
For a complete working example (including the rails side), check out the [Employee Training Tracker](https://github.com/FineLinePrototyping/employee-training-tracker) application
|
179
|
+
we open sourced based on an interface we created for use internally that uses this module as well as many others.
|
180
|
+
|
181
|
+
### Define Resource
|
182
|
+
In order to create a Book resource, we would first define the factory within a module.
|
151
183
|
|
152
184
|
angular.module('book.services', ['rails']);
|
153
185
|
angular.module('book.services').factory('Book', ['railsResourceFactory', function (railsResourceFactory) {
|
154
186
|
return railsResourceFactory({url: '/books', name: 'book'});
|
155
187
|
}]);
|
156
188
|
|
157
|
-
|
158
|
-
var books = Book.query({title: 'The Hobbit'});
|
189
|
+
We would then inject that service into a controller:
|
159
190
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
#### Create Book
|
164
|
-
var book = new Book({author: 'J. R. R. Tolkein', title: 'The Hobbit'});
|
165
|
-
book.create().then(function (result) {
|
166
|
-
// creation was successful
|
167
|
-
});
|
191
|
+
angular.module('book.controllers').controller('BookShelfCtrl', ['$scope', 'Book', function ($scope, Book) {
|
192
|
+
// See following examples for using Book within your controller
|
193
|
+
}]);
|
168
194
|
|
169
|
-
|
170
|
-
Book.get(1234).then(function (book) {
|
171
|
-
book.author = 'J. R. R. Tolkein';
|
172
|
-
book.update();
|
173
|
-
});
|
195
|
+
The examples below illustrate how you would then use the Book service to get, create, update, and delete data.
|
174
196
|
|
175
|
-
|
197
|
+
#### Extending
|
176
198
|
You can add additional "class" or "instance" methods by modifying the resource returned from the factory call. For instance,
|
177
199
|
if you wanted to add a "class" method named "findByTitle" to the Book resource you would modify the service setup as follows:
|
178
200
|
|
@@ -182,18 +204,81 @@ if you wanted to add a "class" method named "findByTitle" to the Book resource y
|
|
182
204
|
resource.findByTitle = function (title) {
|
183
205
|
return resource.query({title: title});
|
184
206
|
};
|
207
|
+
return resource;
|
185
208
|
}]);
|
186
209
|
|
187
|
-
If you wanted to add an "instance" method
|
210
|
+
If you wanted to add an "instance" method to retrieve a related object:
|
188
211
|
|
189
212
|
angular.module('book.services', ['rails']);
|
190
|
-
angular.module('book.services').factory('
|
213
|
+
angular.module('book.services').factory('Author', ['railsResourceFactory', function (railsResourceFactory) {
|
214
|
+
return railsResourceFactory({url: '/authors', name: 'author'});
|
215
|
+
}]);
|
216
|
+
angular.module('book.services').factory('Book', ['railsResourceFactory', 'Author', function (railsResourceFactory, Author) {
|
217
|
+
var resource = railsResourceFactory({url: '/books', name: 'book'});
|
218
|
+
resource.prototype.getAuthor = function () {
|
219
|
+
return Author.get(this.authorId);
|
220
|
+
};
|
221
|
+
}]);
|
222
|
+
|
223
|
+
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
|
224
|
+
passes the resulting promise to the processResponse method which will perform the same transformations and handling that the get or query would use.
|
225
|
+
|
226
|
+
angular.module('book.services', ['rails']);
|
227
|
+
angular.module('book.services').factory('Book', ['railsResourceFactory', '$http', function (railsResourceFactory, $http) {
|
191
228
|
var resource = railsResourceFactory({url: '/books', name: 'book'});
|
192
|
-
resource.prototype.
|
193
|
-
|
229
|
+
resource.prototype.getReferences = function () {
|
230
|
+
var self = this;
|
231
|
+
return resource.processResponse($http.get(resource.resourceUrl(this.id) + '/references')).then(function (references) {
|
232
|
+
self.references = references;
|
233
|
+
return self.references;
|
234
|
+
});
|
194
235
|
};
|
195
236
|
}]);
|
196
237
|
|
238
|
+
### Query Books
|
239
|
+
To query for a list of books with the title "The Hobbit" you would use the query method:
|
240
|
+
|
241
|
+
var books = Book.query({title: 'The Hobbit'});
|
242
|
+
|
243
|
+
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
|
244
|
+
to your user that the search is executing so you'd want to use a then handler:
|
245
|
+
|
246
|
+
$scope.searching = true;
|
247
|
+
var books = Book.query({title: 'The Hobbit'});
|
248
|
+
books.then(function(results) {
|
249
|
+
$scope.searching = false;
|
250
|
+
}, function (error) {
|
251
|
+
$scope.searching = false;
|
252
|
+
// display error
|
253
|
+
});
|
254
|
+
|
255
|
+
|
256
|
+
### Get Book
|
257
|
+
var book = Book.get(1234);
|
258
|
+
|
259
|
+
Again, it's important to remember that book is a promise, if instead you wanted the book data you would use the "then" function:
|
260
|
+
|
261
|
+
Book.get(1234).then(function (book) {
|
262
|
+
// book contains the data returned from the service
|
263
|
+
});
|
264
|
+
|
265
|
+
### Create Book
|
266
|
+
var book = new Book({author: 'J. R. R. Tolkein', title: 'The Hobbit'});
|
267
|
+
book.create().then(function (result) {
|
268
|
+
// creation was successful
|
269
|
+
});
|
270
|
+
|
271
|
+
### Update Book
|
272
|
+
Book.get(1234).then(function (book) {
|
273
|
+
book.author = 'J. R. R. Tolkein';
|
274
|
+
book.update();
|
275
|
+
});
|
276
|
+
|
277
|
+
Or, if you say the user typed in the book id into a scope variable and you wanted to update the book without having to first retrieve it:
|
278
|
+
|
279
|
+
var book = new Book({id: $scope.bookId, author: $scope.authorName, title: $scope.bookTitle});
|
280
|
+
book.update();
|
281
|
+
|
197
282
|
## Tests
|
198
283
|
The tests are written using [Jasmine](http://pivotal.github.com/jasmine/) and are run using [Testacular](http://vojtajina.github.com/testacular/).
|
199
284
|
|
@@ -134,15 +134,11 @@ describe("js.rails", function () {
|
|
134
134
|
});
|
135
135
|
});
|
136
136
|
|
137
|
-
describe('railsResourceFactory', function() {
|
138
|
-
var $httpBackend, $rootScope, factory, Test,
|
137
|
+
describe('singular railsResourceFactory', function() {
|
138
|
+
var $httpBackend, $rootScope, factory, Test,
|
139
139
|
config = {
|
140
140
|
url: '/test',
|
141
141
|
name: 'test'
|
142
|
-
}, pluralConfig = {
|
143
|
-
url: '/pluralTest',
|
144
|
-
name: 'singular',
|
145
|
-
pluralName: 'plural'
|
146
142
|
};
|
147
143
|
|
148
144
|
beforeEach(inject(function (_$httpBackend_, _$rootScope_, railsResourceFactory) {
|
@@ -150,7 +146,6 @@ describe("js.rails", function () {
|
|
150
146
|
$rootScope = _$rootScope_;
|
151
147
|
factory = railsResourceFactory;
|
152
148
|
Test = railsResourceFactory(config);
|
153
|
-
PluralTest = railsResourceFactory(pluralConfig);
|
154
149
|
}));
|
155
150
|
|
156
151
|
afterEach(function() {
|
@@ -222,34 +217,12 @@ describe("js.rails", function () {
|
|
222
217
|
$httpBackend.flush();
|
223
218
|
}));
|
224
219
|
|
225
|
-
it('
|
226
|
-
var promise, result;
|
227
|
-
|
228
|
-
$httpBackend.expectGET('/pluralTest').respond(200, {plural: [{abc: 'xyz'}, {xyz: 'abc'}]});
|
229
|
-
|
230
|
-
expect(promise = PluralTest.query()).toBeDefined();
|
231
|
-
|
232
|
-
promise.then(function (response) {
|
233
|
-
result = response;
|
234
|
-
});
|
235
|
-
|
236
|
-
$httpBackend.flush();
|
237
|
-
|
238
|
-
expect(angular.isArray(result)).toBe(true);
|
239
|
-
angular.forEach(result, function (value) {
|
240
|
-
expect(value).toBeInstanceOf(PluralTest);
|
241
|
-
});
|
242
|
-
expect(result[0]).toEqualData({abc: 'xyz'});
|
243
|
-
expect(result[1]).toEqualData({xyz: 'abc'});
|
244
|
-
|
245
|
-
}));
|
246
|
-
|
247
|
-
it('query should return empty array when result is empty array', inject(function($httpBackend) {
|
220
|
+
it('get should return resource object when response is 200', inject(function($httpBackend) {
|
248
221
|
var promise, result;
|
249
222
|
|
250
|
-
$httpBackend.expectGET('/
|
223
|
+
$httpBackend.expectGET('/test/123').respond(200, {test: {id: 123, abc: 'xyz'}});
|
251
224
|
|
252
|
-
expect(promise =
|
225
|
+
expect(promise = Test.get(123)).toBeDefined();
|
253
226
|
|
254
227
|
promise.then(function (response) {
|
255
228
|
result = response;
|
@@ -257,17 +230,16 @@ describe("js.rails", function () {
|
|
257
230
|
|
258
231
|
$httpBackend.flush();
|
259
232
|
|
260
|
-
expect(
|
261
|
-
expect(result
|
233
|
+
expect(result).toBeInstanceOf(Test);
|
234
|
+
expect(result).toEqualData({id: 123, abc: 'xyz'});
|
262
235
|
}));
|
263
236
|
|
264
|
-
|
265
|
-
it('get should return resource object when response is 200', inject(function($httpBackend) {
|
237
|
+
it('get should work with id as string as well', inject(function($httpBackend) {
|
266
238
|
var promise, result;
|
267
239
|
|
268
240
|
$httpBackend.expectGET('/test/123').respond(200, {test: {id: 123, abc: 'xyz'}});
|
269
241
|
|
270
|
-
expect(promise = Test.get(123)).toBeDefined();
|
242
|
+
expect(promise = Test.get('123')).toBeDefined();
|
271
243
|
|
272
244
|
promise.then(function (response) {
|
273
245
|
result = response;
|
@@ -340,7 +312,7 @@ describe("js.rails", function () {
|
|
340
312
|
angular.copy(config, testConfig);
|
341
313
|
testConfig.requestTransformers = [];
|
342
314
|
testConfig.responseInterceptors = ['railsFieldRenamingInterceptor'];
|
343
|
-
resource = factory(
|
315
|
+
resource = factory(testConfig);
|
344
316
|
|
345
317
|
expect(promise = resource.get(123)).toBeDefined();
|
346
318
|
|
@@ -483,4 +455,372 @@ describe("js.rails", function () {
|
|
483
455
|
}));
|
484
456
|
});
|
485
457
|
|
458
|
+
describe('plural railsResourceFactory', function() {
|
459
|
+
var $httpBackend, $rootScope, factory, PluralTest,
|
460
|
+
pluralConfig = {
|
461
|
+
url: '/pluralTest',
|
462
|
+
name: 'singular',
|
463
|
+
pluralName: 'plural'
|
464
|
+
};
|
465
|
+
|
466
|
+
beforeEach(inject(function (_$httpBackend_, _$rootScope_, railsResourceFactory) {
|
467
|
+
$httpBackend = _$httpBackend_;
|
468
|
+
$rootScope = _$rootScope_;
|
469
|
+
factory = railsResourceFactory;
|
470
|
+
PluralTest = railsResourceFactory(pluralConfig);
|
471
|
+
}));
|
472
|
+
|
473
|
+
afterEach(function() {
|
474
|
+
$httpBackend.verifyNoOutstandingExpectation();
|
475
|
+
$httpBackend.verifyNoOutstandingRequest();
|
476
|
+
});
|
477
|
+
|
478
|
+
it('query should return array of resource objects when result is an array', inject(function($httpBackend) {
|
479
|
+
var promise, result;
|
480
|
+
|
481
|
+
$httpBackend.expectGET('/pluralTest').respond(200, {plural: [{abc: 'xyz'}, {xyz: 'abc'}]});
|
482
|
+
|
483
|
+
expect(promise = PluralTest.query()).toBeDefined();
|
484
|
+
|
485
|
+
promise.then(function (response) {
|
486
|
+
result = response;
|
487
|
+
});
|
488
|
+
|
489
|
+
$httpBackend.flush();
|
490
|
+
|
491
|
+
expect(angular.isArray(result)).toBe(true);
|
492
|
+
angular.forEach(result, function (value) {
|
493
|
+
expect(value).toBeInstanceOf(PluralTest);
|
494
|
+
});
|
495
|
+
expect(result[0]).toEqualData({abc: 'xyz'});
|
496
|
+
expect(result[1]).toEqualData({xyz: 'abc'});
|
497
|
+
|
498
|
+
}));
|
499
|
+
|
500
|
+
it('query should return empty array when result is empty array', inject(function($httpBackend) {
|
501
|
+
var promise, result;
|
502
|
+
|
503
|
+
$httpBackend.expectGET('/pluralTest').respond(200, {plural: []});
|
504
|
+
|
505
|
+
expect(promise = PluralTest.query()).toBeDefined();
|
506
|
+
|
507
|
+
promise.then(function (response) {
|
508
|
+
result = response;
|
509
|
+
});
|
510
|
+
|
511
|
+
$httpBackend.flush();
|
512
|
+
|
513
|
+
expect(angular.isArray(result)).toBe(true);
|
514
|
+
expect(result.length).toBe(0);
|
515
|
+
}));
|
516
|
+
});
|
517
|
+
|
518
|
+
describe('nested railsResourceFactory', function() {
|
519
|
+
var $httpBackend, $rootScope, factory, NestedTest,
|
520
|
+
nestedConfig = {
|
521
|
+
url: '/nested/{{nestedId}}/test/{{id}}',
|
522
|
+
name: 'nestedTest'
|
523
|
+
};
|
524
|
+
|
525
|
+
beforeEach(inject(function (_$httpBackend_, _$rootScope_, railsResourceFactory) {
|
526
|
+
$httpBackend = _$httpBackend_;
|
527
|
+
$rootScope = _$rootScope_;
|
528
|
+
factory = railsResourceFactory;
|
529
|
+
NestedTest = railsResourceFactory(nestedConfig);
|
530
|
+
}));
|
531
|
+
|
532
|
+
afterEach(function() {
|
533
|
+
$httpBackend.verifyNoOutstandingExpectation();
|
534
|
+
$httpBackend.verifyNoOutstandingRequest();
|
535
|
+
});
|
536
|
+
|
537
|
+
it('query should return resource object when response is single object', inject(function($httpBackend) {
|
538
|
+
var promise, result;
|
539
|
+
|
540
|
+
$httpBackend.expectGET('/nested/1234/test').respond(200, {nested_test: {abc: 'xyz'}});
|
541
|
+
|
542
|
+
expect(promise = NestedTest.query(null, {nestedId: 1234})).toBeDefined();
|
543
|
+
|
544
|
+
promise.then(function (response) {
|
545
|
+
result = response;
|
546
|
+
});
|
547
|
+
|
548
|
+
$httpBackend.flush();
|
549
|
+
|
550
|
+
expect(result).toBeInstanceOf(NestedTest);
|
551
|
+
expect(result).toEqualData({abc: 'xyz'});
|
552
|
+
}));
|
553
|
+
|
554
|
+
it('query should return no data on 204', inject(function($httpBackend) {
|
555
|
+
var promise, result;
|
556
|
+
|
557
|
+
$httpBackend.expectGET('/nested/1234/test').respond(204);
|
558
|
+
expect(promise = NestedTest.query(null, {nestedId: 1234})).toBeDefined();
|
559
|
+
|
560
|
+
promise.then(function (response) {
|
561
|
+
result = response;
|
562
|
+
});
|
563
|
+
|
564
|
+
$httpBackend.flush();
|
565
|
+
|
566
|
+
expect(result).toBeUndefined();
|
567
|
+
}));
|
568
|
+
|
569
|
+
it('query should add parameter abc=1', inject(function($httpBackend) {
|
570
|
+
var promise;
|
571
|
+
|
572
|
+
$httpBackend.expectGET('/nested/1234/test?abc=1').respond(200, {nested_test: {abc: 'xyz'}});
|
573
|
+
|
574
|
+
expect(promise = NestedTest.query({abc: '1'}, {nestedId: 1234})).toBeDefined();
|
575
|
+
$httpBackend.flush();
|
576
|
+
}));
|
577
|
+
|
578
|
+
it('query should add parameters abc=1 & xyz=2', inject(function($httpBackend) {
|
579
|
+
var promise;
|
580
|
+
|
581
|
+
$httpBackend.expectGET('/nested/1234/test?abc=1&xyz=2').respond(200, {nested_test: {abc: 'xyz'}});
|
582
|
+
|
583
|
+
expect(promise = NestedTest.query({abc: '1', xyz: 2}, {nestedId: 1234})).toBeDefined();
|
584
|
+
$httpBackend.flush();
|
585
|
+
}));
|
586
|
+
|
587
|
+
it('query with default params should add parameter abc=1', inject(function($httpBackend) {
|
588
|
+
var promise, resource, defaultParamsConfig = {};
|
589
|
+
|
590
|
+
$httpBackend.expectGET('/nested/1234/test?abc=1').respond(200, {nested_test: {abc: 'xyz'}});
|
591
|
+
|
592
|
+
angular.copy(nestedConfig, defaultParamsConfig);
|
593
|
+
defaultParamsConfig.defaultParams = {abc: '1'};
|
594
|
+
|
595
|
+
resource = factory(defaultParamsConfig);
|
596
|
+
expect(promise = resource.query(null, {nestedId: 1234})).toBeDefined();
|
597
|
+
|
598
|
+
$httpBackend.flush();
|
599
|
+
}));
|
600
|
+
|
601
|
+
it('get should return resource object when response is 200', inject(function($httpBackend) {
|
602
|
+
var promise, result;
|
603
|
+
|
604
|
+
$httpBackend.expectGET('/nested/1234/test/123').respond(200, {nested_test: {id: 123, abc: 'xyz'}});
|
605
|
+
|
606
|
+
expect(promise = NestedTest.get({nestedId: 1234, id: 123})).toBeDefined();
|
607
|
+
|
608
|
+
promise.then(function (response) {
|
609
|
+
result = response;
|
610
|
+
});
|
611
|
+
|
612
|
+
$httpBackend.flush();
|
613
|
+
|
614
|
+
expect(result).toBeInstanceOf(NestedTest);
|
615
|
+
expect(result).toEqualData({id: 123, abc: 'xyz'});
|
616
|
+
}));
|
617
|
+
|
618
|
+
it('get should call failure callback when 404', inject(function($httpBackend) {
|
619
|
+
var promise, success = false, failure = false;
|
620
|
+
|
621
|
+
$httpBackend.expectGET('/nested/1234/test/123').respond(404);
|
622
|
+
|
623
|
+
expect(promise = NestedTest.get({nestedId: 1234, id: 123})).toBeDefined();
|
624
|
+
|
625
|
+
promise.then(function () {
|
626
|
+
success = true;
|
627
|
+
}, function () {
|
628
|
+
failure = true;
|
629
|
+
});
|
630
|
+
|
631
|
+
$httpBackend.flush();
|
632
|
+
|
633
|
+
expect(success).toBe(false);
|
634
|
+
expect(failure).toBe(true);
|
635
|
+
}));
|
636
|
+
|
637
|
+
it('get with default params should add parameter abc=1', inject(function($httpBackend) {
|
638
|
+
var promise, resource, defaultParamsConfig = {};
|
639
|
+
|
640
|
+
$httpBackend.expectGET('/nested/1234/test/123?abc=1').respond(200, {nested_test: {abc: 'xyz'}});
|
641
|
+
|
642
|
+
angular.copy(nestedConfig, defaultParamsConfig);
|
643
|
+
defaultParamsConfig.defaultParams = {abc: '1'};
|
644
|
+
|
645
|
+
resource = factory(defaultParamsConfig);
|
646
|
+
expect(promise = resource.get({nestedId: 1234, id: 123})).toBeDefined();
|
647
|
+
|
648
|
+
$httpBackend.flush();
|
649
|
+
}));
|
650
|
+
|
651
|
+
it('should be able to turn off root mapping and field renaming', inject(function($httpBackend) {
|
652
|
+
var promise, result, resource;
|
653
|
+
|
654
|
+
$httpBackend.expectGET('/nested/1234/test/123').respond(200, {id: 123, nested_id: 1234, abc_def: 'xyz'});
|
655
|
+
|
656
|
+
resource = factory(nestedConfig);
|
657
|
+
resource.responseInterceptors = [];
|
658
|
+
resource.requestTransformers = [];
|
659
|
+
expect(promise = resource.get({nestedId: 1234, id: 123})).toBeDefined();
|
660
|
+
|
661
|
+
promise.then(function (response) {
|
662
|
+
result = response;
|
663
|
+
});
|
664
|
+
|
665
|
+
$httpBackend.flush();
|
666
|
+
|
667
|
+
expect(result).toBeInstanceOf(resource);
|
668
|
+
expect(result).toEqualData({id: 123, nested_id: 1234, abc_def: 'xyz'});
|
669
|
+
}));
|
670
|
+
|
671
|
+
it('should be able to turn off root mapping but keep field renaming', inject(function($httpBackend) {
|
672
|
+
var promise, result, resource, testConfig = {};
|
673
|
+
|
674
|
+
$httpBackend.expectGET('/nested/1234/test/123').respond(200, {id: 123, nested_id: 1234, abc_def: 'xyz'});
|
675
|
+
|
676
|
+
angular.copy(nestedConfig, testConfig);
|
677
|
+
testConfig.requestTransformers = [];
|
678
|
+
testConfig.responseInterceptors = ['railsFieldRenamingInterceptor'];
|
679
|
+
resource = factory(testConfig);
|
680
|
+
|
681
|
+
expect(promise = resource.get({nestedId: 1234, id: 123})).toBeDefined();
|
682
|
+
|
683
|
+
promise.then(function (response) {
|
684
|
+
result = response;
|
685
|
+
});
|
686
|
+
|
687
|
+
$httpBackend.flush();
|
688
|
+
|
689
|
+
expect(result).toBeInstanceOf(resource);
|
690
|
+
expect(result).toEqualData({id: 123, nestedId: 1234, abcDef: 'xyz'});
|
691
|
+
}));
|
692
|
+
|
693
|
+
it('should be able to create new instance and save it', inject(function($httpBackend) {
|
694
|
+
var data = new NestedTest({nestedId: 1234, abcDef: 'xyz'});
|
695
|
+
|
696
|
+
$httpBackend.expectPOST('/nested/1234/test').respond(200, {nested_test: {id: 123, nested_id: 1234, abc_def: 'xyz'}});
|
697
|
+
data.nestedId = 1234;
|
698
|
+
data.create();
|
699
|
+
$httpBackend.flush();
|
700
|
+
|
701
|
+
expect(data).toEqualData({id: 123, nestedId: 1234, abcDef: 'xyz'});
|
702
|
+
}));
|
703
|
+
|
704
|
+
it('should be able to create new instance and update it', inject(function($httpBackend) {
|
705
|
+
var data = new NestedTest({abcDef: 'xyz'});
|
706
|
+
|
707
|
+
$httpBackend.expectPOST('/nested/1234/test').respond(200, {nested_test: {id: 123, nested_id: 1234, abc_def: 'xyz'}});
|
708
|
+
data.nestedId = 1234;
|
709
|
+
data.create();
|
710
|
+
$httpBackend.flush(1);
|
711
|
+
|
712
|
+
expect(data).toEqualData({id: 123, nestedId: 1234, abcDef: 'xyz'});
|
713
|
+
|
714
|
+
$httpBackend.expectPUT('/nested/1234/test/123').respond(200, {nested_test: {id: 123, nested_id: 1234, abc_def: 'xyz', xyz: 'abc', extra: 'test'}});
|
715
|
+
data.xyz = 'abc';
|
716
|
+
data.update();
|
717
|
+
$httpBackend.flush();
|
718
|
+
|
719
|
+
expect(data).toEqualData({id: 123, nestedId: 1234, abcDef: 'xyz', xyz: 'abc', extra: 'test'});
|
720
|
+
}));
|
721
|
+
|
722
|
+
it('create with default params should add parameter abc=1', inject(function($httpBackend) {
|
723
|
+
var promise, Resource, data, defaultParamsConfig = {};
|
724
|
+
|
725
|
+
$httpBackend.expectPOST('/nested/1234/test?abc=1').respond(200, {nested_test: {abc: 'xyz'}});
|
726
|
+
|
727
|
+
angular.copy(nestedConfig, defaultParamsConfig);
|
728
|
+
defaultParamsConfig.defaultParams = {abc: '1'};
|
729
|
+
|
730
|
+
Resource = factory(defaultParamsConfig);
|
731
|
+
data = new Resource();
|
732
|
+
data.nestedId = 1234;
|
733
|
+
data.create();
|
734
|
+
|
735
|
+
$httpBackend.flush();
|
736
|
+
}));
|
737
|
+
|
738
|
+
it('should be able to get resource and update it', inject(function($httpBackend) {
|
739
|
+
var promise, result;
|
740
|
+
|
741
|
+
$httpBackend.expectGET('/nested/1234/test/123').respond(200, {nested_test: {id: 123, nested_id: 1234, abc: 'xyz'}});
|
742
|
+
|
743
|
+
expect(promise = NestedTest.get({nestedId: 1234, id: 123})).toBeDefined();
|
744
|
+
|
745
|
+
promise.then(function (response) {
|
746
|
+
result = response;
|
747
|
+
});
|
748
|
+
|
749
|
+
$httpBackend.flush();
|
750
|
+
|
751
|
+
expect(result).toBeInstanceOf(NestedTest);
|
752
|
+
expect(result).toEqualData({id: 123, nestedId: 1234, abc: 'xyz'});
|
753
|
+
|
754
|
+
$httpBackend.expectPUT('/nested/1234/test/123').respond(200, {nested_test: {id: 123, nested_id: 1234, abc_def: 'xyz', xyz: 'abc', extra: 'test'}});
|
755
|
+
result.xyz = 'abc';
|
756
|
+
result.update();
|
757
|
+
$httpBackend.flush();
|
758
|
+
|
759
|
+
// abc was originally set on the object so it should still be there after the update
|
760
|
+
expect(result).toEqualData({id: 123, nestedId: 1234, abc: 'xyz', abcDef: 'xyz', xyz: 'abc', extra: 'test'});
|
761
|
+
}));
|
762
|
+
|
763
|
+
it('update should handle 204 response', inject(function($httpBackend) {
|
764
|
+
var promise, result;
|
765
|
+
|
766
|
+
$httpBackend.expectGET('/nested/1234/test/123').respond(200, {nested_test: {id: 123, nested_id: 1234, abc: 'xyz'}});
|
767
|
+
|
768
|
+
expect(promise = NestedTest.get({nestedId: 1234, id: 123})).toBeDefined();
|
769
|
+
|
770
|
+
promise.then(function (response) {
|
771
|
+
result = response;
|
772
|
+
});
|
773
|
+
|
774
|
+
$httpBackend.flush();
|
775
|
+
|
776
|
+
expect(result).toBeInstanceOf(NestedTest);
|
777
|
+
expect(result).toEqualData({id: 123, nestedId: 1234, abc: 'xyz'});
|
778
|
+
|
779
|
+
$httpBackend.expectPUT('/nested/1234/test/123').respond(204);
|
780
|
+
result.xyz = 'abc';
|
781
|
+
result.update();
|
782
|
+
$httpBackend.flush();
|
783
|
+
|
784
|
+
expect(result).toEqualData({id: 123, nestedId: 1234, abc: 'xyz', xyz: 'abc'});
|
785
|
+
}));
|
786
|
+
|
787
|
+
it('should be able to delete instance returned from get', inject(function($httpBackend) {
|
788
|
+
var promise, result;
|
789
|
+
|
790
|
+
$httpBackend.expectGET('/nested/1234/test/123').respond(200, {nested_test: {id: 123, nestedId: 1234, abc: 'xyz'}});
|
791
|
+
|
792
|
+
expect(promise = NestedTest.get({nestedId: 1234, id: 123})).toBeDefined();
|
793
|
+
|
794
|
+
promise.then(function (response) {
|
795
|
+
result = response;
|
796
|
+
});
|
797
|
+
|
798
|
+
$httpBackend.flush();
|
799
|
+
|
800
|
+
expect(result).toBeInstanceOf(NestedTest);
|
801
|
+
expect(result).toEqualData({id: 123, nestedId: 1234, abc: 'xyz'});
|
802
|
+
|
803
|
+
$httpBackend.expectDELETE('/nested/1234/test/123').respond(204);
|
804
|
+
result.remove();
|
805
|
+
$httpBackend.flush();
|
806
|
+
}));
|
807
|
+
|
808
|
+
it('delete with default params should add parameter abc=1', inject(function($httpBackend) {
|
809
|
+
var Resource, data, defaultParamsConfig = {};
|
810
|
+
|
811
|
+
$httpBackend.expectDELETE('/nested/1234/test/123?abc=1').respond(204);
|
812
|
+
|
813
|
+
angular.copy(nestedConfig, defaultParamsConfig);
|
814
|
+
defaultParamsConfig.defaultParams = {abc: '1'};
|
815
|
+
|
816
|
+
Resource = factory(defaultParamsConfig);
|
817
|
+
data = new Resource();
|
818
|
+
data.id = 123;
|
819
|
+
data.nestedId = 1234;
|
820
|
+
data.remove();
|
821
|
+
|
822
|
+
$httpBackend.flush();
|
823
|
+
}));
|
824
|
+
});
|
825
|
+
|
486
826
|
});
|
@@ -84,7 +84,30 @@
|
|
84
84
|
};
|
85
85
|
});
|
86
86
|
|
87
|
-
angular.module('rails').factory('railsResourceFactory', ['$http', '$q', '$injector', function ($http, $q, $injector) {
|
87
|
+
angular.module('rails').factory('railsResourceFactory', ['$http', '$q', '$injector', '$interpolate', function ($http, $q, $injector, $interpolate) {
|
88
|
+
function urlBuilder(url) {
|
89
|
+
var expression;
|
90
|
+
|
91
|
+
if (angular.isFunction(url)) {
|
92
|
+
return url;
|
93
|
+
}
|
94
|
+
|
95
|
+
if (url.indexOf('{{') === -1) {
|
96
|
+
url = url + '/{{id}}';
|
97
|
+
}
|
98
|
+
|
99
|
+
expression = $interpolate(url);
|
100
|
+
|
101
|
+
return function (params) {
|
102
|
+
url = expression(params);
|
103
|
+
|
104
|
+
if (url.charAt(url.length - 1) === '/') {
|
105
|
+
url = url.substr(0, url.length - 1);
|
106
|
+
}
|
107
|
+
|
108
|
+
return url;
|
109
|
+
};
|
110
|
+
}
|
88
111
|
|
89
112
|
function railsResourceFactory(config) {
|
90
113
|
var transformers = config.requestTransformers || ['railsRootWrappingTransformer', 'railsFieldRenamingTransformer'],
|
@@ -94,7 +117,7 @@
|
|
94
117
|
angular.extend(this, value || {});
|
95
118
|
}
|
96
119
|
|
97
|
-
RailsResource.url = config.url;
|
120
|
+
RailsResource.url = urlBuilder(config.url);
|
98
121
|
RailsResource.rootName = config.name;
|
99
122
|
RailsResource.rootPluralName = config.pluralName || config.name + 's';
|
100
123
|
RailsResource.httpConfig = config.httpConfig || {};
|
@@ -159,30 +182,58 @@
|
|
159
182
|
});
|
160
183
|
};
|
161
184
|
|
162
|
-
RailsResource.
|
163
|
-
var
|
185
|
+
RailsResource.getParameters = function (queryParams) {
|
186
|
+
var params;
|
164
187
|
|
165
188
|
if (RailsResource.defaultParams) {
|
166
|
-
|
189
|
+
params = RailsResource.defaultParams;
|
167
190
|
}
|
168
191
|
|
169
|
-
if (queryParams) {
|
170
|
-
|
192
|
+
if (angular.isObject(queryParams)) {
|
193
|
+
params = angular.extend(params || {}, queryParams);
|
194
|
+
}
|
195
|
+
|
196
|
+
return params;
|
197
|
+
};
|
198
|
+
|
199
|
+
RailsResource.getHttpConfig = function (queryParams) {
|
200
|
+
var params = RailsResource.getParameters(queryParams);
|
201
|
+
|
202
|
+
if (params) {
|
203
|
+
return angular.extend({params: params}, RailsResource.httpConfig);
|
171
204
|
}
|
172
205
|
|
173
|
-
return
|
206
|
+
return RailsResource.httpConfig;
|
174
207
|
};
|
175
208
|
|
176
|
-
|
177
|
-
|
209
|
+
/**
|
210
|
+
* Returns a URL from the given parameters. You can override this method on your resource definitions to provide
|
211
|
+
* custom logic for building your URLs or you can utilize the parameterized url strings to substitute values in the
|
212
|
+
* URL string.
|
213
|
+
*
|
214
|
+
* The parameters in the URL string follow the normal Angular binding expression using {{ and }} for the start/end symbols.
|
215
|
+
*
|
216
|
+
* If the context is a number and the URL string does not contain an id parameter then the number is appended
|
217
|
+
* to the URL string.
|
218
|
+
*
|
219
|
+
* If the context is a number and the URL string does
|
220
|
+
* @param context
|
221
|
+
* @return {string}
|
222
|
+
*/
|
223
|
+
RailsResource.resourceUrl = function (context) {
|
224
|
+
if (!angular.isObject(context)) {
|
225
|
+
context = {id: context};
|
226
|
+
}
|
227
|
+
|
228
|
+
return RailsResource.url(context || {});
|
178
229
|
};
|
179
230
|
|
180
|
-
RailsResource.query = function (queryParams) {
|
181
|
-
return RailsResource.processResponse($http.get(RailsResource.
|
231
|
+
RailsResource.query = function (queryParams, context) {
|
232
|
+
return RailsResource.processResponse($http.get(RailsResource.resourceUrl(context), RailsResource.getHttpConfig(queryParams)));
|
182
233
|
};
|
183
234
|
|
184
|
-
RailsResource.get = function (
|
185
|
-
return RailsResource.processResponse($http.get(RailsResource.resourceUrl(
|
235
|
+
RailsResource.get = function (context, queryParams) {
|
236
|
+
return RailsResource.processResponse($http.get(RailsResource.resourceUrl(context), RailsResource.getHttpConfig(queryParams)));
|
186
237
|
};
|
187
238
|
|
188
239
|
RailsResource.prototype.processResponse = function (promise) {
|
@@ -207,17 +258,17 @@
|
|
207
258
|
RailsResource.prototype.create = function () {
|
208
259
|
// clone so we can manipulate w/o modifying our instance
|
209
260
|
var data = RailsResource.transformData(angular.copy(this, {}));
|
210
|
-
return this.processResponse($http.post(RailsResource.
|
261
|
+
return this.processResponse($http.post(RailsResource.resourceUrl(this), data, RailsResource.getHttpConfig()));
|
211
262
|
};
|
212
263
|
|
213
264
|
RailsResource.prototype.update = function () {
|
214
265
|
// clone so we can manipulate w/o modifying our instance
|
215
266
|
var data = RailsResource.transformData(angular.copy(this, {}));
|
216
|
-
return this.processResponse($http.put(RailsResource.resourceUrl(this
|
267
|
+
return this.processResponse($http.put(RailsResource.resourceUrl(this), data, RailsResource.getHttpConfig()));
|
217
268
|
};
|
218
269
|
|
219
|
-
RailsResource.prototype.remove = RailsResource.prototype.delete = function (
|
220
|
-
return this.processResponse($http.delete(RailsResource.resourceUrl(this
|
270
|
+
RailsResource.prototype.remove = RailsResource.prototype.delete = function () {
|
271
|
+
return this.processResponse($http.delete(RailsResource.resourceUrl(this), RailsResource.getHttpConfig()));
|
221
272
|
};
|
222
273
|
|
223
274
|
return RailsResource;
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: angularjs-rails-resource
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2013-02-11 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: A small AngularJS add-on for integrating with Rails via JSON more easily.
|
15
15
|
email:
|