angularjs-rails-resource 0.0.2 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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:
|