From 8096ce132a541e6c963765022710a4597fef50dc Mon Sep 17 00:00:00 2001 From: gregj1 Date: Mon, 4 May 2015 22:26:06 +1000 Subject: [PATCH 01/55] Fix missing 'get' in decoupled service Adds missing 'get' function to decoupled restangular services. --- src/restangular.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/restangular.js b/src/restangular.js index 73fc73ae..d407a51d 100644 --- a/src/restangular.js +++ b/src/restangular.js @@ -1305,6 +1305,7 @@ restangular.provider('Restangular', function() { serv.one = _.bind(one, (parent || service), parent, route); serv.post = _.bind(collection.post, collection); serv.getList = _.bind(collection.getList, collection); + serv.get = _.bind(collection.get, collection); for (var prop in collection) { if (collection.hasOwnProperty(prop) && _.isFunction(collection[prop]) && !_.contains(knownCollectionMethods, prop)) { From 95ea231148c0e49b624faffe5ef1e37426fe175d Mon Sep 17 00:00:00 2001 From: Ryan Dale Date: Wed, 10 Jun 2015 22:06:32 -0400 Subject: [PATCH 02/55] Fixes #1167: Extend condition to treat '0, which as a falsy value currently fails, as a valid ID --- src/restangular.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/restangular.js b/src/restangular.js index 73fc73ae..8b2dd0b3 100644 --- a/src/restangular.js +++ b/src/restangular.js @@ -555,7 +555,7 @@ restangular.provider('Restangular', function() { var url = this.base(current); - if (what) { + if (what || what === 0) { var add = ''; if (!/\/$/.test(url)) { add += '/'; From b8e0bd7a4e2d90ed61b924a02f10666378228699 Mon Sep 17 00:00:00 2001 From: Gaurav Arora Date: Wed, 7 Oct 2015 22:06:15 +0530 Subject: [PATCH 03/55] Fix minor details in README.md - Use latest version - Add a link to addfullrequestinterceptor in index --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 67945ba3..4ae4df43 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ You can also **check out [a video introduction of a talk I gave at Devoxx France - [setRequestInterceptor](#setrequestinterceptor) - [addRequestInterceptor](#addrequestinterceptor) - [setFullRequestInterceptor](#setfullrequestinterceptor) + - [addFullRequestInterceptor](#addfullrequestinterceptor) - [setErrorInterceptor](#seterrorinterceptor) - [setRestangularFields](#setrestangularfields) - [setMethodOverriders](#setmethodoverriders) @@ -143,8 +144,8 @@ You can download this by: ````html - - + + ```` **[Back to top](#table-of-contents)** From 51066ecf01a23f5756f1bcf756d4e6d6ef0ffeb5 Mon Sep 17 00:00:00 2001 From: sschollmeyer Date: Thu, 16 Jun 2016 12:13:38 +0200 Subject: [PATCH 04/55] add ability to restangularize a collection with fromServer set --- src/restangular.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/restangular.js b/src/restangular.js index 361f0c11..9fcbecc5 100644 --- a/src/restangular.js +++ b/src/restangular.js @@ -1006,11 +1006,11 @@ restangular.provider('Restangular', function() { return config.transformElem(localElem, true, route, service, true); } - function restangularizeCollectionAndElements(parent, element, route) { - var collection = restangularizeCollection(parent, element, route, false); + function restangularizeCollectionAndElements(parent, element, route, fromServer) { + var collection = restangularizeCollection(parent, element, route, fromServer); _.each(collection, function(elem) { if (elem) { - restangularizeElem(parent, elem, route, false); + restangularizeElem(parent, elem, route, fromServer); } }); return collection; From 3f1eb9ada2abe2a35f066e6510bb6976db07e648 Mon Sep 17 00:00:00 2001 From: sschollmeyer Date: Fri, 17 Jun 2016 09:50:05 +0200 Subject: [PATCH 05/55] update documentation and add a test for my changed behaviour --- README.md | 4 ++-- test/restangularSpec.js | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a7dea9d4..626520c7 100644 --- a/README.md +++ b/README.md @@ -710,8 +710,8 @@ These are the methods that can be called on the Restangular object. * **oneUrl(route, url)**: This will create a new Restangular object that is just a pointer to one element with the specified URL. * **allUrl(route, url)**: This creates a Restangular object that is just a pointer to a list at the specified URL. * **copy(fromElement)**: This will create a copy of the from element so that we can modify the copied one. -* **restangularizeElement(parent, element, route, queryParams)**: Restangularizes a new element -* **restangularizeCollection(parent, element, route, queryParams)**: Restangularizes a new collection +* **restangularizeElement(parent, element, route, fromServer, collection, queryParams)**: Restangularizes a new element +* **restangularizeCollection(parent, element, route, fromServer, queryParams)**: Restangularizes a new collection **[Back to top](#table-of-contents)** diff --git a/test/restangularSpec.js b/test/restangularSpec.js index 58ed4f39..5a307e7d 100644 --- a/test/restangularSpec.js +++ b/test/restangularSpec.js @@ -441,6 +441,17 @@ describe("Restangular", function() { expect(collection.getRestangularUrl()).toBe('/accounts'); }); + + it("should have fromServer set when restangularizeCollection is called with that param", function() { + var collection = Restangular.restangularizeCollection(null, [{}], 'accounts', true); + expect(collection[0].fromServer).toEqual(true); + + collection = Restangular.restangularizeCollection(null, [{}], 'accounts', false); + expect(collection[0].fromServer).toEqual(false); + + collection = Restangular.restangularizeCollection(null, [{}], 'accounts'); + expect(collection[0].fromServer).toEqual(false); + }); }); describe("restangularizePromiseIntercept", function() { From 57d3494dce9524b51ccc1c1e9b1123ffa6cf666c Mon Sep 17 00:00:00 2001 From: Matthew Davies Date: Fri, 17 Jun 2016 15:21:06 -0700 Subject: [PATCH 06/55] Updated README to include current versions of Angular See #1112 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 644d0222..460e8036 100644 --- a/README.md +++ b/README.md @@ -1284,7 +1284,7 @@ So, why not use it? If you've never heard of them, by using Restangular, you cou # Supported Angular versions -Restangular supports all angular versions including 1.0.X, 1.1.X and 1.2.X (1.2.4 being the current at the time) +Restangular supports all Angular versions from 1.0.X - 1.5.X Also, when using Restangular with version >= 1.1.4, in case you're using Restangular inside a callback not handled by Angular, you have to wrap the whole request with `$scope.apply` to make it work or you need to run one extra `$digest` manually. Check out https://github.com/mgonto/restangular/issues/71 From 4cf3e6ca85c56f0520dbca738cd260601c90af86 Mon Sep 17 00:00:00 2001 From: sschollmeyer Date: Mon, 20 Jun 2016 09:34:04 +0200 Subject: [PATCH 07/55] add another test to test restangularizeElement's fromServer too --- test/restangularSpec.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/restangularSpec.js b/test/restangularSpec.js index 5a307e7d..0bec3ea2 100644 --- a/test/restangularSpec.js +++ b/test/restangularSpec.js @@ -442,6 +442,17 @@ describe("Restangular", function() { expect(collection.getRestangularUrl()).toBe('/accounts'); }); + it("should have fromServer set when restangularizeElement is called with that param", function() { + var element = Restangular.restangularizeElement(null, {}, 'accounts', true); + expect(element.fromServer).toEqual(true); + + element = Restangular.restangularizeElement(null, {}, 'accounts', false); + expect(element.fromServer).toEqual(false); + + element = Restangular.restangularizeElement(null, {}, 'accounts'); + expect(element.fromServer).toEqual(false); + }); + it("should have fromServer set when restangularizeCollection is called with that param", function() { var collection = Restangular.restangularizeCollection(null, [{}], 'accounts', true); expect(collection[0].fromServer).toEqual(true); From b53f4b61a4168588a1df4589cb1bd6fb803d8190 Mon Sep 17 00:00:00 2001 From: Ivan Zubok Date: Wed, 31 Aug 2016 14:14:13 +0300 Subject: [PATCH 08/55] fix fromServer param while copying --- src/restangular.js | 4 ++-- test/restangularSpec.js | 25 ++++++++++++++++++++----- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/restangular.js b/src/restangular.js index 5362b84c..0bdb07a4 100644 --- a/src/restangular.js +++ b/src/restangular.js @@ -73,7 +73,7 @@ restangular.provider('Restangular', function() { object.setPlainByDefault = function(value) { config.plainByDefault = value === true ? true : false; return this; - } + }; config.withHttpValues = function(httpLocalConfig, obj) { return _.defaults(obj, httpLocalConfig, config.defaultHttpFields); @@ -962,7 +962,7 @@ restangular.provider('Restangular', function() { function copyRestangularizedElement(fromElement, toElement) { var copiedElement = angular.copy(fromElement, toElement); return restangularizeElem(copiedElement[config.restangularFields.parentResource], - copiedElement, copiedElement[config.restangularFields.route], true); + copiedElement, copiedElement[config.restangularFields.route], copiedElement[config.restangularFields.fromServer]); } function restangularizeElem(parent, element, route, fromServer, collection, reqParams) { diff --git a/test/restangularSpec.js b/test/restangularSpec.js index 218b40e0..944fbebc 100644 --- a/test/restangularSpec.js +++ b/test/restangularSpec.js @@ -27,11 +27,11 @@ describe("Restangular", function() { infoModel = { id: 0, text: "Some additional account information" - } + }; newAccount = {id: 44, user: "First User", amount: 45, transactions: []}; - messages = [{id: 23, name: "Gonto"}, {id: 45, name: "John"}] + messages = [{id: 23, name: "Gonto"}, {id: 45, name: "John"}]; accountsDoSomethingModel = { result: 1 }; @@ -332,7 +332,7 @@ describe("Restangular", function() { it("Should decorate element both on server and local by default", function() { Restangular.extendModel('accounts', function(account) { - account.extended = function() {return true;} + account.extended = function() {return true;}; return account; }); @@ -476,7 +476,7 @@ describe("Restangular", function() { it("Shouldn't be restangularized by default", function() { Restangular.extendModel('accounts', function(account) { - account.extended = function() {return true;} + account.extended = function() {return true;}; return account; }); @@ -824,6 +824,21 @@ describe("Restangular", function() { copiedAccount.getRestangularUrl(); // invoke the method we are spying on expect(that).toBe(copiedAccount); }); + + it("should copy an object and 'fromServer' param should be the same with the copied object", function() { + // with fromServer=true + restangularAccount1.get().then(function(account) { + var copiedAccount = Restangular.copy(account); + expect(account.fromServer).toEqual(copiedAccount.fromServer); + }); + + // with fromServer=false + var account = Restangular.one('accounts', 123), + copiedAccount = Restangular.copy(account); + expect(account.fromServer).toEqual(copiedAccount.fromServer); + + $httpBackend.flush(); + }); }); describe("getRestangularUrl", function() { @@ -1049,7 +1064,7 @@ describe("Restangular", function() { }); it('does not use the id for single nested resource GET', function() { - Restangular.one('accounts', 1).one('info', 0, true).get() + Restangular.one('accounts', 1).one('info', 0, true).get(); $httpBackend.expectGET('/accounts/1/info'); $httpBackend.flush(); }); From 82bd47e4199b8d828319ff37d4737d72a8763a46 Mon Sep 17 00:00:00 2001 From: Omar Meky Date: Tue, 15 Nov 2016 17:47:12 -0500 Subject: [PATCH 09/55] Update README --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5f6c37f8..3e617168 100644 --- a/README.md +++ b/README.md @@ -370,7 +370,7 @@ This is a hook. After each element has been "restangularized" (Added the new met **I favor the usage of `addElementTransformer` instead of `onElemRestangularized` whenever possible as the implementation is much cleaner.** -This callback is a function that has 3 parameters: +This callback is a function that has 4 parameters: * **elem**: The element that has just been restangularized. Can be a collection or a single element. * **isCollection**: Boolean indicating if this is a collection or a single element. @@ -459,7 +459,7 @@ Restangular.setErrorInterceptor(function(response, deferred, responseHandler) { #### setRestangularFields -Restangular required 3 fields for every "Restangularized" element. These are: +Restangular requires 7 fields for every "Restangularized" element. These are: * id: Id of the element. Default: id * route: Name of the route of this element. Default: route @@ -747,7 +747,7 @@ These are the methods that can be called on the Restangular object. ### Collection methods * **getList([queryParams, headers]): Gets itself again (Remember this is a collection)**. -* **get([id]): Gets one item from the collection by id**. +* **get(id): Gets one item from the collection by id**. * **post(elementToPost, [queryParams, headers])**: Creates a new element of this collection. * **head([queryParams, headers])**: Does a HEAD * **trace: ([queryParams, headers])**: Does a TRACE @@ -792,7 +792,7 @@ All custom methods have an alias where you replace `custom` by `do`. For example **[Back to top](#table-of-contents)** ## Copying elements -Before modifying an object, we sometimes want to copy it and then modify the copied object. We can't use `angular.copy` for this because it'll not change the `this` binded in the functions we add to the object. In this cases, you must use `Restangular.copy(fromElement)`. +Before modifying an object, we sometimes want to copy it and then modify the copied object. We can't use `angular.copy` for this because it'll not change the `this` bound in the functions we add to the object. In this cases, you must use `Restangular.copy(fromElement)`. **[Back to top](#table-of-contents)** From c17df47a40b172a988e7f49e48fad4135ce9d7fd Mon Sep 17 00:00:00 2001 From: Matthew Davies Date: Sat, 18 Jun 2016 10:27:58 -0700 Subject: [PATCH 10/55] docs(contributing): Rename CONTRIBUTE.md to CONTRIBUTING.md in accordance with GitHub's spec Add section to address opening issues Add issue template with this information --- .github/ISSUE_TEMPLATE.md | 4 ++++ CONTRIBUTE.md | 31 ----------------------------- CONTRIBUTING.md | 42 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 31 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE.md delete mode 100644 CONTRIBUTE.md create mode 100644 CONTRIBUTING.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..c43fb629 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,4 @@ +To open an issue, please keep in mind a few important things. First, take a look at the README docs and make sure that your question isn't already answered in the documentation. In particular, review [the configuration methods](https://github.com/mgonto/restangular#configuring-restangular) and [methods description](https://github.com/mgonto/restangular#methods-description). Then make sure you search the issues list to see if there's already an issue open that solves your problem. Then, once you've determined that your issue isn't a duplicate, here a couple guidelines to opening an issue that will be addressed easily and quickly: + +- Please make sure your issue is written in a clear and succint manner, so that all the details of your issue are easy to understand and fully explained. Also, please make sure enclose your code in [fenced blocks](https://help.github.com/articles/creating-and-highlighting-code-blocks/) for readability. +- Make sure your issue includes a live Plunker (fork [this Plunker](http://plnkr.co/edit/26Heuv5F6hUgxpNWNTee?p=info)), and all relevant code samples (as well as information about server responses, if relevant) diff --git a/CONTRIBUTE.md b/CONTRIBUTE.md deleted file mode 100644 index 8ccfef3b..00000000 --- a/CONTRIBUTE.md +++ /dev/null @@ -1,31 +0,0 @@ -##Install env - -In order to contribute just git clone the repository and then run: - -``` -git clone git@github.com:mgonto/restangular.git -cd restangular -npm install grunt-cli --global -npm install -``` - -Be sure to have PhantomJS installed as Karma tests use it. Otherwise, in Mac just run - -``` -brew install phantomjs -``` - -All changes must be done in src/restangular.js - -##Branching - -Please submit a Pull Request or create issues for anything you want :). If your code is super small, nobody will blame -you for doing it from master to master. Otherwise, please try to create a branch `features/[name of your awesome feature]`. - -##Testing and styling - -Before submiting any PR, you should run `grunt test` to validate your didn't break anything. If you just added a new -feature, please consider also adding tests for it. And when you're done with your code, run `grunt jshint` to check -if you code follow the same simple coding design as the rest of the project. - -Thanks! diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..9722aac4 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,42 @@ +#Issues + +## Opening an Issue + +To open an issue, please keep in mind a few important things. First, take a look at the README docs and make sure that your question isn't already answered in the documentation. In particular, review [the configuration methods](https://github.com/mgonto/restangular#configuring-restangular) and [methods description](https://github.com/mgonto/restangular#methods-description). Then make sure you search the issues list to see if there's already an issue open that solves your problem. Then, once you've determined that your issue isn't a duplicate, here a couple guidelines to opening an issue that will be addressed easily and quickly: + +- Please make sure your issue is written in a clear and succint manner, so that all the details of your issue are easy to understand and fully explained. Also, please make sure enclose your code in [fenced blocks](https://help.github.com/articles/creating-and-highlighting-code-blocks/) for readability. +- Make sure your issue includes a live Plunker (fork [this Plunker](http://plnkr.co/edit/26Heuv5F6hUgxpNWNTee?p=info)), and all relevant code samples (as well as information about server responses, if relevant) + +# PRs + +##Install env + +In order to contribute just git clone the repository and then run: + +``` +git clone git@github.com:mgonto/restangular.git +cd restangular +npm install grunt-cli --global +npm install +``` + +Be sure to have PhantomJS installed as Karma tests use it. Otherwise, in Mac just run + +``` +brew install phantomjs +``` + +All changes must be done in src/restangular.js + +##Branching + +Please submit a Pull Request or create issues for anything you want :). If your code is super small, nobody will blame +you for doing it from master to master. Otherwise, please try to create a branch `features/[name of your awesome feature]`. + +##Testing and styling + +Before submitting any PR, you should run `grunt test` to validate your didn't break anything. If you just added a new +feature, please also add tests for it. And when you're done with your code, run `grunt jshint` to check +if you code follow the same simple coding design as the rest of the project. + +Thanks! From 1a585f3e9c933fb25714007428d490096e88e229 Mon Sep 17 00:00:00 2001 From: Kevin Kirsche Date: Mon, 8 Jun 2015 21:48:01 -0400 Subject: [PATCH 11/55] Remove moot `version` property from bower.json Per bower/bower.json-spec@a325da3 --- bower.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bower.json b/bower.json index cf036a0b..2d7bd12b 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,5 @@ { "name": "restangular", - "version": "1.5.2", "main": "./dist/restangular.js", "description": "Restful Resources service for AngularJS apps", "repository": { @@ -16,4 +15,4 @@ "components", "lib" ] -} \ No newline at end of file +} From e8f7295dbd0887e18c0a7a0745926213de660c57 Mon Sep 17 00:00:00 2001 From: madflow Date: Sun, 20 Nov 2016 09:29:17 +0100 Subject: [PATCH 12/55] feat(service): add withHttpConfig to objects created with .service * Replacement for PR #1068 (#1392) * First test for #1068 * Added doc --- README.md | 18 +++++++++++++++++- src/restangular.js | 1 + test/restangularSpec.js | 9 +++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 466dc9e2..6d8fec80 100644 --- a/README.md +++ b/README.md @@ -672,7 +672,8 @@ app.controller('MainCtrl', function(Restangular, BingRestangular) { ### Decoupled Restangular Service -There're some times where you want to use Restangular but you don't want to expose Restangular object anywhere. For those cases, you can actually use the `service` feature of Restangular. +There're some times where you want to use Restangular but you don't want to expose Restangular object anywhere. +For those cases, you can actually use the `service` feature of Restangular. Let's see how it works: @@ -695,6 +696,21 @@ Users.getList().then(function(users) { }) ```` +You can also use ```withHttpConfig``` on objects created by ```Restangular.service```. + +```js +var personService = Restangular.service('person'); +var entity = personService.withHttpConfig({transformRequest: function(data) { + data.fullname = data.firstname + ' ' + data.lastname; + return JSON.stringify(data); +}}).post({ + "lastname": "Mueller", + "firstname": "Gerd" +}).then(function(resp) { + console.log(resp); +}); +``` + We can also use Nested RESTful resources with this: ````js diff --git a/src/restangular.js b/src/restangular.js index 0bdb07a4..a5a0670a 100644 --- a/src/restangular.js +++ b/src/restangular.js @@ -1335,6 +1335,7 @@ restangular.provider('Restangular', function() { serv.one = _.bind(one, (parent || service), parent, route); serv.post = _.bind(collection.post, collection); serv.getList = _.bind(collection.getList, collection); + serv.withHttpConfig = _.bind(collection.withHttpConfig, collection); for (var prop in collection) { if (collection.hasOwnProperty(prop) && _.isFunction(collection[prop]) && !_.includes(knownCollectionMethods, prop)) { diff --git a/test/restangularSpec.js b/test/restangularSpec.js index 944fbebc..cea505f9 100644 --- a/test/restangularSpec.js +++ b/test/restangularSpec.js @@ -709,6 +709,15 @@ describe("Restangular", function() { $httpBackend.expectGET('/accounts/do-something'); $httpBackend.flush(); }); + + it("should provide a one-off $http configuration method", function() { + var Accounts = Restangular.service('accounts'); + Accounts.withHttpConfig({transformRequest: angular.identity}); + Accounts.post(newAccount); + $httpBackend.expectPOST('/accounts'); + $httpBackend.flush(); + }); + }); describe("ONE", function() { From 11fb47521b48487c8907ec387284b04bf8084e79 Mon Sep 17 00:00:00 2001 From: Jess Bowers Date: Sun, 20 Nov 2016 03:54:18 -0500 Subject: [PATCH 13/55] fix(tests): add more realistic POST response for accounts, with id (#943) --- test/restangularSpec.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/restangularSpec.js b/test/restangularSpec.js index cea505f9..58a51e65 100644 --- a/test/restangularSpec.js +++ b/test/restangularSpec.js @@ -3,7 +3,7 @@ describe("Restangular", function() { var Restangular, $httpBackend; var accountsModel, restangularAccounts, restangularAccount0, restangularAccount1; var accountsHalModel; - var messages, newAccount; + var messages, newAccount, nextAccountId // Load required modules beforeEach(angular.mock.module("restangular")); @@ -15,6 +15,7 @@ describe("Restangular", function() { {id: 0, user: "Martin ", amount: 42, transactions: []}, {id: 1, user: "Paul", amount: 3.1416, transactions: [{from: "Martin", amount: 3, id: 0}, {from: "Anonymous", amount: 0.1416, id:1}]} ]; + nextAccountId = 22; // HAL model (http://stateless.co/hal_specification.html) accountsHalModel = [ @@ -29,7 +30,7 @@ describe("Restangular", function() { id: 0, text: "Some additional account information" }; - newAccount = {id: 44, user: "First User", amount: 45, transactions: []}; + newAccount = {user: "First User", amount: 45, transactions: []}; messages = [{id: 23, name: "Gonto"}, {id: 45, name: "John"}]; @@ -72,6 +73,7 @@ describe("Restangular", function() { $httpBackend.whenPOST("/accounts").respond(function(method, url, data, headers) { var newData = angular.fromJson(data); newData.fromServer = true; + newData.id = nextAccountId; return [201, JSON.stringify(newData), ""]; }); @@ -581,7 +583,8 @@ describe("Restangular", function() { it("post() should add a new item with data and return the data from the server", function() { restangularAccounts.post(newAccount).then(function(added) { expect(added.fromServer).toEqual(true); - expect(added.id).toEqual(newAccount.id); + expect(added.id).toEqual(nextAccountId); + expect(added.user).toEqual(newAccount.user); }); $httpBackend.expectPOST('/accounts'); @@ -591,7 +594,7 @@ describe("Restangular", function() { it("Doing a post and then other operation (delete) should call right URLs", function() { restangularAccounts.post(newAccount).then(function(added) { added.remove(); - $httpBackend.expectDELETE('/accounts/44').respond(201, ''); + $httpBackend.expectDELETE('/accounts/'+nextAccountId).respond(201, ''); }); $httpBackend.flush(); From 775dca3a5fafa9cbbce19098d2fb0a7b10d5d1f9 Mon Sep 17 00:00:00 2001 From: Andrey Koleshko Date: Thu, 29 Jan 2015 23:31:58 +0300 Subject: [PATCH 14/55] Signatures of restangularize methods --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6d8fec80..8cd4df23 100644 --- a/README.md +++ b/README.md @@ -672,7 +672,7 @@ app.controller('MainCtrl', function(Restangular, BingRestangular) { ### Decoupled Restangular Service -There're some times where you want to use Restangular but you don't want to expose Restangular object anywhere. +There're some times where you want to use Restangular but you don't want to expose Restangular object anywhere. For those cases, you can actually use the `service` feature of Restangular. Let's see how it works: @@ -732,8 +732,8 @@ These are the methods that can be called on the Restangular object. * **oneUrl(route, url)**: This will create a new Restangular object that is just a pointer to one element with the specified URL. * **allUrl(route, url)**: This creates a Restangular object that is just a pointer to a list at the specified URL. * **copy(fromElement)**: This will create a copy of the from element so that we can modify the copied one. -* **restangularizeElement(parent, element, route, queryParams)**: Restangularizes a new element -* **restangularizeCollection(parent, element, route, queryParams)**: Restangularizes a new collection +* **restangularizeElement(parent, element, route, fromServer, collection, reqParams)**: Restangularizes a new element +* **restangularizeCollection(parent, element, route, fromServer, reqParams)**: Restangularizes a new collection **[Back to top](#table-of-contents)** From cafeae8f3302b0295a05c3479b2112f4e50b7a46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Sun, 20 Nov 2016 13:35:03 +0200 Subject: [PATCH 15/55] chore(tests): fix lint problems --- test/restangularSpec.js | 480 ++++++++++++++++++++-------------------- 1 file changed, 240 insertions(+), 240 deletions(-) diff --git a/test/restangularSpec.js b/test/restangularSpec.js index 4bd8916a..aaaf1b1f 100644 --- a/test/restangularSpec.js +++ b/test/restangularSpec.js @@ -1,112 +1,112 @@ -describe("Restangular", function() { +describe('Restangular', function() { // API var Restangular, $httpBackend; var accountsModel, restangularAccounts, restangularAccount0, restangularAccount1; var accountsHalModel; - var messages, newAccount, nextAccountId + var messages, newAccount, nextAccountId; // Load required modules - beforeEach(angular.mock.module("restangular")); + beforeEach(angular.mock.module('restangular')); // Init HTTP mock backend and Restangular resources beforeEach(inject(function($injector) { // Model accountsModel = [ - {id: 0, user: "Martin ", amount: 42, transactions: []}, - {id: 1, user: "Paul", amount: 3.1416, transactions: [{from: "Martin", amount: 3, id: 0}, {from: "Anonymous", amount: 0.1416, id:1}]} + {id: 0, user: 'Martin ', amount: 42, transactions: []}, + {id: 1, user: 'Paul', amount: 3.1416, transactions: [{from: 'Martin', amount: 3, id: 0}, {from: 'Anonymous', amount: 0.1416, id:1}]} ]; nextAccountId = 22; // HAL model (http://stateless.co/hal_specification.html) accountsHalModel = [ - {id: 0, user: "Martin", amount: 42, transaction: [], _links: {self: "/accountsHAL/martin"}}, - {id: 1, user: "Paul", amount: 3.1416, transaction: [ - {from: "Martin", amount: 3, id: 0, _links: {self: "/accountsHAL/paul/transactions/0"}}, - {from: "Anonymous", amount: 0.1416, id: 1, _links: {self: "/accountsHAL/paul/transactions/1"}} - ], _links: {self: "/accountsHAL/paul"}} + {id: 0, user: 'Martin', amount: 42, transaction: [], _links: {self: '/accountsHAL/martin'}}, + {id: 1, user: 'Paul', amount: 3.1416, transaction: [ + {from: 'Martin', amount: 3, id: 0, _links: {self: '/accountsHAL/paul/transactions/0'}}, + {from: 'Anonymous', amount: 0.1416, id: 1, _links: {self: '/accountsHAL/paul/transactions/1'}} + ], _links: {self: '/accountsHAL/paul'}} ]; infoModel = { - id: 0, text: "Some additional account information" + id: 0, text: 'Some additional account information' }; - newAccount = {user: "First User", amount: 45, transactions: []}; + newAccount = {user: 'First User', amount: 45, transactions: []}; - messages = [{id: 23, name: "Gonto"}, {id: 45, name: "John"}]; + messages = [{id: 23, name: 'Gonto'}, {id: 45, name: 'John'}]; accountsDoSomethingModel = { result: 1 }; - $httpBackend = $injector.get("$httpBackend"); - - $httpBackend.when("HEAD", "/accounts").respond(); - $httpBackend.when("TRACE", "/accounts").respond(); - $httpBackend.when("OPTIONS", "/accounts").respond(); - - $httpBackend.whenGET("/accounts").respond(accountsModel); - $httpBackend.whenGET("/accounts/do-something").respond(accountsDoSomethingModel); - $httpBackend.whenJSONP("/accounts").respond(accountsModel); - $httpBackend.whenGET("/accounts/0,1").respond(accountsModel); - $httpBackend.whenGET("/accounts/messages").respond(messages); - $httpBackend.whenGET("/accounts/1/message").respond(messages[0]); - $httpBackend.whenGET("/accounts/1/messages").respond(messages); - $httpBackend.whenGET("/accounts/0").respond(accountsModel[0]); - $httpBackend.whenGET("/accounts/1").respond(accountsModel[1]); - $httpBackend.whenJSONP("/accounts/1").respond(accountsModel[1]); - $httpBackend.whenGET("/accounts/1/transactions").respond(accountsModel[1].transactions); - $httpBackend.whenGET("/accounts/1/transactions/1").respond(accountsModel[1].transactions[1]); - - $httpBackend.whenGET("/info").respond(infoModel); - $httpBackend.whenGET("/accounts/1/info").respond(infoModel); - $httpBackend.whenPUT("/info").respond(function(method, url, data) { - return [200, data, ""]; - }); - - $httpBackend.whenGET("/accountsHAL").respond(accountsHalModel); - $httpBackend.whenPUT("/accountsHAL/martin").respond(function(method, url, data) { + $httpBackend = $injector.get('$httpBackend'); + + $httpBackend.when('HEAD', '/accounts').respond(); + $httpBackend.when('TRACE', '/accounts').respond(); + $httpBackend.when('OPTIONS', '/accounts').respond(); + + $httpBackend.whenGET('/accounts').respond(accountsModel); + $httpBackend.whenGET('/accounts/do-something').respond(accountsDoSomethingModel); + $httpBackend.whenJSONP('/accounts').respond(accountsModel); + $httpBackend.whenGET('/accounts/0,1').respond(accountsModel); + $httpBackend.whenGET('/accounts/messages').respond(messages); + $httpBackend.whenGET('/accounts/1/message').respond(messages[0]); + $httpBackend.whenGET('/accounts/1/messages').respond(messages); + $httpBackend.whenGET('/accounts/0').respond(accountsModel[0]); + $httpBackend.whenGET('/accounts/1').respond(accountsModel[1]); + $httpBackend.whenJSONP('/accounts/1').respond(accountsModel[1]); + $httpBackend.whenGET('/accounts/1/transactions').respond(accountsModel[1].transactions); + $httpBackend.whenGET('/accounts/1/transactions/1').respond(accountsModel[1].transactions[1]); + + $httpBackend.whenGET('/info').respond(infoModel); + $httpBackend.whenGET('/accounts/1/info').respond(infoModel); + $httpBackend.whenPUT('/info').respond(function(method, url, data) { + return [200, data, '']; + }); + + $httpBackend.whenGET('/accountsHAL').respond(accountsHalModel); + $httpBackend.whenPUT('/accountsHAL/martin').respond(function(method, url, data) { accountsHalModel[0] = angular.fromJson(data); - return [200, data, ""]; + return [200, data, '']; }); // Full URL $httpBackend.whenGET('http://accounts.com/all').respond(accountsModel); - $httpBackend.whenPOST("/accounts").respond(function(method, url, data, headers) { + $httpBackend.whenPOST('/accounts').respond(function(method, url, data, headers) { var newData = angular.fromJson(data); newData.fromServer = true; newData.id = nextAccountId; - return [201, JSON.stringify(newData), ""]; + return [201, JSON.stringify(newData), '']; }); - $httpBackend.whenPOST("/accounts/1/transactions").respond(function(method, url, data, headers) { - return [201, "", ""]; + $httpBackend.whenPOST('/accounts/1/transactions').respond(function(method, url, data, headers) { + return [201, '', '']; }); - $httpBackend.whenDELETE("/accounts/1/transactions/1").respond(function(method, url, data, headers) { - return [200, "", ""]; + $httpBackend.whenDELETE('/accounts/1/transactions/1').respond(function(method, url, data, headers) { + return [200, '', '']; }); - $httpBackend.whenDELETE("/accounts/1").respond(function(method, url, data, headers) { - return [200, "", ""]; + $httpBackend.whenDELETE('/accounts/1').respond(function(method, url, data, headers) { + return [200, '', '']; }); - $httpBackend.whenPOST("/accounts/1").respond(function(method, url, data, headers) { - return [200, "", ""]; + $httpBackend.whenPOST('/accounts/1').respond(function(method, url, data, headers) { + return [200, '', '']; }); - $httpBackend.whenPUT("/accounts/1").respond(function(method, url, data, headers) { + $httpBackend.whenPUT('/accounts/1').respond(function(method, url, data, headers) { accountsModel[1] = angular.fromJson(data); - return [201, data, ""]; + return [201, data, '']; }); - $httpBackend.whenGET("/error").respond(function() { - return [500, {}, ""]; + $httpBackend.whenGET('/error').respond(function() { + return [500, {}, '']; }); - $httpBackend.whenPOST("/customs").respond(function(method, url, data, headers) { + $httpBackend.whenPOST('/customs').respond(function(method, url, data, headers) { if (JSON.parse(data).one) { - return [201, "", ""]; + return [201, '', '']; } else { - return [400, "", ""]; + return [400, '', '']; } }); @@ -114,32 +114,32 @@ describe("Restangular", function() { // e.g.: /error/404 returns 404 Not Found var urlRegex = /\/error\/(\d{3})/; $httpBackend.whenGET(urlRegex).respond(function(method, url, data, headers) { - return [url.match(urlRegex)[1], {}, ""]; + return [url.match(urlRegex)[1], {}, '']; }); - Restangular = $injector.get("Restangular"); - restangularAccounts = Restangular.all("accounts"); - restangularAccount0 = Restangular.one("accounts", 0); - restangularAccount1 = Restangular.one("accounts", 1); + Restangular = $injector.get('Restangular'); + restangularAccounts = Restangular.all('accounts'); + restangularAccount0 = Restangular.one('accounts', 0); + restangularAccount1 = Restangular.one('accounts', 1); // Another API for testing customers = [ { id: 0, - name: "Alice", + name: 'Alice', status: 'active', credit: 4000.0 }, { id: 1, - name: "Bob", + name: 'Bob', status: 'active', credit: 4000.0 }, { id: 2, - name: "Carl", + name: 'Carl', status: 'active', credit: 4000.0 } @@ -147,8 +147,8 @@ describe("Restangular", function() { publications = [ { id: 1, - title: "Sample", - content: "Rich data", + title: 'Sample', + content: 'Rich data', tags: [ 'science', 'chemistry' @@ -157,24 +157,24 @@ describe("Restangular", function() { ]; newCustomer = { id: 3, - name: "New", + name: 'New', status: 'active', credit: 4000.0 }; - $httpBackend.whenGET("/customers/").respond(customers); - $httpBackend.whenGET("http://localhost:8080/customers/").respond(customers); - $httpBackend.whenGET("api.new.domain/customers/").respond(customers); - $httpBackend.whenGET("/customers/?active=true").respond(customers); - $httpBackend.whenGET("/customers/publications/?tags=chemistry").respond(publications); - $httpBackend.whenPUT("/customers/0").respond(function (method, url, data) { + $httpBackend.whenGET('/customers/').respond(customers); + $httpBackend.whenGET('http://localhost:8080/customers/').respond(customers); + $httpBackend.whenGET('api.new.domain/customers/').respond(customers); + $httpBackend.whenGET('/customers/?active=true').respond(customers); + $httpBackend.whenGET('/customers/publications/?tags=chemistry').respond(publications); + $httpBackend.whenPUT('/customers/0').respond(function (method, url, data) { customers[0] = angular.fromJson(data); - return [200, data, ""]; + return [200, data, '']; }); - $httpBackend.whenPOST("/customers/").respond(function (method, url, data, headers) { + $httpBackend.whenPOST('/customers/').respond(function (method, url, data, headers) { var newData = angular.fromJson(data); newData.fromServer = true; - return [201, JSON.stringify(newData), ""]; + return [201, JSON.stringify(newData), '']; }); })); @@ -184,8 +184,8 @@ describe("Restangular", function() { $httpBackend.verifyNoOutstandingRequest(); }); - describe("Interceptors", function() { - it("Should add multiple request and response interceptors", function() { + describe('Interceptors', function() { + it('Should add multiple request and response interceptors', function() { Restangular.addRequestInterceptor(function(elem) { var elemCopy = angular.copy(elem); elemCopy.firstRequestInterceptor = true; @@ -220,17 +220,17 @@ describe("Restangular", function() { return elemCopy; }); - $httpBackend.whenPOST("/list").respond(function(method, url, data, headers) { + $httpBackend.whenPOST('/list').respond(function(method, url, data, headers) { var elem = angular.fromJson(data); expect(elem.firstRequestInterceptor).toBeDefined(); expect(elem.secondRequestInterceptor).toBeDefined(); expect(elem.thirdRequestInterceptor).toBeDefined(); - return [200, elem, ""]; + return [200, elem, '']; }); $httpBackend.expectPOST('/list'); - Restangular.all('list').post({name: "Gonto"}).then(function(elem) { + Restangular.all('list').post({name: 'Gonto'}).then(function(elem) { expect(elem.firstResponseInterceptor).toBeDefined(); expect(elem.secondResponseInterceptor).toBeDefined(); }); @@ -238,8 +238,8 @@ describe("Restangular", function() { $httpBackend.flush(); }); - it("Should add multiple error interceptors", function() { - $httpBackend.expectGET("/error"); + it('Should add multiple error interceptors', function() { + $httpBackend.expectGET('/error'); var CallbackManager = function() {}; CallbackManager.successCallback = function() { @@ -253,22 +253,22 @@ describe("Restangular", function() { CallbackManager.firstErrorInterceptor = function() {}; CallbackManager.secondErrorInterceptor = function() {}; - spyOn(CallbackManager, "successCallback").andCallThrough(); - spyOn(CallbackManager, "firstErrorInterceptor").andCallThrough(); - spyOn(CallbackManager, "secondErrorInterceptor").andCallThrough(); + spyOn(CallbackManager, 'successCallback').andCallThrough(); + spyOn(CallbackManager, 'firstErrorInterceptor').andCallThrough(); + spyOn(CallbackManager, 'secondErrorInterceptor').andCallThrough(); Restangular.addErrorInterceptor(CallbackManager.firstErrorInterceptor); Restangular.addErrorInterceptor(CallbackManager.secondErrorInterceptor); - Restangular.all("error").getList() + Restangular.all('error').getList() .then(CallbackManager.successCallback) .catch(CallbackManager.errorCallback); $httpBackend.flush(); }); - it("Should add multiple error interceptors but don't reject the promise if one of them returns false", function() { - $httpBackend.expectGET("/error"); + it('Should add multiple error interceptors but don\'t reject the promise if one of them returns false', function() { + $httpBackend.expectGET('/error'); var CallbackManager = function() {}; CallbackManager.successCallback = function() { @@ -285,21 +285,21 @@ describe("Restangular", function() { return false; // prevent promise to be rejected }; - spyOn(CallbackManager, "successCallback").andCallThrough(); - spyOn(CallbackManager, "errorCallback").andCallThrough(); + spyOn(CallbackManager, 'successCallback').andCallThrough(); + spyOn(CallbackManager, 'errorCallback').andCallThrough(); Restangular.addErrorInterceptor(CallbackManager.firstErrorInterceptor); Restangular.addErrorInterceptor(CallbackManager.secondErrorInterceptor); - Restangular.all("error").getList() + Restangular.all('error').getList() .then(CallbackManager.successCallback) .catch(CallbackManager.errorCallback); $httpBackend.flush(); }); - it("Should add multiple error interceptors for a single get too", function() { - $httpBackend.expectGET("/error/404"); + it('Should add multiple error interceptors for a single get too', function() { + $httpBackend.expectGET('/error/404'); var CallbackManager = function() {}; CallbackManager.successCallback = function() { @@ -315,14 +315,14 @@ describe("Restangular", function() { }; CallbackManager.secondErrorInterceptor = function() {}; - spyOn(CallbackManager, "successCallback").andCallThrough(); - spyOn(CallbackManager, "firstErrorInterceptor").andCallThrough(); - spyOn(CallbackManager, "secondErrorInterceptor").andCallThrough(); + spyOn(CallbackManager, 'successCallback').andCallThrough(); + spyOn(CallbackManager, 'firstErrorInterceptor').andCallThrough(); + spyOn(CallbackManager, 'secondErrorInterceptor').andCallThrough(); Restangular.addErrorInterceptor(CallbackManager.firstErrorInterceptor); Restangular.addErrorInterceptor(CallbackManager.secondErrorInterceptor); - Restangular.one("error", 404).get() + Restangular.one('error', 404).get() .then(CallbackManager.successCallback) .catch(CallbackManager.errorCallback); @@ -330,8 +330,8 @@ describe("Restangular", function() { }); }); - describe("Transformers", function() { - it("Should decorate element both on server and local by default", function() { + describe('Transformers', function() { + it('Should decorate element both on server and local by default', function() { Restangular.extendModel('accounts', function(account) { account.extended = function() {return true;}; @@ -351,8 +351,8 @@ describe("Restangular", function() { }); - describe("With Suffix", function() { - it("shouldn't add suffix to getRestangularUrl", function() { + describe('With Suffix', function() { + it('shouldn\'t add suffix to getRestangularUrl', function() { var suffixRestangular = Restangular.withConfig(function(RestangularConfigurer) { RestangularConfigurer.setRequestSuffix('.json'); }); @@ -361,7 +361,7 @@ describe("Restangular", function() { expect(collection.one('1').getRestangularUrl()).toBe('/accounts/1'); }); - it("should add suffix to getRequestedUrl", function() { + it('should add suffix to getRequestedUrl', function() { var suffixRestangular = Restangular.withConfig(function(RestangularConfigurer) { RestangularConfigurer.setRequestSuffix('.json'); }); @@ -370,7 +370,7 @@ describe("Restangular", function() { expect(collection.one('1').getRequestedUrl()).toBe('/accounts/1.json'); }); - it("should add suffix to request", function() { + it('should add suffix to request', function() { var suffixRestangular = Restangular.withConfig(function(RestangularConfigurer) { RestangularConfigurer.setRequestSuffix('.json'); }); @@ -382,7 +382,7 @@ describe("Restangular", function() { $httpBackend.flush(); }); - it("shouldn't add suffix to allUrl", function() { + it('shouldn\'t add suffix to allUrl', function() { var suffixRestangular = Restangular.withConfig(function(RestangularConfigurer) { RestangularConfigurer.setRequestSuffix('.json'); }); @@ -392,8 +392,8 @@ describe("Restangular", function() { }); }); - describe("JSONp", function() { - it("should work for get", function() { + describe('JSONp', function() { + it('should work for get', function() { Restangular.setJsonp(true); Restangular.one('accounts', 1).get(); @@ -401,7 +401,7 @@ describe("Restangular", function() { $httpBackend.flush(); }); - it("should work for getList", function() { + it('should work for getList', function() { Restangular.setJsonp(true); Restangular.all('accounts').getList(); @@ -409,9 +409,9 @@ describe("Restangular", function() { $httpBackend.flush(); }); - it("shouldn't override post", function() { + it('shouldn\'t override post', function() { Restangular.setJsonp(true); - restangularAccounts.post({id: 2, user: "Someone"}); + restangularAccounts.post({id: 2, user: 'Someone'}); $httpBackend.expectPOST('/accounts').respond(201, ''); $httpBackend.flush(); @@ -420,8 +420,8 @@ describe("Restangular", function() { }); - describe("Local data", function() { - it("Should restangularize a collection OK", function() { + describe('Local data', function() { + it('Should restangularize a collection OK', function() { var collection = angular.copy(accountsModel); Restangular.restangularizeCollection(null, collection, 'accounts'); @@ -434,7 +434,7 @@ describe("Restangular", function() { }); - it("Should restangularize a function with arguments OK", function() { + it('Should restangularize a function with arguments OK', function() { var collection = function(a, b) { }; Restangular.restangularizeCollection(null, collection, 'accounts'); @@ -444,7 +444,7 @@ describe("Restangular", function() { expect(collection.getRestangularUrl()).toBe('/accounts'); }); - it("should have fromServer set when restangularizeElement is called with that param", function() { + it('should have fromServer set when restangularizeElement is called with that param', function() { var element = Restangular.restangularizeElement(null, {}, 'accounts', true); expect(element.fromServer).toEqual(true); @@ -455,7 +455,7 @@ describe("Restangular", function() { expect(element.fromServer).toEqual(false); }); - it("should have fromServer set when restangularizeCollection is called with that param", function() { + it('should have fromServer set when restangularizeCollection is called with that param', function() { var collection = Restangular.restangularizeCollection(null, [{}], 'accounts', true); expect(collection[0].fromServer).toEqual(true); @@ -467,8 +467,8 @@ describe("Restangular", function() { }); }); - describe("restangularizePromiseIntercept", function() { - it("should be invoked by restangularizePromise", function() { + describe('restangularizePromiseIntercept', function() { + it('should be invoked by restangularizePromise', function() { var calledWithPromise; Restangular.setRestangularizePromiseInterceptor(function(promise) { @@ -486,8 +486,8 @@ describe("Restangular", function() { }); }); - describe("$object", function() { - it("Should work for single get", function() { + describe('$object', function() { + it('Should work for single get', function() { var promise = Restangular.one('accounts', 1).get(); var obj = promise.$object; expect(obj).toBeDefined(); @@ -498,7 +498,7 @@ describe("Restangular", function() { expect(obj.amount).toEqual(3.1416); }); - it("Shouldn't be restangularized by default", function() { + it('Shouldn\'t be restangularized by default', function() { Restangular.extendModel('accounts', function(account) { account.extended = function() {return true;}; return account; @@ -512,7 +512,7 @@ describe("Restangular", function() { $httpBackend.flush(); }); - it("Should work for single get", function() { + it('Should work for single get', function() { var promise = Restangular.all('accounts').getList(); var list = promise.$object; expect(list).toBeDefined(); @@ -525,8 +525,8 @@ describe("Restangular", function() { }); }); - describe("ALL", function() { - it("getList() should return an array of items", function() { + describe('ALL', function() { + it('getList() should return an array of items', function() { restangularAccounts.getList().then(function(accounts) { expect(Restangular.stripRestangular(accounts)).toEqual(Restangular.stripRestangular(accountsModel)); }); @@ -534,23 +534,23 @@ describe("Restangular", function() { $httpBackend.flush(); }); - it("several getList() should return an array of items", function() { + it('several getList() should return an array of items', function() { $httpBackend.expectGET('/accounts/0,1'); - Restangular.several("accounts", 0, 1).getList().then(function(accounts) { + Restangular.several('accounts', 0, 1).getList().then(function(accounts) { expect(Restangular.stripRestangular(accounts)).toEqual(Restangular.stripRestangular(accountsModel)); }); $httpBackend.flush(); }); - it("several remove() should work", function() { - $httpBackend.expectDELETE('/accounts/0,1').respond([200, "", ""]); - Restangular.several("accounts", 0, 1).remove(); + it('several remove() should work', function() { + $httpBackend.expectDELETE('/accounts/0,1').respond([200, '', '']); + Restangular.several('accounts', 0, 1).remove(); $httpBackend.flush(); }); - it("get(id) should return the item with given id", function() { + it('get(id) should return the item with given id', function() { restangularAccounts.get(0).then(function(account) { expect(Restangular.stripRestangular(account)).toEqual(Restangular.stripRestangular(accountsModel[0])); }); @@ -564,16 +564,16 @@ describe("Restangular", function() { $httpBackend.flush(); }); - it("Custom GET methods should work", function() { - restangularAccounts.customGETLIST("messages").then(function(msgs) { + it('Custom GET methods should work', function() { + restangularAccounts.customGETLIST('messages').then(function(msgs) { expect(Restangular.stripRestangular(msgs)).toEqual(Restangular.stripRestangular(messages)); }); $httpBackend.flush(); }); - it("post() should add a new item", function() { - restangularAccounts.post({id: 2, user: "Someone"}).then(function() { + it('post() should add a new item', function() { + restangularAccounts.post({id: 2, user: 'Someone'}).then(function() { expect(accountsModel.length).toEqual(2); }); @@ -581,8 +581,8 @@ describe("Restangular", function() { $httpBackend.flush(); }); - it("customPOST() should add a new item", function() { - restangularAccounts.customPOST({id: 2, user: "Someone"}).then(function() { + it('customPOST() should add a new item', function() { + restangularAccounts.customPOST({id: 2, user: 'Someone'}).then(function() { expect(accountsModel.length).toEqual(2); }); @@ -590,19 +590,19 @@ describe("Restangular", function() { $httpBackend.flush(); }); - it("post() should work with arrays", function() { - Restangular.all('places').post([{name: "Gonto"}, {name: 'John'}]).then(function(value) { + it('post() should work with arrays', function() { + Restangular.all('places').post([{name: 'Gonto'}, {name: 'John'}]).then(function(value) { expect(value.length).toEqual(2); }); $httpBackend.expectPOST('/places').respond(function(method, url, data, headers) { - return [201, angular.fromJson(data), ""]; + return [201, angular.fromJson(data), '']; }); $httpBackend.flush(); }); - it("post() should add a new item with data and return the data from the server", function() { + it('post() should add a new item with data and return the data from the server', function() { restangularAccounts.post(newAccount).then(function(added) { expect(added.fromServer).toEqual(true); expect(added.id).toEqual(nextAccountId); @@ -613,7 +613,7 @@ describe("Restangular", function() { $httpBackend.flush(); }); - it("Doing a post and then other operation (delete) should call right URLs", function() { + it('Doing a post and then other operation (delete) should call right URLs', function() { restangularAccounts.post(newAccount).then(function(added) { added.remove(); $httpBackend.expectDELETE('/accounts/'+nextAccountId).respond(201, ''); @@ -622,9 +622,9 @@ describe("Restangular", function() { $httpBackend.flush(); }); - it("Doing a post to a server that returns no element will return undefined", function() { + it('Doing a post to a server that returns no element will return undefined', function() { restangularAccounts.getList().then(function(accounts) { - var newTransaction = {id: 1, name: "Gonto"}; + var newTransaction = {id: 1, name: 'Gonto'}; accounts[1].post('transactions', newTransaction).then(function(transaction) { expect(transaction).toBeUndefined(); }); @@ -633,14 +633,14 @@ describe("Restangular", function() { $httpBackend.flush(); }); - it("head() should safely return", function() { + it('head() should safely return', function() { restangularAccounts.head().then(function() { expect(true).toBe(true); }); $httpBackend.flush(); }); - it("trace() should safely return", function() { + it('trace() should safely return', function() { restangularAccounts.trace().then(function() { expect(true).toBe(true); }); @@ -648,21 +648,21 @@ describe("Restangular", function() { $httpBackend.flush(); }); - it("customPUT should work", function() { + it('customPUT should work', function() { $httpBackend.expectPUT('/accounts/hey').respond(accountsModel); restangularAccounts.customPUT({key: 'value'}, 'hey'); $httpBackend.flush(); }); - it("customPATCH should work", function() { + it('customPATCH should work', function() { var data = { foo: 'bar' }; $httpBackend.expectPATCH('/accounts/hey', data).respond(accountsModel); restangularAccounts.customPATCH(data, 'hey'); $httpBackend.flush(); }); - it("options() should safely return", function() { + it('options() should safely return', function() { restangularAccounts.options().then(function() { expect(true).toBe(true); }); @@ -670,7 +670,7 @@ describe("Restangular", function() { $httpBackend.flush(); }); - it("getList() should correctly handle params after customDELETE", function() { + it('getList() should correctly handle params after customDELETE', function() { $httpBackend.expectGET('/accounts?foo=1').respond(accountsModel); restangularAccounts.getList({foo: 1}).then(function(){ $httpBackend.expectDELETE('/accounts?id=1').respond(201, ''); @@ -686,9 +686,9 @@ describe("Restangular", function() { }); }); - describe("Scoped Service", function() { + describe('Scoped Service', function() { - it("should correctly work", function() { + it('should correctly work', function() { var Accounts = Restangular.service('accounts'); Accounts.post(newAccount); Accounts.one(0).get(); @@ -700,7 +700,7 @@ describe("Restangular", function() { $httpBackend.flush(); }); - it("should correctly work with children", function() { + it('should correctly work with children', function() { var Transactions = Restangular.service('transactions', restangularAccount1); Transactions.post(newAccount); Transactions.one(1).get(); @@ -712,7 +712,7 @@ describe("Restangular", function() { $httpBackend.flush(); }); - it("should add custom collection method added with withConfig", function() { + it('should add custom collection method added with withConfig', function() { var Accounts = Restangular.withConfig(function(RestangularConfigurer) { RestangularConfigurer.addElementTransformer('accounts', true, function(worker) { worker.addRestangularMethod('doSomething', 'get', 'do-something'); @@ -735,7 +735,7 @@ describe("Restangular", function() { $httpBackend.flush(); }); - it("should provide a one-off $http configuration method", function() { + it('should provide a one-off $http configuration method', function() { var Accounts = Restangular.service('accounts'); Accounts.withHttpConfig({transformRequest: angular.identity}); Accounts.post(newAccount); @@ -745,8 +745,8 @@ describe("Restangular", function() { }); - describe("ONE", function() { - it("get() should return a JSON item", function() { + describe('ONE', function() { + it('get() should return a JSON item', function() { restangularAccount1.get().then(function(account) { expect(Restangular.stripRestangular(account)) .toEqual(Restangular.stripRestangular(accountsModel[1])); @@ -755,7 +755,7 @@ describe("Restangular", function() { $httpBackend.flush(); }); - it("Should save as put correctly", function() { + it('Should save as put correctly', function() { restangularAccount1.get().then(function(account) { $httpBackend.expectPUT('/accounts/1'); account.put(); @@ -764,20 +764,20 @@ describe("Restangular", function() { $httpBackend.flush(); }); - it("Should save as post correctly", function() { + it('Should save as post correctly', function() { var account1 = angular.copy(restangularAccount1); $httpBackend.expectPOST('/accounts/1'); - account1.name = "Hey"; + account1.name = 'Hey'; account1.save(); $httpBackend.flush(); }); - it("Should keep route property when element is created", function() { + it('Should keep route property when element is created', function() { var account1 = Restangular.restangularizeElement(null, {}, 'accounts'); $httpBackend.expectPOST('/accounts'); $httpBackend.expectPUT('/accounts/1'); - account1.name = "Hey"; + account1.name = 'Hey'; account1.save().then(function(accountFromServer) { accountFromServer.id = 1; return accountFromServer.save(); @@ -787,8 +787,8 @@ describe("Restangular", function() { $httpBackend.flush() }); - it("Should make RequestLess connections with one", function() { - restangularAccount1.one("transactions", 1).get().then(function(transaction) { + it('Should make RequestLess connections with one', function() { + restangularAccount1.one('transactions', 1).get().then(function(transaction) { expect(Restangular.stripRestangular(transaction)) .toEqual(Restangular.stripRestangular(accountsModel[1].transactions[1])); }); @@ -796,8 +796,8 @@ describe("Restangular", function() { $httpBackend.flush(); }); - it("Should make RequestLess connections with all", function() { - restangularAccount1.all("transactions").getList().then(function(transactions) { + it('Should make RequestLess connections with all', function() { + restangularAccount1.all('transactions').getList().then(function(transactions) { expect(Restangular.stripRestangular(transactions)) .toEqual(Restangular.stripRestangular(accountsModel[1].transactions)); }); @@ -806,23 +806,23 @@ describe("Restangular", function() { }); - it("Custom GET methods should work", function() { - restangularAccount1.customGET("message").then(function(msg) { + it('Custom GET methods should work', function() { + restangularAccount1.customGET('message').then(function(msg) { expect(Restangular.stripRestangular(msg)).toEqual(Restangular.stripRestangular(messages[0])); }); $httpBackend.flush(); }); - it("put() should update the value", function() { + it('put() should update the value', function() { restangularAccount1.get().then(function(account) { account.amount = 1.618; account.put().then(function(newAc) { expect(accountsModel[1].amount).toEqual(1.618); newAc.remove(); - $httpBackend.expectDELETE("/accounts/1"); + $httpBackend.expectDELETE('/accounts/1'); }); - $httpBackend.expectPUT("/accounts/1"); + $httpBackend.expectPUT('/accounts/1'); }); @@ -830,9 +830,9 @@ describe("Restangular", function() { $httpBackend.flush(); }); - it("should return an array when accessing a subvalue", function() { + it('should return an array when accessing a subvalue', function() { restangularAccount1.get().then(function(account) { - account.getList("transactions").then(function(transactions) { + account.getList('transactions').then(function(transactions) { expect(Restangular.stripRestangular(transactions)) .toEqual(Restangular.stripRestangular(accountsModel[1].transactions)); }); @@ -842,12 +842,12 @@ describe("Restangular", function() { }); }); - describe("COPY", function() { - it("should copy an object and 'this' should reference the copied object", function() { + describe('COPY', function() { + it('should copy an object and "this" should reference the copied object', function() { var copiedAccount = Restangular.copy(accountsModel[0]); var that; - copiedAccount.user = "Copied string"; + copiedAccount.user = 'Copied string'; expect(copiedAccount).not.toBe(accountsModel[0]); // create a spy for one of the methods to capture the value of 'this' @@ -859,7 +859,7 @@ describe("Restangular", function() { expect(that).toBe(copiedAccount); }); - it("should copy an object and 'fromServer' param should be the same with the copied object", function() { + it('should copy an object and "fromServer" param should be the same with the copied object', function() { // with fromServer=true restangularAccount1.get().then(function(account) { var copiedAccount = Restangular.copy(account); @@ -875,26 +875,26 @@ describe("Restangular", function() { }); }); - describe("getRestangularUrl", function() { - it("should return the generated URL when you chain Restangular methods together", function() { - var restangularSpaces = Restangular.one("accounts",123).one("buildings", 456).all("spaces"); - expect(restangularSpaces.getRestangularUrl()).toEqual("/accounts/123/buildings/456/spaces"); + describe('getRestangularUrl', function() { + it('should return the generated URL when you chain Restangular methods together', function() { + var restangularSpaces = Restangular.one('accounts',123).one('buildings', 456).all('spaces'); + expect(restangularSpaces.getRestangularUrl()).toEqual('/accounts/123/buildings/456/spaces'); }); }); - describe("getRestangularUrl with useCannonicalId set to true", function() { - it("should return the generated URL when you chain Restangular methods together", function() { + describe('getRestangularUrl with useCannonicalId set to true', function() { + it('should return the generated URL when you chain Restangular methods together', function() { var R = Restangular.withConfig(function(config) { config.setUseCannonicalId(true); }); - var restangularSpaces = R.one("accounts",123).one("buildings", 456).all("spaces"); - expect(restangularSpaces.getRestangularUrl()).toEqual("/accounts/123/buildings/456/spaces"); + var restangularSpaces = R.one('accounts',123).one('buildings', 456).all('spaces'); + expect(restangularSpaces.getRestangularUrl()).toEqual('/accounts/123/buildings/456/spaces'); }); }); - describe("addElementTransformer", function() { - it("should allow for a custom method to be placed at the collection level", function() { + describe('addElementTransformer', function() { + it('should allow for a custom method to be placed at the collection level', function() { var accountsPromise; Restangular.addElementTransformer('accounts', true, function(collection) { @@ -905,13 +905,13 @@ describe("Restangular", function() { accountsPromise = Restangular.all('accounts').getList(); accountsPromise.then(function(accounts) { - expect(typeof accounts.totalAmount).toEqual("function"); + expect(typeof accounts.totalAmount).toEqual('function'); }); $httpBackend.flush(); }); - it("should allow for a custom method to be placed at the model level when one model is requested", function() { + it('should allow for a custom method to be placed at the model level when one model is requested', function() { var accountPromise; Restangular.addElementTransformer('accounts', false, function(model) { @@ -922,13 +922,13 @@ describe("Restangular", function() { accountPromise = Restangular.one('accounts', 1).get(); accountPromise.then(function(account) { - expect(typeof account.prettifyAmount).toEqual("function"); + expect(typeof account.prettifyAmount).toEqual('function'); }); $httpBackend.flush(); }); - it("should allow for a custom method to be placed at the model level when several models are requested", function() { + it('should allow for a custom method to be placed at the model level when several models are requested', function() { var accountPromise; Restangular.addElementTransformer('accounts', false, function(model) { @@ -940,7 +940,7 @@ describe("Restangular", function() { accountsPromise.then(function(accounts) { accounts.forEach(function(account, index) { - expect(typeof account.prettifyAmount).toEqual("function"); + expect(typeof account.prettifyAmount).toEqual('function'); }); }); @@ -948,8 +948,8 @@ describe("Restangular", function() { }); }); - describe("extendCollection", function() { - it("should be an alias for a specific invocation of addElementTransformer", function() { + describe('extendCollection', function() { + it('should be an alias for a specific invocation of addElementTransformer', function() { var spy = spyOn(Restangular, 'addElementTransformer'); var fn = function(collection) { @@ -963,8 +963,8 @@ describe("Restangular", function() { }); }); - describe("extendModel", function() { - it("should be an alias for a specific invocation of addElementTransformer", function() { + describe('extendModel', function() { + it('should be an alias for a specific invocation of addElementTransformer', function() { var spy = spyOn(Restangular, 'addElementTransformer'); var fn = function(model) { @@ -978,15 +978,15 @@ describe("Restangular", function() { }); }); - describe("headers", function() { - it("should return defaultHeaders", function() { + describe('headers', function() { + it('should return defaultHeaders', function() { var defaultHeaders = {testheader:'header value'}; Restangular.setDefaultHeaders(defaultHeaders); expect(Restangular.defaultHeaders).toEqual(defaultHeaders); }); - it("should pass uppercase methods in X-HTTP-Method-Override", function() { - Restangular.setMethodOverriders(["put"]); + it('should pass uppercase methods in X-HTTP-Method-Override', function() { + Restangular.setMethodOverriders(['put']); $httpBackend.expectPOST('/overriders/1').respond(function(method, url, data, headers) { expect(headers['X-HTTP-Method-Override']).toBe('PUT'); return {}; @@ -996,8 +996,8 @@ describe("Restangular", function() { }); }); - describe("defaultRequestParams", function() { - it("should return defaultRequestParams", function() { + describe('defaultRequestParams', function() { + it('should return defaultRequestParams', function() { var defaultRequestParams = {param:'value'}; Restangular.setDefaultRequestParams(defaultRequestParams); @@ -1005,7 +1005,7 @@ describe("Restangular", function() { expect(Restangular.requestParams.common).toEqual(defaultRequestParams); }); - it("should be able to set default params for get, post, put.. methods separately", function() { + it('should be able to set default params for get, post, put.. methods separately', function() { var postParams = {post:'value'}, putParams = {put:'value'}; @@ -1018,7 +1018,7 @@ describe("Restangular", function() { expect(Restangular.requestParams.common).not.toEqual(putParams); }); - it("should be able to set default params for multiple methods with array", function() { + it('should be able to set default params for multiple methods with array', function() { var defaultParams = {param:'value'}; Restangular.setDefaultRequestParams(['post', 'put'], defaultParams); @@ -1030,8 +1030,8 @@ describe("Restangular", function() { }); }); - describe("withConfig", function() { - it("should create new service with scoped configuration", function() { + describe('withConfig', function() { + it('should create new service with scoped configuration', function() { var childRestangular = Restangular.withConfig(function(RestangularConfigurer){ RestangularConfigurer.setBaseUrl('/api/v1'); }); @@ -1041,7 +1041,7 @@ describe("Restangular", function() { }); - it("should allow nested configurations", function() { + it('should allow nested configurations', function() { var childRestangular = Restangular.withConfig(function(RestangularConfigurer){ RestangularConfigurer.setBaseUrl('/api/v1'); }); @@ -1061,11 +1061,11 @@ describe("Restangular", function() { }); }); - describe("Self linking", function() { - it("Should request the link in HAL format", function() { + describe('Self linking', function() { + it('Should request the link in HAL format', function() { var linkRestangular = Restangular.withConfig(function(RestangularConfigurer) { RestangularConfigurer.setRestangularFields({ - selfLink: "_links.self" + selfLink: '_links.self' }); }); @@ -1073,15 +1073,15 @@ describe("Restangular", function() { $httpBackend.flush(); var account = arr[0]; - $httpBackend.expectPUT("/accountsHAL/martin"); - account.name = "Updated"; + $httpBackend.expectPUT('/accountsHAL/martin'); + account.name = 'Updated'; account.put(); $httpBackend.flush(); }); }); - describe("Singe one (endpoint not expecting an id)", function() { + describe('Singe one (endpoint not expecting an id)', function() { it('does not use the id for single resource GET', function() { Restangular.one('info', 0, true).get(); $httpBackend.expectGET('/info'); @@ -1091,7 +1091,7 @@ describe("Restangular", function() { it('getRestangularUrl() returns still the url without id after GET', function() { record = Restangular.one('info', 0, true); record.get().then(function(data){ - expect(data.getRestangularUrl()).toEqual("/info") + expect(data.getRestangularUrl()).toEqual('/info'); }); $httpBackend.expectGET('/info'); $httpBackend.flush(); @@ -1110,35 +1110,35 @@ describe("Restangular", function() { }); }); - describe("setSelfLinkAbsoluteUrl", function() { - it("works", function() { + describe('setSelfLinkAbsoluteUrl', function() { + it('works', function() { var childRestangular = Restangular.withConfig(function(RestangularConfigurer){ RestangularConfigurer.setSelfLinkAbsoluteUrl(false); }); expect(Restangular.configuration.absoluteUrl).toEqual(true); expect(childRestangular.configuration.absoluteUrl).toEqual(false); - }) + }); }); - describe("Misc", function () { - it("should not strip [one] or [all] key from plain object", function () { - Restangular.all("customs").customPOST({one: 'I am here', two: 'I am also here'}).then(function () { + describe('Misc', function () { + it('should not strip [one] or [all] key from plain object', function () { + Restangular.all('customs').customPOST({one: 'I am here', two: 'I am also here'}).then(function () { expect(1).toBe(1); }, function () { - expect("Promise").toBe("correctly fulfilled"); + expect('Promise').toBe('correctly fulfilled'); }); $httpBackend.flush(); }); - it("should not stip non-restangularized elements", function () { - expect(Restangular.stripRestangular(["test","test2"])).toEqual(["test","test2"]); + it('should not stip non-restangularized elements', function () { + expect(Restangular.stripRestangular(['test','test2'])).toEqual(['test','test2']); }); }); - describe("testing normalize url", function () { + describe('testing normalize url', function () { - it("should get a list of objects", function () { + it('should get a list of objects', function () { Restangular.all('customers/').getList().then(function(res){ res.getList({active: true}); $httpBackend.expectGET('/customers/?active=true'); @@ -1149,7 +1149,7 @@ describe("Restangular", function() { $httpBackend.flush(); }); - it("should get a list of objects even if the path has extra slashes", function () { + it('should get a list of objects even if the path has extra slashes', function () { Restangular.all('customers///').getList().then(function(res){ res.getList({active: true}); $httpBackend.expectGET('/customers/?active=true'); @@ -1158,7 +1158,7 @@ describe("Restangular", function() { $httpBackend.flush(); }); - it("should post with slash at the end", function () { + it('should post with slash at the end', function () { Restangular.all('customers/').getList().then(function(res){ res.post(newCustomer); $httpBackend.expectPOST('/customers/'); @@ -1167,7 +1167,7 @@ describe("Restangular", function() { $httpBackend.flush(); }); - it("should put with slash at the end", function () { + it('should put with slash at the end', function () { Restangular.all('customers/').getList().then(function(customers){ customers[0].put(); $httpBackend.expectPUT('/customers/0'); @@ -1175,17 +1175,17 @@ describe("Restangular", function() { $httpBackend.flush(); }); - it("should return a normalized URL even it has extra slashes", function() { - var restangularSpaces = Restangular.one("accounts//", 123).one("buildings//", 456).all("spaces///"); - expect(restangularSpaces.getRestangularUrl()).toEqual("/accounts/123/buildings/456/spaces/"); + it('should return a normalized URL even it has extra slashes', function() { + var restangularSpaces = Restangular.one('accounts//', 123).one('buildings//', 456).all('spaces///'); + expect(restangularSpaces.getRestangularUrl()).toEqual('/accounts/123/buildings/456/spaces/'); }); - it("should create a new service and still working normalized URL", function() { + it('should create a new service and still working normalized URL', function() { var newRes = Restangular.withConfig(function(RestangularConfigurer){ RestangularConfigurer.setBaseUrl('http://localhost:8080'); }); expect(newRes.configuration.baseUrl).toEqual('http://localhost:8080'); - newRes.all("customers////").getList(); + newRes.all('customers////').getList(); $httpBackend.expectGET('http://localhost:8080/customers/'); var newApi = Restangular.withConfig(function(RestangularConfigurer){ @@ -1193,27 +1193,27 @@ describe("Restangular", function() { }); expect(newApi.configuration.baseUrl).toEqual('api.new.domain'); - newApi.all("customers////").getList(); + newApi.all('customers////').getList(); $httpBackend.expectGET('api.new.domain/customers/'); $httpBackend.flush(); }); - it("Should work with absolute URL with //authority", function() { + it('Should work with absolute URL with //authority', function() { var newRes = Restangular.withConfig(function(RestangularConfigurer){ RestangularConfigurer.setBaseUrl('//localhost:8080'); }); expect(newRes.configuration.baseUrl).toEqual('//localhost:8080'); - newRes.all("customers////").getList(); + newRes.all('customers////').getList(); $httpBackend.expectGET('//localhost:8080/customers/').respond([]); $httpBackend.flush(); }); }); - describe("setPlainByDefault", function() { - - it("should not add restangularized methods to response object", function() { + describe('setPlainByDefault', function() { + + it('should not add restangularized methods to response object', function() { var newRes = Restangular.withConfig(function(RestangularConfigurer) { RestangularConfigurer.setPlainByDefault(true); }); @@ -1227,7 +1227,7 @@ describe("Restangular", function() { $httpBackend.flush(); }); - it("shoud not add restangularized methods to response collection", function() { + it('shoud not add restangularized methods to response collection', function() { var newRes = Restangular.withConfig(function(RestangularConfigurer) { RestangularConfigurer.setPlainByDefault(true); }); From c0a8f4092ecb4d59259e1a9501ccae0cbac085f4 Mon Sep 17 00:00:00 2001 From: madflow Date: Sun, 25 Dec 2016 12:51:50 +0100 Subject: [PATCH 16/55] chore(build pipeline and tests) (#1441) * deleted duplicate dependencies keys * updated build dependencies * changed main file to unminified version * pin angular to 1.5.9 --- karma.conf.js | 14 ++++++++------ karma.underscore.conf.js | 17 ++++++++++------- package.json | 11 ++++------- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/karma.conf.js b/karma.conf.js index 6db78439..79bde350 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,19 +1,21 @@ // Karma configuration -// Generated on Fri Aug 09 2013 14:14:35 GMT-0500 (CDT) -module.exports = function(config) { +var angularVersion = '1.5.9'; +var lodashVersion = '4.17.2'; + +module.exports = function (config) { config.set({ // base path, that will be used to resolve files and exclude basePath: '', - frameworks: ["jasmine"], + frameworks: ['jasmine'], // list of files / patterns to load in the browser files: [ - 'https://ajax.googleapis.com/ajax/libs/angularjs/1.3.12/angular.js', - 'https://ajax.googleapis.com/ajax/libs/angularjs/1.3.12/angular-mocks.js', - 'http://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.3.0/lodash.js', + 'https://cdnjs.cloudflare.com/ajax/libs/angular.js/' + angularVersion + '/angular.js', + 'https://cdnjs.cloudflare.com/ajax/libs/angular.js/' + angularVersion + '/angular-mocks.js', + 'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/' + lodashVersion + '/lodash.js', 'src/restangular.js', 'test/*.js' ], diff --git a/karma.underscore.conf.js b/karma.underscore.conf.js index 0a46f791..8ace4908 100644 --- a/karma.underscore.conf.js +++ b/karma.underscore.conf.js @@ -1,19 +1,22 @@ -// Karma configuration -// Generated on Fri Aug 09 2013 14:14:35 GMT-0500 (CDT) +// Karma configuration for use with underscore instead of lodash -module.exports = function(config) { +var angularVersion = '1.5.9'; +var underscoreVersion = '1.8.3'; + + +module.exports = function (config) { config.set({ // base path, that will be used to resolve files and exclude basePath: '', - frameworks: ["jasmine"], + frameworks: ['jasmine'], // list of files / patterns to load in the browser files: [ - 'https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.6/angular.js', - 'https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.6/angular-mocks.js', - 'http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.1/underscore.js', + 'https://cdnjs.cloudflare.com/ajax/libs/angular.js/' + angularVersion + '/angular.js', + 'https://cdnjs.cloudflare.com/ajax/libs/angular.js/' + angularVersion + '/angular-mocks.js', + 'https://cdnjs.cloudflare.com/ajax/libs/underscore.js/' + underscoreVersion + '/underscore.js', 'src/restangular.js', 'test/*.js' ], diff --git a/package.json b/package.json index e03ad893..f7d1afa5 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "description": "Restful Resources service for AngularJS apps", "version": "1.5.2", "filename": "restangular.min.js", - "main": "./dist/restangular.min.js", + "main": "./dist/restangular.js", "homepage": "https://github.com/mgonto/restangular", "author": "Martin Gontovnikas ", "repository": { @@ -47,14 +47,11 @@ "grunt-conventional-changelog": "0.0.12", "grunt-zip": "*", "karma": "^0.13.19", - "karma-chrome-launcher": "~0.1.2", - "karma-firefox-launcher": "~0.1.3", + "karma-chrome-launcher": "~v2.0.0", + "karma-firefox-launcher": "~v1.0.0", "karma-jasmine": "~0.1.5", "karma-mocha-reporter": "0.2.8", - "karma-jasmine": "~0.1.5", - "karma-chrome-launcher": "~0.1.2", - "karma-phantomjs-launcher": "~0.1.2", - "karma-firefox-launcher": "~0.1.3" + "karma-phantomjs-launcher": "~v1.0.2" }, "scripts": { "test": "grunt test --verbose" From d60d5991d741f600ec2db21abe1906a0befc0b7d Mon Sep 17 00:00:00 2001 From: CMRHDL Date: Sun, 25 Dec 2016 12:56:16 +0100 Subject: [PATCH 17/55] Fixes #1257 (#1445) accept (number) 0 as response in get() --- src/restangular.js | 3 ++- test/restangularSpec.js | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/restangular.js b/src/restangular.js index 03349c53..4436afe8 100644 --- a/src/restangular.js +++ b/src/restangular.js @@ -1184,7 +1184,8 @@ restangular.provider('Restangular', function() { var fullParams = response.config.params; var elem = parseResponse(resData, operation, route, fetchUrl, response, deferred); - if (elem) { + // accept 0 as response + if (elem !== null && elem !== undefined && elem !== '') { var data; if (true === config.plainByDefault) { diff --git a/test/restangularSpec.js b/test/restangularSpec.js index aaaf1b1f..43a0604a 100644 --- a/test/restangularSpec.js +++ b/test/restangularSpec.js @@ -102,6 +102,10 @@ describe('Restangular', function() { return [500, {}, '']; }); + $httpBackend.whenGET('/misc/zero').respond(function() { + return [200, 0, '']; + }); + $httpBackend.whenPOST('/customs').respond(function(method, url, data, headers) { if (JSON.parse(data).one) { return [201, '', '']; @@ -1134,6 +1138,13 @@ describe('Restangular', function() { it('should not stip non-restangularized elements', function () { expect(Restangular.stripRestangular(['test','test2'])).toEqual(['test','test2']); }); + + it('should accept 0 as response', function () { + Restangular.one('misc', 'zero').get().then(function(res) { + expect(res).toEqual(0); + }); + $httpBackend.flush(); + }); }); describe('testing normalize url', function () { From de8f56184fac0554c4f49d8393bc00523f0b349d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Sun, 25 Dec 2016 18:00:59 +0200 Subject: [PATCH 18/55] feat(service) Add regexp matching for route to element transformers (#1430) * Add regexp matching for route to element transformers * Change https to http for CDN resources in karma --- src/restangular.js | 37 +++++++++++++++++++------- test/restangularSpec.js | 57 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 9 deletions(-) diff --git a/src/restangular.js b/src/restangular.js index 4436afe8..49f64d00 100644 --- a/src/restangular.js +++ b/src/restangular.js @@ -65,7 +65,7 @@ restangular.provider('Restangular', function() { config.defaultHttpFields = values; return this; }; - + /** * Always return plain data, no restangularized object **/ @@ -440,6 +440,7 @@ restangular.provider('Restangular', function() { * Add element transformers for certain routes. */ config.transformers = config.transformers || {}; + config.matchTransformers = config.matchTransformers || []; object.addElementTransformer = function(type, secondArg, thirdArg) { var isCollection = null; var transformer = null; @@ -450,17 +451,24 @@ restangular.provider('Restangular', function() { isCollection = secondArg; } - var typeTransformers = config.transformers[type]; - if (!typeTransformers) { - typeTransformers = config.transformers[type] = []; - } - - typeTransformers.push(function(coll, elem) { + var transformerFn = function(coll, elem) { if (_.isNull(isCollection) || (coll === isCollection)) { return transformer(elem); } return elem; - }); + }; + + if (_.isRegExp(type)) { + config.matchTransformers.push({ + regexp: type, + transformer: transformerFn + }); + } else { + if (!config.transformers[type]) { + config.transformers[type] = []; + } + config.transformers[type].push(transformerFn); + } return object; }; @@ -477,8 +485,19 @@ restangular.provider('Restangular', function() { if (!force && !config.transformLocalElements && !elem[config.restangularFields.fromServer]) { return elem; } - var typeTransformers = config.transformers[route]; + var changedElem = elem; + + var matchTransformers = config.matchTransformers; + if (matchTransformers) { + _.each(matchTransformers, function (transformer) { + if (route.match(transformer.regexp)) { + changedElem = transformer.transformer(isCollection, changedElem); + } + }); + } + + var typeTransformers = config.transformers[route]; if (typeTransformers) { _.each(typeTransformers, function(transformer) { changedElem = transformer(isCollection, changedElem); diff --git a/test/restangularSpec.js b/test/restangularSpec.js index 43a0604a..8090a49c 100644 --- a/test/restangularSpec.js +++ b/test/restangularSpec.js @@ -54,6 +54,7 @@ describe('Restangular', function() { $httpBackend.whenJSONP('/accounts/1').respond(accountsModel[1]); $httpBackend.whenGET('/accounts/1/transactions').respond(accountsModel[1].transactions); $httpBackend.whenGET('/accounts/1/transactions/1').respond(accountsModel[1].transactions[1]); + $httpBackend.whenGET('/accounts/search/byOwner').respond(accountsModel); $httpBackend.whenGET('/info').respond(infoModel); $httpBackend.whenGET('/accounts/1/info').respond(infoModel); @@ -950,6 +951,62 @@ describe('Restangular', function() { $httpBackend.flush(); }); + + it("should allow for a custom method to be placed at the collection level using a regexp matching the route", function () { + var accountPromise; + + Restangular.addElementTransformer(/^accounts/, false, function(model) { + model.prettifyAmount = function() {}; + return model; + }); + + accountsPromise = Restangular.all('accounts/search/byOwner', 1).getList(); + + accountsPromise.then(function(accounts) { + accounts.forEach(function(account, index) { + expect(typeof account.prettifyAmount).toEqual("function"); + }); + }); + + $httpBackend.flush(); + }); + + it("should allow for a custom method to be placed at the model level using regexp route when one model is requested", function() { + var accountPromise; + + Restangular.addElementTransformer(/^accounts/, false, function(model) { + model.prettifyAmount = function() {}; + return model; + }); + + accountPromise = Restangular.one('accounts', 1).get(); + + accountPromise.then(function(account) { + expect(typeof account.prettifyAmount).toEqual("function"); + }); + + $httpBackend.flush(); + }); + + it("should allow for a custom method to be placed at the model level using regexp when several models are requested", function() { + var accountPromise; + + Restangular.addElementTransformer(/^accounts/, false, function(model) { + model.prettifyAmount = function() {}; + return model; + }); + + accountsPromise = Restangular.all('accounts', 1).getList(); + + accountsPromise.then(function(accounts) { + accounts.forEach(function(account, index) { + expect(typeof account.prettifyAmount).toEqual("function"); + }); + }); + + $httpBackend.flush(); + }); + }); describe('extendCollection', function() { From d4f919f88fc6932bfb22c8a49b142480936d2a8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Sun, 25 Dec 2016 18:52:32 +0200 Subject: [PATCH 19/55] Add tests --- test/restangularSpec.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/restangularSpec.js b/test/restangularSpec.js index 8090a49c..21259c7e 100644 --- a/test/restangularSpec.js +++ b/test/restangularSpec.js @@ -710,10 +710,12 @@ describe('Restangular', function() { Transactions.post(newAccount); Transactions.one(1).get(); Transactions.getList(); + Transactions.get(1); $httpBackend.expectPOST('/accounts/1/transactions'); $httpBackend.expectGET('/accounts/1/transactions/1'); $httpBackend.expectGET('/accounts/1/transactions'); + $httpBackend.expectGET('/accounts/1/transactions/1'); $httpBackend.flush(); }); @@ -732,11 +734,13 @@ describe('Restangular', function() { Accounts.one(0).get(); Accounts.getList(); Accounts.doSomething(); + Accounts.get(0); $httpBackend.expectPOST('/accounts'); $httpBackend.expectGET('/accounts/0'); $httpBackend.expectGET('/accounts'); $httpBackend.expectGET('/accounts/do-something'); + $httpBackend.expectGET('/accounts/0'); $httpBackend.flush(); }); From a0495a10ea0e5bf33f776b81d94d44e0878ed58a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Sun, 25 Dec 2016 19:33:54 +0200 Subject: [PATCH 20/55] Add tests --- test/restangularSpec.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/restangularSpec.js b/test/restangularSpec.js index 21259c7e..47fecfdb 100644 --- a/test/restangularSpec.js +++ b/test/restangularSpec.js @@ -1206,6 +1206,12 @@ describe('Restangular', function() { }); $httpBackend.flush(); }); + + it('Should accept 0 as a proper id in custom requests', function () { + $httpBackend.expectDELETE('/accounts/0').respond(202); + Restangular.all('accounts').customDELETE(0); + $httpBackend.flush(); + }); }); describe('testing normalize url', function () { From 74d444ea9c8779d56650fec43fe2ae9041607060 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Sun, 25 Dec 2016 19:59:26 +0200 Subject: [PATCH 21/55] Remove bower.json from bump script --- Gruntfile.js | 1 - 1 file changed, 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index 20cbe953..d06566eb 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -142,7 +142,6 @@ module.exports = function(grunt) { grunt.file.write(file, JSON.stringify(json, null, ' ')); } updateFile('package.json'); - updateFile('bower.json'); grunt.log.ok('Version bumped to ' + version); }); From 71472a24bdd6f795da743ec55d568add8b6a3f48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Sun, 25 Dec 2016 20:46:36 +0200 Subject: [PATCH 22/55] Bump version to 1.6.0, update changelog --- CHANGELOG.md | 43 +++++++++++++++++++++++++++++++++---------- README.md | 1 + package.json | 4 ++-- 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7420402c..a0602d85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,20 +1,43 @@ +# 1.6.0 (2016-12-25) + +* Url now supports unescaped suffix (0350bcd) +* Added Restangular Plunkr example (c4ef002) +* Now id can be a nested property (a94228b) +* Add withHttpConfig to objects created with .service (e8f7295) +* Add support for angularjs dependency injection using commonjs require syntax (f02db83) +* Fix missing 'get' in decoupled service (8096ce1) +* Avoid restangularizing an undefined element in restangularizeCollecti onAndElements. (0f8b562) +* Fixes #1167: Extend condition to treat '0, which as a falsy value currently fails, as a valid ID (95ea231) +* Add customPatch method (01297fe) +* Added UMD snippet (caab5e6) +* Support BaseUrl with athority without schema (5f3eacb) +* Add ability to restangularize a collection with fromServer set (51066ec) +* Add configuration option to use plain() by default (94ffaf0) +* Fix fromServer param while copying (b53f4b6) +* Rename CONTRIBUTE.md to CONTRIBUTING.md in accordance with GitHub's spec (c17df47) +* Remove moot `version` property from bower.json (1a585f3) +* Add more realistic POST response for accounts, with id (#943) (11fb475) +* Added context/explanation of when to use JSONP. (fec9b27) +* Add regexp matching for route to element transformers (#1430) (de8f561) + + 1.5.2 / 2016-02-15 ================== - * Change _.contains to _.includes for compatability with lodash >= 4.0 +* Change \_.contains to \_.includes for compatability with lodash >= 4.0 1.5.1 / 2015-04-03 ================== - * Release 1.5.0 - * Updated zip - * Merge branch 'master' of github.com:mgonto/restangular - * Merge pull request #1081 from rajeshwarpatlolla/develop - * Merge pull request #1079 from wching/master - * change in README file - * url modified for 'Chain methods together to easily build complex requests' - * Update README.md - * Update README.md +* Release 1.5.0 +* Updated zip +* Merge branch 'master' of github.com:mgonto/restangular +* Merge pull request #1081 from rajeshwarpatlolla/develop +* Merge pull request #1079 from wching/master +* change in README file +* url modified for 'Chain methods together to easily build complex requests' +* Update README.md +* Update README.md 1.5.0 / 2015-04-03 ================== diff --git a/README.md b/README.md index 8cd4df23..4f3414a8 100644 --- a/README.md +++ b/README.md @@ -684,6 +684,7 @@ module.factory('Users', function(Restangular) { }); // In your controller you inject Users +Users.get(2) // GET to /users/2 Users.one(2).get() // GET to /users/2 Users.post({data}) // POST to /users diff --git a/package.json b/package.json index f7d1afa5..7f085cd1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "restangular", "description": "Restful Resources service for AngularJS apps", - "version": "1.5.2", + "version": "1.6.0", "filename": "restangular.min.js", "main": "./dist/restangular.js", "homepage": "https://github.com/mgonto/restangular", @@ -57,4 +57,4 @@ "test": "grunt test --verbose" }, "license": "MIT" -} +} \ No newline at end of file From 088f4e424e49ac92eeaa346a6bc948339685d71d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Sun, 25 Dec 2016 21:04:12 +0200 Subject: [PATCH 23/55] Add built dist files --- dist/restangular.js | 116 +++++++++++++++++++++++++++++----------- dist/restangular.min.js | 4 +- dist/restangular.zip | Bin 68322 -> 70984 bytes 3 files changed, 86 insertions(+), 34 deletions(-) diff --git a/dist/restangular.js b/dist/restangular.js index dd2e369e..bff968f9 100644 --- a/dist/restangular.js +++ b/dist/restangular.js @@ -1,9 +1,19 @@ /** * Restful Resources service for AngularJS apps - * @version v1.5.2 - 2016-02-08 * @link https://github.com/mgonto/restangular + * @version v1.6.0 - 2016-12-25 * @link https://github.com/mgonto/restangular * @author Martin Gontovnikas * @license MIT License, http://www.opensource.org/licenses/MIT - */(function() { + */(function (root, factory) { + // https://github.com/umdjs/umd/blob/master/templates/returnExports.js + if (typeof define === 'function' && define.amd) { + define(['lodash', 'angular'], factory); + } else if (typeof module === 'object' && module.exports) { + module.exports = factory(require('lodash'), require('angular')); + } else { + // No global export, Restangular will register itself as Angular.js module + factory(root._, root.angular); + } +}(this, function (_, angular) { var restangular = angular.module('restangular', []); @@ -61,6 +71,15 @@ restangular.provider('Restangular', function() { return this; }; + /** + * Always return plain data, no restangularized object + **/ + config.plainByDefault = config.plainByDefault || false; + object.setPlainByDefault = function(value) { + config.plainByDefault = value === true ? true : false; + return this; + }; + config.withHttpValues = function(httpLocalConfig, obj) { return _.defaults(obj, httpLocalConfig, config.defaultHttpFields); }; @@ -188,12 +207,14 @@ restangular.provider('Restangular', function() { oneUrl: 'oneUrl', allUrl: 'allUrl', customPUT: 'customPUT', + customPATCH: 'customPATCH', customPOST: 'customPOST', customDELETE: 'customDELETE', customGET: 'customGET', customGETLIST: 'customGETLIST', customOperation: 'customOperation', doPUT: 'doPUT', + doPATCH: 'doPATCH', doPOST: 'doPOST', doDELETE: 'doDELETE', doGET: 'doGET', @@ -424,6 +445,7 @@ restangular.provider('Restangular', function() { * Add element transformers for certain routes. */ config.transformers = config.transformers || {}; + config.matchTransformers = config.matchTransformers || []; object.addElementTransformer = function(type, secondArg, thirdArg) { var isCollection = null; var transformer = null; @@ -434,17 +456,24 @@ restangular.provider('Restangular', function() { isCollection = secondArg; } - var typeTransformers = config.transformers[type]; - if (!typeTransformers) { - typeTransformers = config.transformers[type] = []; - } - - typeTransformers.push(function(coll, elem) { + var transformerFn = function(coll, elem) { if (_.isNull(isCollection) || (coll === isCollection)) { return transformer(elem); } return elem; - }); + }; + + if (_.isRegExp(type)) { + config.matchTransformers.push({ + regexp: type, + transformer: transformerFn + }); + } else { + if (!config.transformers[type]) { + config.transformers[type] = []; + } + config.transformers[type].push(transformerFn); + } return object; }; @@ -461,8 +490,19 @@ restangular.provider('Restangular', function() { if (!force && !config.transformLocalElements && !elem[config.restangularFields.fromServer]) { return elem; } - var typeTransformers = config.transformers[route]; + var changedElem = elem; + + var matchTransformers = config.matchTransformers; + if (matchTransformers) { + _.each(matchTransformers, function (transformer) { + if (route.match(transformer.regexp)) { + changedElem = transformer.transformer(isCollection, changedElem); + } + }); + } + + var typeTransformers = config.transformers[route]; if (typeTransformers) { _.each(typeTransformers, function(transformer) { changedElem = transformer(isCollection, changedElem); @@ -560,7 +600,7 @@ restangular.provider('Restangular', function() { var url = this.base(current); - if (what) { + if (what || what === 0) { var add = ''; if (!/\/$/.test(url)) { add += '/'; @@ -641,7 +681,7 @@ restangular.provider('Restangular', function() { Path.prototype = new BaseCreator(); Path.prototype.normalizeUrl = function (url){ - var parts = /(http[s]?:\/\/)?(.*)?/.exec(url); + var parts = /((?:http[s]?:)?\/\/)?(.*)?/.exec(url); parts[2] = parts[2].replace(/[\\\/]+/g, '/'); return (typeof parts[1] !== 'undefined')? parts[1] + parts[2] : parts[2]; }; @@ -927,20 +967,16 @@ restangular.provider('Restangular', function() { function addCustomOperation(elem) { elem[config.restangularFields.customOperation] = _.bind(customFunction, elem); - _.each(['put', 'post', 'get', 'delete'], function(oper) { + var requestMethods = { get: customFunction, delete: customFunction }; + _.each(['put', 'patch', 'post'], function(name) { + requestMethods[name] = function(operation, elem, path, params, headers) { + return _.bind(customFunction, this)(operation, path, params, headers, elem); + }; + }); + _.each(requestMethods, function(requestFunc, name) { + var callOperation = name === 'delete' ? 'remove' : name; _.each(['do', 'custom'], function(alias) { - var callOperation = oper === 'delete' ? 'remove' : oper; - var name = alias + oper.toUpperCase(); - var callFunction; - - if (callOperation !== 'put' && callOperation !== 'post') { - callFunction = customFunction; - } else { - callFunction = function(operation, elem, path, params, headers) { - return _.bind(customFunction, this)(operation, path, params, headers, elem); - }; - } - elem[name] = _.bind(callFunction, elem, callOperation); + elem[alias + name.toUpperCase()] = _.bind(requestFunc, elem, callOperation); }); }); elem[config.restangularFields.customGETLIST] = _.bind(fetchFunction, elem); @@ -950,7 +986,7 @@ restangular.provider('Restangular', function() { function copyRestangularizedElement(fromElement, toElement) { var copiedElement = angular.copy(fromElement, toElement); return restangularizeElem(copiedElement[config.restangularFields.parentResource], - copiedElement, copiedElement[config.restangularFields.route], true); + copiedElement, copiedElement[config.restangularFields.route], copiedElement[config.restangularFields.fromServer]); } function restangularizeElem(parent, element, route, fromServer, collection, reqParams) { @@ -1003,10 +1039,12 @@ restangular.provider('Restangular', function() { return config.transformElem(localElem, true, route, service, true); } - function restangularizeCollectionAndElements(parent, element, route) { - var collection = restangularizeCollection(parent, element, route, false); + function restangularizeCollectionAndElements(parent, element, route, fromServer) { + var collection = restangularizeCollection(parent, element, route, fromServer); _.each(collection, function(elem) { - restangularizeElem(parent, elem, route, false); + if (elem) { + restangularizeElem(parent, elem, route, fromServer); + } }); return collection; } @@ -1074,6 +1112,11 @@ restangular.provider('Restangular', function() { if (!_.isArray(data)) { throw new Error('Response for getList SHOULD be an array and not an object or something else'); } + + if (true === config.plainByDefault) { + return resolvePromise(deferred, response, data, filledArray); + } + var processedData = _.map(data, function(elem) { if (!__this[config.restangularFields.restangularCollection]) { return restangularizeElem(__this, elem, what, true, data); @@ -1164,8 +1207,15 @@ restangular.provider('Restangular', function() { var resData = response.data; var fullParams = response.config.params; var elem = parseResponse(resData, operation, route, fetchUrl, response, deferred); - if (elem) { + + // accept 0 as response + if (elem !== null && elem !== undefined && elem !== '') { var data; + + if (true === config.plainByDefault) { + return resolvePromise(deferred, response, elem, filledObject); + } + if (operation === 'post' && !__this[config.restangularFields.restangularCollection]) { data = restangularizeElem( __this[config.restangularFields.parentResource], @@ -1310,6 +1360,8 @@ restangular.provider('Restangular', function() { serv.one = _.bind(one, (parent || service), parent, route); serv.post = _.bind(collection.post, collection); serv.getList = _.bind(collection.getList, collection); + serv.withHttpConfig = _.bind(collection.withHttpConfig, collection); + serv.get = _.bind(collection.get, collection); for (var prop in collection) { if (collection.hasOwnProperty(prop) && _.isFunction(collection[prop]) && !_.includes(knownCollectionMethods, prop)) { @@ -1351,5 +1403,5 @@ restangular.provider('Restangular', function() { return createServiceForConfiguration(globalConfiguration); }]; }); - -})(); + return restangular.name; +})); diff --git a/dist/restangular.min.js b/dist/restangular.min.js index f0599d58..7e481e41 100644 --- a/dist/restangular.min.js +++ b/dist/restangular.min.js @@ -1,6 +1,6 @@ /** * Restful Resources service for AngularJS apps - * @version v1.5.2 - 2016-02-08 * @link https://github.com/mgonto/restangular + * @version v1.6.0 - 2016-12-25 * @link https://github.com/mgonto/restangular * @author Martin Gontovnikas * @license MIT License, http://www.opensource.org/licenses/MIT - */!function(){var a=angular.module("restangular",[]);a.provider("Restangular",function(){var a={};a.init=function(a,b){function c(a,b,c,d){var e={};return _.each(_.keys(d),function(f){var g=d[f];g.params=_.extend({},g.params,a.defaultRequestParams[g.method.toLowerCase()]),_.isEmpty(g.params)&&delete g.params,a.isSafe(g.method)?e[f]=function(){return b(_.extend(g,{url:c}))}:e[f]=function(a){return b(_.extend(g,{url:c,data:a}))}}),e}a.configuration=b;var d=["get","head","options","trace","getlist"];b.isSafe=function(a){return _.includes(d,a.toLowerCase())};var e=/^https?:\/\//i;b.isAbsoluteUrl=function(a){return _.isUndefined(b.absoluteUrl)||_.isNull(b.absoluteUrl)?a&&e.test(a):b.absoluteUrl},b.absoluteUrl=_.isUndefined(b.absoluteUrl)?!0:b.absoluteUrl,a.setSelfLinkAbsoluteUrl=function(a){b.absoluteUrl=a},b.baseUrl=_.isUndefined(b.baseUrl)?"":b.baseUrl,a.setBaseUrl=function(a){return b.baseUrl=/\/$/.test(a)?a.substring(0,a.length-1):a,this},b.extraFields=b.extraFields||[],a.setExtraFields=function(a){return b.extraFields=a,this},b.defaultHttpFields=b.defaultHttpFields||{},a.setDefaultHttpFields=function(a){return b.defaultHttpFields=a,this},b.withHttpValues=function(a,c){return _.defaults(c,a,b.defaultHttpFields)},b.encodeIds=_.isUndefined(b.encodeIds)?!0:b.encodeIds,a.setEncodeIds=function(a){b.encodeIds=a},b.defaultRequestParams=b.defaultRequestParams||{get:{},post:{},put:{},remove:{},common:{}},a.setDefaultRequestParams=function(a,c){var d=[],e=c||a;return _.isUndefined(c)?d.push("common"):_.isArray(a)?d=a:d.push(a),_.each(d,function(a){b.defaultRequestParams[a]=e}),this},a.requestParams=b.defaultRequestParams,b.defaultHeaders=b.defaultHeaders||{},a.setDefaultHeaders=function(c){return b.defaultHeaders=c,a.defaultHeaders=b.defaultHeaders,this},a.defaultHeaders=b.defaultHeaders,b.methodOverriders=b.methodOverriders||[],a.setMethodOverriders=function(a){var c=_.extend([],a);return b.isOverridenMethod("delete",c)&&c.push("remove"),b.methodOverriders=c,this},b.jsonp=_.isUndefined(b.jsonp)?!1:b.jsonp,a.setJsonp=function(a){b.jsonp=a},b.isOverridenMethod=function(a,c){var d=c||b.methodOverriders;return!_.isUndefined(_.find(d,function(b){return b.toLowerCase()===a.toLowerCase()}))},b.urlCreator=b.urlCreator||"path",a.setUrlCreator=function(a){if(!_.has(b.urlCreatorFactory,a))throw new Error("URL Path selected isn't valid");return b.urlCreator=a,this},b.restangularFields=b.restangularFields||{id:"id",route:"route",parentResource:"parentResource",restangularCollection:"restangularCollection",cannonicalId:"__cannonicalId",etag:"restangularEtag",selfLink:"href",get:"get",getList:"getList",put:"put",post:"post",remove:"remove",head:"head",trace:"trace",options:"options",patch:"patch",getRestangularUrl:"getRestangularUrl",getRequestedUrl:"getRequestedUrl",putElement:"putElement",addRestangularMethod:"addRestangularMethod",getParentList:"getParentList",clone:"clone",ids:"ids",httpConfig:"_$httpConfig",reqParams:"reqParams",one:"one",all:"all",several:"several",oneUrl:"oneUrl",allUrl:"allUrl",customPUT:"customPUT",customPOST:"customPOST",customDELETE:"customDELETE",customGET:"customGET",customGETLIST:"customGETLIST",customOperation:"customOperation",doPUT:"doPUT",doPOST:"doPOST",doDELETE:"doDELETE",doGET:"doGET",doGETLIST:"doGETLIST",fromServer:"fromServer",withConfig:"withConfig",withHttpConfig:"withHttpConfig",singleOne:"singleOne",plain:"plain",save:"save",restangularized:"restangularized"},a.setRestangularFields=function(a){return b.restangularFields=_.extend(b.restangularFields,a),this},b.isRestangularized=function(a){return!!a[b.restangularFields.restangularized]},b.setFieldToElem=function(a,b,c){var d=a.split("."),e=b;return _.each(_.initial(d),function(a){e[a]={},e=e[a]}),e[_.last(d)]=c,this},b.getFieldFromElem=function(a,b){var c=a.split("."),d=b;return _.each(c,function(a){d&&(d=d[a])}),angular.copy(d)},b.setIdToElem=function(a,c){return b.setFieldToElem(b.restangularFields.id,a,c),this},b.getIdFromElem=function(a){return b.getFieldFromElem(b.restangularFields.id,a)},b.isValidId=function(a){return""!==a&&!_.isUndefined(a)&&!_.isNull(a)},b.setUrlToElem=function(a,c){return b.setFieldToElem(b.restangularFields.selfLink,a,c),this},b.getUrlFromElem=function(a){return b.getFieldFromElem(b.restangularFields.selfLink,a)},b.useCannonicalId=_.isUndefined(b.useCannonicalId)?!1:b.useCannonicalId,a.setUseCannonicalId=function(a){return b.useCannonicalId=a,this},b.getCannonicalIdFromElem=function(a){var c=a[b.restangularFields.cannonicalId],d=b.isValidId(c)?c:b.getIdFromElem(a);return d},b.responseInterceptors=b.responseInterceptors||[],b.defaultResponseInterceptor=function(a){return a},b.responseExtractor=function(a,c,d,e,f,g){var h=angular.copy(b.responseInterceptors);h.push(b.defaultResponseInterceptor);var i=a;return _.each(h,function(a){i=a(i,c,d,e,f,g)}),i},a.addResponseInterceptor=function(a){return b.responseInterceptors.push(a),this},b.errorInterceptors=b.errorInterceptors||[],a.addErrorInterceptor=function(a){return b.errorInterceptors.push(a),this},a.setResponseInterceptor=a.addResponseInterceptor,a.setResponseExtractor=a.addResponseInterceptor,a.setErrorInterceptor=a.addErrorInterceptor,b.requestInterceptors=b.requestInterceptors||[],b.defaultInterceptor=function(a,b,c,d,e,f,g){return{element:a,headers:e,params:f,httpConfig:g}},b.fullRequestInterceptor=function(a,c,d,e,f,g,h){var i=angular.copy(b.requestInterceptors),j=b.defaultInterceptor(a,c,d,e,f,g,h);return _.reduce(i,function(a,b){return _.extend(a,b(a.element,c,d,e,a.headers,a.params,a.httpConfig))},j)},a.addRequestInterceptor=function(a){return b.requestInterceptors.push(function(b,c,d,e,f,g,h){return{headers:f,params:g,element:a(b,c,d,e),httpConfig:h}}),this},a.setRequestInterceptor=a.addRequestInterceptor,a.addFullRequestInterceptor=function(a){return b.requestInterceptors.push(a),this},a.setFullRequestInterceptor=a.addFullRequestInterceptor,b.onBeforeElemRestangularized=b.onBeforeElemRestangularized||function(a){return a},a.setOnBeforeElemRestangularized=function(a){return b.onBeforeElemRestangularized=a,this},a.setRestangularizePromiseInterceptor=function(a){return b.restangularizePromiseInterceptor=a,this},b.onElemRestangularized=b.onElemRestangularized||function(a){return a},a.setOnElemRestangularized=function(a){return b.onElemRestangularized=a,this},b.shouldSaveParent=b.shouldSaveParent||function(){return!0},a.setParentless=function(a){return _.isArray(a)?b.shouldSaveParent=function(b){return!_.includes(a,b)}:_.isBoolean(a)&&(b.shouldSaveParent=function(){return!a}),this},b.suffix=_.isUndefined(b.suffix)?null:b.suffix,a.setRequestSuffix=function(a){return b.suffix=a,this},b.transformers=b.transformers||{},a.addElementTransformer=function(c,d,e){var f=null,g=null;2===arguments.length?g=d:(g=e,f=d);var h=b.transformers[c];return h||(h=b.transformers[c]=[]),h.push(function(a,b){return _.isNull(f)||a===f?g(b):b}),a},a.extendCollection=function(b,c){return a.addElementTransformer(b,!0,c)},a.extendModel=function(b,c){return a.addElementTransformer(b,!1,c)},b.transformElem=function(a,c,d,e,f){if(!f&&!b.transformLocalElements&&!a[b.restangularFields.fromServer])return a;var g=b.transformers[d],h=a;return g&&_.each(g,function(a){h=a(c,h)}),b.onElemRestangularized(h,c,d,e)},b.transformLocalElements=_.isUndefined(b.transformLocalElements)?!1:b.transformLocalElements,a.setTransformOnlyServerElements=function(a){b.transformLocalElements=!a},b.fullResponse=_.isUndefined(b.fullResponse)?!1:b.fullResponse,a.setFullResponse=function(a){return b.fullResponse=a,this},b.urlCreatorFactory={};var f=function(){};f.prototype.setConfig=function(a){return this.config=a,this},f.prototype.parentsArray=function(a){for(var b=[];a;)b.push(a),a=a[this.config.restangularFields.parentResource];return b.reverse()},f.prototype.resource=function(a,d,e,f,g,h,i,j){var k=_.defaults(g||{},this.config.defaultRequestParams.common),l=_.defaults(f||{},this.config.defaultHeaders);i&&(b.isSafe(j)?l["If-None-Match"]=i:l["If-Match"]=i);var m=this.base(a);if(h){var n="";/\/$/.test(m)||(n+="/"),n+=h,m+=n}return this.config.suffix&&-1===m.indexOf(this.config.suffix,m.length-this.config.suffix.length)&&!this.config.getUrlFromElem(a)&&(m+=this.config.suffix),a[this.config.restangularFields.httpConfig]=void 0,c(this.config,d,m,{getList:this.config.withHttpValues(e,{method:"GET",params:k,headers:l}),get:this.config.withHttpValues(e,{method:"GET",params:k,headers:l}),jsonp:this.config.withHttpValues(e,{method:"jsonp",params:k,headers:l}),put:this.config.withHttpValues(e,{method:"PUT",params:k,headers:l}),post:this.config.withHttpValues(e,{method:"POST",params:k,headers:l}),remove:this.config.withHttpValues(e,{method:"DELETE",params:k,headers:l}),head:this.config.withHttpValues(e,{method:"HEAD",params:k,headers:l}),trace:this.config.withHttpValues(e,{method:"TRACE",params:k,headers:l}),options:this.config.withHttpValues(e,{method:"OPTIONS",params:k,headers:l}),patch:this.config.withHttpValues(e,{method:"PATCH",params:k,headers:l})})};var g=function(){};g.prototype=new f,g.prototype.normalizeUrl=function(a){var b=/(http[s]?:\/\/)?(.*)?/.exec(a);return b[2]=b[2].replace(/[\\\/]+/g,"/"),"undefined"!=typeof b[1]?b[1]+b[2]:b[2]},g.prototype.base=function(a){var c=this;return _.reduce(this.parentsArray(a),function(a,d){var e,f=c.config.getUrlFromElem(d);if(f){if(c.config.isAbsoluteUrl(f))return f;e=f}else if(e=d[c.config.restangularFields.route],d[c.config.restangularFields.restangularCollection]){var g=d[c.config.restangularFields.ids];g&&(e+="/"+g.join(","))}else{var h;h=c.config.useCannonicalId?c.config.getCannonicalIdFromElem(d):c.config.getIdFromElem(d),b.isValidId(h)&&!d.singleOne&&(e+="/"+(c.config.encodeIds?encodeURIComponent(h):h))}return a=a.replace(/\/$/,"")+"/"+e,c.normalizeUrl(a)},this.config.baseUrl)},g.prototype.fetchUrl=function(a,b){var c=this.base(a);return b&&(c+="/"+b),c},g.prototype.fetchRequestedUrl=function(a,c){function d(a){var b=[];for(var c in a)a.hasOwnProperty(c)&&b.push(c);return b.sort()}function e(a,b,c){for(var e=d(a),f=0;fjJ+N>8!2YY)Z|?cWikv9tjFRo-B)Q-?B~?o3 zM#(A6E7C%jbT;o4O2ufp2tsy3l1uY>%SlM4m9TS`q-mPepQyOA_e*_!?w6>ZHO1_b z3z4V$GmfbjQ~j#cuji8Gj9%!Usj@nZ36>-)Q?!EfWF51dap!U3Y0D__Nd!1*3GOQ1 zOtoN1^W-2GtkY$?V96c}iYoaaj~7J=ut0L-k~`)|NytafNMbTonzD)|cZ_t#&Sa!Q z(n{JSJ;^Q=txQ7Fi_)lAAmkW^Z1SODVeuvG_X*JQrm- z8tPxqCf2vSVKj3_pD$t!v_}$l%+5)sUeZ-5=K$(6Xy0s^l8_tw;QH`5$2>59gO$+B znGytKIJtzKjMl*y7a=+91PMfP$484+X@Vy5hqLRY$UxmlO`6j)WwBwU0v<*r{wKx> zvd|R~c4yc6<##q+x%rCG69CzO%^%n+^vpU)z3N*G8AktX?uw z?83F%+xXLof}SfToI=(r6eaLHJ+tgwwLi4E3O<~}8rk?YpR5cw^R);iJ{7f#Q&tkn z&GlLF^oLd(oGLg$G+!>JWT6*?Cqd3h=tN8+lTcmi1TR(R$4BwfN=zQk`B+QXZnpI^ zZR{5ZH+BaG6sL*WOpqaot-q$9?Rfjft6kO_(FJb-_~9H^C*o#l`O-BPL##R0y~Uk< zPA^ElBqI%y6q_ytCD5L`x}6=5t>4*z8cIAPP>oWJNiu9N;J_q~(g5-4g2! zMcGGUL#*>JFJ}+OTG)rK-oK8IomuXWeV}<0ZRnYl4kd+;8Cw3r4ec8?$s;lHWYfi& zk(e^F{M7hea)nkywhW~ow@N9;EMm(Sq@-1fNkX*_yQZYV6J@NDRS%bzyd_K0si>tJ zDfp^s@)3X+~soURDQivcM(}Xd@l(PreJ3hCGEu9@>H!W{u zd*)lh?kqLTvkPx(VcCU^tSjBczVhDvEWfaU-Tdwrw)%ZhmbtT?eV7U4 ziT5?L)9((`UM{EKch?4X<5bI=c(foFKpVKCpQ+JusH$*h?EX&WOc5yb`ALUxoyhIVMHp%#1 z>8a1$F?HEx^nV*A8DVzEJ!2~+8Fy85HLff~ytow6w3<|)uTKa(ioic29_8tXi1-mT z;zqE@42Z-cA|h2uh!EM=R~x@aYF*=+cyE#=$5lX7OIaX|Nze$FhAupY z9-X!3i?XGt-XJ+CkW-p9otzm?Me}+=&lWWdI9sw{hYL$80oM^!jkDV~Y+E&*V(>AqbSRrI&CA{>h2%@lOY$IU97u1|6Bcl?-0>+RiRmP}@TG8<0Rgy^q|}9SArmvP z`Aac;pV3(8q1M<67%JI8EKZ0^SjCcoG?9{nazQ8c)XZVNbavBy>IT!Dn-jYl?Bqs|%6?b8yfeHrccHwX^1jnk%&0lb*fGW>k+C zoeYA=$wEe1T%@nZVBU2fM&f;a$tV)lk_?Ti8MLInhpovGo(^pN@DOl*>?&!A(Vjj5cgzFMl~2GFbP6@F}Vx49?OZ$Ao4qF2<`y8r(8N z8L>pl6hS4Fb#f6UMw>cXDCqN0rx8nw$6SoK0!PAg0;_7-y0XA$@1E9Yv;?e0$i_gD z!!v*hVd)%44hY23O{A*W4o^Y?;BH{gS9Y>{A8D@Cf7W#DRqcod5^<1B|DOPA;!C*c%6v z3lj)J_1?;HeQ!xJb;omSvx^>!Ig!^@eRE)^vC005*wbI#+E#0vdK6`^(84)b8a(osP}&efO2#R$^DR0LW%^H6&Pg0T}a)|%3ba86ac!=m_x zOF#EeYGSaBuXmo}A(eE=(%>9} z)0uVC)tXwOuTK{A6-+?gi~PKt;5!8uJ7%764k7o_*rms}U15^f&Dv5W9^wmD`7R8UzE@jntl{)t#r zr}J@WI|uR56(=(6{5N*6KRn+28a%R}exs$i$|C#IH+o-_HFo5Y%a1^@iq=6rmveHq zp=ZEd9S;o%+yd(cL76}CNPlMoE}V|*@Y#h&l(lh9pXQ=uw=G`2Uo~JDzEsF##u51- z#xc{Eq3*i~gBCxzd)q3wZ2VcroeLg&JJ|;x4G(i`_fq;p6db8{RTZ_NLLJnkno@0{ zt!bDo*8w8e)|3{m++;nE_FOaDpt|WAw>_)r@@#-06l#`&=Pg&Z5i_t8aLDd?bn`X@ zokGDWjOEbSq0+-4$IYI9v<-Inn@43}c0bmG=h4U7@r&`RKxRunR_VV@_J`Y{>bhwj zsv`w~=yI3@fUVZEosGh!~jWs{k44Tq2;U<5jz>6trrt0_uj3Mm$B2v@7+;zAG zvus#cFuV9v+d(%Ygx$;WD3vQ#uH+7kM^hpc!MsG8iU-QqM7ct4F@ygN79H3Q?nq8x zh@JZOI`+YDO@r6X1KVlg_)KD$9O$UQ+6)Zm+nxBumsME&%eU^N#p4hA@e}Rqc&>Du z4LyPS^WiI2N&HK*uYv|s2Y0j2eKWioo{sGs>}GdAu>mYR{sbnaKa>Kz`ow?$b@}7q z3{p-xW4=wFvk;Q8TwuTJJMtDjpMYenVv%RDn`$xulZBq_96&-Lteh;%OK~ATq|$_a zz;QB`o`e3wL(z!clssDikyC@TDn!T#>4PWHRUHx3H36G|SCjjXFkWyv2KZiO_Cq%^~W$FbKL zvyYDnOwL1&pS(aPge8fbAPMJhvNsR6t;0hcj#K|`UnR~(nnDg>ue3qKc3+^t+kb;ehO-t8l$+R^?;Z>$iaoV4w)-8e_rvdGSJ$<)xJ!uHJ zZLDEyLtG)~8VV-#K=yw8dn#-XFE;=e!=edj)s)+%X&7GfmB`cpK<<0r+kK!G7_f(i zJd0{^$||6_UT;nTD8;US`nphxEj<06Q0niW>FT9Da^r}aR%e9)P&MbK%tblH-uz5- zbym~aRnPRYk;~Utlwn8=s_0hQv&u+zIx?0RIOgQ6fpN0C$c&bb384Qj_93TH0Oj={ zJj5_^MSF2^2nOVaF-MC;hEWUe9u#G44)!HGd$q_QQZW3r)tt0fbC%pb11ILT(S*&vmsp`D=|JNf-c69^CNgSs*8cxM;)NV&a&gPK7|pxA}$yV=j*-@=~#ep9=}&l}uQ z9}>!`3y9-KZMOBfU7-X!_3ZW*_y-XjVp(?Gvyl~`w>-OBNvI2=jEF^ex*t(RAc+Nr zL%s54Hu`K=B!E3}^2AEGY~ypim}=;`wr#Z(MDsJoE*|N;GC<~rLq`vtIJ6?Ak8?_2 zey%NCO9>WaSe(%A0HG68qX$=}v*-Cfa5VXRM;#sBB<6G`0(AKC#Y+4v!BU0F+O;Zc zNAm@zSjT3hl)hac z2WBt45V>5aBVquQ(qOk8-_CYl=s;_1lVoJ_#gQm9BROFb`iJ2Lde*HAYT=MvrlC0_9KirWLYMroPekThV%ws6< zsg$5zdTATG^T$nNWNkgR;+jrsZ&1nIh>L12;yKJ|?jgxR!bpSSGT5#k?x;|wP4otI zvQb?_sY8VbR(6O^3-rJcWr47d^P@^E7v4kXRVG!tcvn<6T}>i}?u}@Xy-9*n;i;&v z!RHVd-HR?CDp~97@q`>1i74FKNAMJj^xBTUt4j(I?<&iAO!5! zVSqZAyPQK07j0pFJX^#=-Zo|t*%~_s(~$zZ=O=x$D1fS+N;qy1;bY>eqYT%(0WexE z#pOJTHFUy*NI4$pfdt;%(man~11!>*ThB*YwEPl3{deEl$9~$b*`6x8=_=VZW!}fi z*|=4J33>ft{u)=5QmWvbk&;^E9Zs1&p4h)Gq9zei*^cC!*r}g&^(J{BCKA<-v(Ydt z!hV(9u^L0!b3dDoVT{Nxs!jjevrnly zwlq0Quh@|0@J3CxTAW#dL#!A`g}s)EXK=s!d9;@XQVX_*HaSS{h{-3$*^R%r2@T?! zjR5!594Q0Qsvn(g7;UUPn&xg8joJvbacbgvV&UmMQ*db3I4P_L$#V#Ik#=zqD%u+#n$(G zHx^34J8Ks$UjOkW>|y`%tBu?9D^5+*?ZtHHq=0^a9E!`=B3HpT-)En$nuaS}zaN+o z#urS&N2>h#XokALk9;gdpV*vZn^G0Y3d~eM`bwFCUh(4~ER72)sEBUy60Zq{hTELM zNYn$~y&395LMkH#{KU{>!&Z1{t{#ODai)W=yYH$vg!cFDmm^I@;)ZG}a<`}E*Rk=r z=8C5s&tT@wyTE)gyacUMDU&5i9INFEylyD-+lQ#N`gMA!JdU7S|1{IrC05ljU ze^fI2g63guobR*(UJvl^@Bwsk=FWp+!z)Jujn9bS2S=ZpIT8`6EKZGvbr!qSJ%B0V z(}}$(Gi#3Sz15LvV`E5ORRL`BBj>1RJS3Uxf8Bxv&Xv-y+pjFaJpJ<#x2r@D&yAF2 z3)r`w??srWr^$&OYxu+Yk9JE{tt0Q{b({Ql9JOn@t|FI4iZ`oK0+EKqkV^UV8ybwT z>{*vTv=nzXM6+4gH|l5~>C^vl-lhNx#S1+w|D~p4?_?94@bNY=l`?Qqkh6gnjL>n zvoF7S^eZ{l23h$7>Sg7AAXx?W{;Wrc;MjZLC5%E91@lE2L+b^Z=8~xwDN4EMCbV#!zujVE{c0 zKRVfgAW!>v_iwi&DTQ5bQYT>=aIEAe4TatE()P86h7%XI{*|lH4bWcch~6xwA-ARa z_Yiot$8XN~ZFYgi{`M7W;C%k$?dHapcK=`P$77^@Mg;99psy>|gQY7>D*K zgH?0ap0Cc1G?e!m;3fbSZ3lrAJUP~7m`YtN>*waf;rC8G&#&(e(qF>OuN#crC|cqMAI zdOJczk=IgUh+5S0Lg-;MAOvF&QruexcH{aO_j3I1klTKsRx#CJ^_drMA?LM%oX3qY zQbalzfrWM;{f06)s?7~EQ*WMgyw%!Zo^uq7vYvUKsb)@Y%1u`XxaC=RaGR0tAyWdD zM_CO`sw3Eu;n6a%Hk|qj$%|*4vmOFa*m9Ea{*0x~f?z_OQ--V3T3n>~1ruw60heVF z>U!5EcmRT{I(Ky_w2AxJ)=hrZhW;)FA*!644~1HwzeG3*!Z^1GP&s#RD73jZ@KqGE z0^q999GWv{&;2qK>XK+%_~k8}m=KNu_cOPGNt~Nl6D55%#7?I~SSIYxLM%9JL+Xs%EI59CGZeE;wVpV8+cYGw` z_E=+dZ6a{DljwmXLoTfiZLg5OLCx-y9~an^Dm9Go zD?BOzsjRLJJI06c{0fI}3*<)aVY`}}Ao>|2?#xhh%AqVW@JgU|YZ>Do!U`-jmOr+e>n3ra8B$;Hz>ujt(W4M#8(M;g#z#!KV;#=r{AOFCYF4@_z@V(p|7rDFy7yF?UiWOzvueT8x` ze1zOE>C_Bh!J7!^C;}oU@Wr11&miv$HnBu{G)Y34 z;%7=`rSYcPG>UWo%A4Okg(MDvO(!oA4aAsq1aD4a1`70D3 zI}jOR4^1|2pKSBSjoTylwr#w9q<`zyWU-i=E6hj28~&R=C$avuoswB7 z?o<5x5(fXH8)>6x?3-8o@lrNcOqp59%o%yvzsl}kzm|30+QjD8JDaGDnd^gPda$__ zJTGf`e^$#Fe$}s~Q(D14>Zd`WqIpXWe-CN}Lz3&K#I$t!khSEby-8OJ)IEdg8nNUL zsH%Tou2kH2)IT!f->i>z1*R=w2|9j0(q07o#Y%;unXwz#bCpY)h!%V5-?~|G?Q*v5 zv)kC%zh1KU)nRCH*WiEv3KL-lkuiKZR;`#uv+WhfA%foGc#MZd}fCI`sv#@QM-qB z1)LL=SLu*6Rm&zyX)O8bR|b3-^#V!|Co1M*v4SE#Xt zS{02wcBa>tVE;7TmsU=NQibVEuDBxEv*eKxp{0JiMFGc!o3*%sF&U$nG<7**U?F!4 z@~XvR6x>;BCY04uJ{^v&%kp}dhO@xg?(il96gq*hM&s6H#aP!uF*6%aB}zgY)#xON zQ)iX`P+FrFCq(Ny@ScsOOS%DJfXAYF%sg#dfV|FU8S2=F$S4bu@NHwajg6%yiNaL0 zV5Un&Ijk5CB4z_5af>FGVJ$|N(UHDpCnRCUJO@&MhHMX{h6E6|6Cu4-XxgeJqvxaI1u#cJ}Zv(cN3r4rO=mVNn5%WiF6HDCrp z^jauy^$CM!pFh*v<<~>4;6(%oqN%`6&-Rw< zn`_2_6X6eLmW_NqC3vZ85(-9=KF*zoXRyN_#6fb8xh`vNzFpB|Tl!J<__Gt`O_qx} zO1e-mv$;Cm;H}V$3FrlX#%96E4ScY7d8-==f?cHwHj`PwGk10D_OCa4bp+~B&ymG( zpjt^!h2UFsxG85z_0Ztz$U`q2w&>$@mP8udT50$+tN2a^^N-**!9o-Eqm>` zpCXRE@ZWpbm!E07I11gY9qc&&DBHQ()dtPCTnGz)Q-&+L@tq!a|L?bUrtUQKdD;_> zg4SVoKi{1qmmrJ}N0dvaic&BXL_k0Qs11M=KvP<}Dh6WX#vH5%UdT}CiGs=8sZd&j zht}nJ)ln7J@%$kBhhJ>tG;JKy`rc_T{;I2Jcr7P%{pwA9%C`^ii z;xRKwR0Ukh)emrnLm;(8u^%!gLRTCf9PBvtn5qYB#eCZT%d{k71LhmLF3Si z{r%j1-DDJUX0~8VQ&1=BHT6r#bN1fI1;gNexsiIkqWC4B=KE*4vc<|**F0)@f+k`4Q=`sw{ z`)XmshhFxVxsnKtimOCfN$@|D2Pp966D2zTLMUxy6UAf%8BJJ9sIX8~mpP(O){vfy z&D*+`Tw9Yi=?0dQ70ZI-({|cjt%MfBj|1*&;3FPMNKt@#a`dEMBBeQKrqc#b+L`Zr z{cPfu-rlGZ7>7f5#~cSnP21gV=$_!+2z{^gcRMB`Vssqd8o=h&z7{-?V_S-Mu}5F& z8rS$v6SX_l=N0%Td$);^5err(*ojyBp>`Kv)3+@tOa~ls%AK``isky7IUJ&e8*rNM zT(f6~Colk)QqQy+;7BNr6SBQcEv=BZ)AHlqCJ@hjpAs2wqeo%tunFb5gaG0$KQfx) z?D_9s<%_e}C1OtQrhXBIn#YCX&PqbW6&Ps=+aU8vIQvnNc;n&i&v}*3YSk#MiR4 zk&*oq(8UaU{DvW4hW+w~!G@6$)_Qbkh?_BH*Uym^Hv0oV%lxQoXH4>RLoONiz>hAG z4P{k8_Hgj}>61Z4xYh8YVtLwd8afXs5@v569Sr{8quJ1p_xcPr_2d5cRntKEh|{># zLu2-q_gXQBf)XD->>WOH^QPn3>p#9^zh~Ta)5oSyPQS-qXkT@d4^K~Bx7m31;h*ds z^H4r{)6~KDB5dZUR#xD&A%WL^GSFQk0a6^^J?kMkJ9YBl;my`w2dot8pKukL)E{LS zTjkZPaoP{C!v6ZFid$@CPyI9z@{Hoj=+baFhH< zy9&Rr{A`&2?q<14Z!5PW_+m5?Dn6ZLibZY-_?UMloy0034-eE^`GB|3e);8 zj^p>)UtH$XAN=LXTMSX!Lb1!tOJtdbs>5dB+s7y4R~eSipNx-=%Fz&&{HPuP875i*`~-H4BcO~1 z)!=BHS}CQ2oq@B#+qjh{5Vm#Wj+wX=j0920i3G#Jk)S-93N4tatVGY02GqGs;G$wu zO$fz_y5&V~{Mgi&ULPJq$n_K$4KU)C8^Mi@+jvll%|$Cy+kCrPR9(R{4@p}ij(qeVbqpxgBsewRTL2g%% z2$5%sh?WL2;HjgNE5eF0q7mzQTeH|tDNFYZr$WnG8g(W-=9eQai&+!??i~C9PIcYbt3dSCt3I*n_}{Gb%%)NW{LkhTF__BV923 z;x()$Y!$RnA;nOFxF<@PsF6onXP034D~zX@%$sn7YVfbj5*tV#XbdWG_UK!GIUMKl zx4QBYs~aC=H~eN0a3cPjxiDssBT9Ix?A04gAadeVc_UBHoorrvWav^Qp%}7Kzy?TN z0u!?9n3Ck|oElG&P5riKAVoVByVO0NNQYb=Hv8DeetYRKJPEB>O>2SXkr7j6C&m{4 z=R;R=hFl37f=%KCCQ&_mv2J#3^Vzg^-R!hI+w!KU&chaLE6Jcd0elQ)v|F;g1v?V8 zp6)T!U1C62$l!2;W{<)=B}ffHct|a@*W_LlK8GhyjZiZrQNeK{?us-6&&eZEOw|d% zK{=>Ae!G8Zc|0oU{r7EoCA#D6f!hy0WgbO8Ft<8sV zqNH|S=p3PPFm5iuu~TS^DXaYYL`OskFPkad?+!v)%88RXV%st-yGSjUt&O-r(aI<^ z4=|8dAs4#*q%ef^-)qJi=R2@N-+qH0Zhans2hI8Emd&dbyb36k1vWP|H4EZ}=9*aEMBM+)de0gp0}n-ImCgj?MaWsHa3Z8|VPs<)sLG)RF$ z5^UR~fk3Lud$8;UEJh`{-GAIZSR0oXVBSbn`54M>BOlqp;(zRGq&b#@xJX5gOz-Hg zjQM=cwpb1#fwMc}LLtH#r@L?2a4X8MH}XXSWeg_r23% zw_k6ye{#t;V7DE1$9A(<-f6YlkJ#7GS7$LW}&jwJ~{p6UoM z^)67IKv!l^P4FBOS&(&1;>=Opph6UT=-rkf98roNQ4$j=4B1DNsuJzELhpowAd?1f5@=<8{OUR17V^eHI&XrJz{tT*~Pj^N^vYT1G> z!B7o1FftjftT*}k%S)=`Vx9Afg($SahsRKI zrY{2QB5+*216OH+qZk#N5!g6nbT9F#k{1k8DGzW|it`#EzZwz2FK(kkN~lp9uXH#2 zZrV%dNzf*v<)pY9R=|**O1iey5-CU!m2k5$#gf|Yk+>X22eeG1Edx2olSEQf5J43- zN7F3l*B&vEhT>+*rG=3;UtF0a)fBPEu1cN>)MT8`qwTo0w%;m}Q9)oKl@xmMHFIY_nYc z^KDQpA46ax8?;@|q`TkdwKY#bs&sDiH8mF`YU21LP63GIxsM##BfFba>cT^hVoH=Y zCzKL^Ev^8ZNtKtk`8tO^y&{_w4b~x-fW1yvcDDO=`<<#TpsE;?0?BG^zBIg`EW7%< zVCydx_E%7_zSA{eh`DIrO3BI;4&w1=yKw1l2b8IZv;q{#ajIw9ot2-o`$qO?k)Tzq z4AULI=Zigp@6n2~-M0r{IaqaQa6A>MGjCeoxdKz}#H)2RW+-`OMV?!k$LC&_r7tU| z1q!f!=vfkxqnypHv#mX*I7=XE&D#55)7jZP zec6mpQ|VHH;^ZOwJmYi8d9-;iyOF0-m^9GiEP`jycf?|>lUmHsj%HN?er`RzZ04=+ zqvqU3Gw)m$%u>};e06|o5h_;n1(Gxam!-_`?;-3p^9ZJBDj50Y6mBLhT|FUMPp67W zLB#GjE;`JzSDrIgZZ<>mwnrwB9g38Vr~`>aWI53smPu9%Oc za-PzTJv}G5L($1o^dUS?pC?#bEATPQ_w=r-?4}ZLvWr2yyoQgl*6~&K>``As_m<5* m=C6FU+xLDyeceU3+12BdTMo7P(Dc7Ghq!M5 From 2596035066a31da9ea30d0f9816e24428162baf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Sun, 25 Dec 2016 21:33:43 +0200 Subject: [PATCH 24/55] chore(docs) Add new example production site --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4f3414a8..4a96a815 100644 --- a/README.md +++ b/README.md @@ -164,6 +164,7 @@ Each time, there're more Production WebApps using `Restangular`. If your webapp * **Life360** is using Restangular to build the WebApp version of their platform * **Thomson Reuters** is using Restangular for the new Webapp they've built * **Quran.com** is using Restangular for their alpha/beta app and soon to be main site +* **[ENTSO-E Transparency Platform](https://transparency.entsoe.eu)** **[Back to top](#table-of-contents)** From e0b68a021ad9e4f7fc34d66cad4c24af1864ba18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Sun, 25 Dec 2016 22:50:14 +0200 Subject: [PATCH 25/55] chore(dependencies): Update lodash version to ~4.17.0 as in unit tests --- bower.json | 2 +- package.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bower.json b/bower.json index 2d7bd12b..ee38a810 100644 --- a/bower.json +++ b/bower.json @@ -7,7 +7,7 @@ "url": "git://github.com/mgonto/restangular.git" }, "dependencies": { - "lodash": ">=1.3.0", + "lodash": "~4.17.0", "angular": "~1.x" }, "ignore": [ diff --git a/package.json b/package.json index 7f085cd1..691bc3f6 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ } ], "dependencies": { - "lodash": ">=1.3.0" + "lodash": "~4.17.0" }, "peerDependencies": { "angular": ">=1.3.12" @@ -57,4 +57,4 @@ "test": "grunt test --verbose" }, "license": "MIT" -} \ No newline at end of file +} From bc16122bacec9a2981cee838d3eb3056440a6285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Mon, 26 Dec 2016 11:06:23 +0200 Subject: [PATCH 26/55] chore(docs) Apply automatic formatting to code and spec --- src/restangular.js | 2302 ++++++++++++++++++++------------------- test/restangularSpec.js | 375 ++++--- 2 files changed, 1399 insertions(+), 1278 deletions(-) diff --git a/src/restangular.js b/src/restangular.js index 6dc3e114..64a08df2 100644 --- a/src/restangular.js +++ b/src/restangular.js @@ -1,4 +1,5 @@ -(function (root, factory) { +(function(root, factory) { + /* global define, require */ // https://github.com/umdjs/umd/blob/master/templates/returnExports.js if (typeof define === 'function' && define.amd) { define(['lodash', 'angular'], factory); @@ -8,1395 +9,1418 @@ // No global export, Restangular will register itself as Angular.js module factory(root._, root.angular); } -}(this, function (_, angular) { - -var restangular = angular.module('restangular', []); - -restangular.provider('Restangular', function() { - // Configuration - var Configurer = {}; - Configurer.init = function(object, config) { - object.configuration = config; - - /** - * Those are HTTP safe methods for which there is no need to pass any data with the request. - */ - var safeMethods= ['get', 'head', 'options', 'trace', 'getlist']; - config.isSafe = function(operation) { - return _.includes(safeMethods, operation.toLowerCase()); - }; +}(this, function(_, angular) { + + var restangular = angular.module('restangular', []); + + restangular.provider('Restangular', function() { + // Configuration + var Configurer = {}; + Configurer.init = function(object, config) { + object.configuration = config; + + /** + * Those are HTTP safe methods for which there is no need to pass any data with the request. + */ + var safeMethods = ['get', 'head', 'options', 'trace', 'getlist']; + config.isSafe = function(operation) { + return _.includes(safeMethods, operation.toLowerCase()); + }; - var absolutePattern = /^https?:\/\//i; - config.isAbsoluteUrl = function(string) { - return _.isUndefined(config.absoluteUrl) || _.isNull(config.absoluteUrl) ? - string && absolutePattern.test(string) : - config.absoluteUrl; - }; + var absolutePattern = /^https?:\/\//i; + config.isAbsoluteUrl = function(string) { + return _.isUndefined(config.absoluteUrl) || _.isNull(config.absoluteUrl) ? + string && absolutePattern.test(string) : + config.absoluteUrl; + }; - config.absoluteUrl = _.isUndefined(config.absoluteUrl) ? true : config.absoluteUrl; - object.setSelfLinkAbsoluteUrl = function(value) { - config.absoluteUrl = value; - }; - /** - * This is the BaseURL to be used with Restangular - */ - config.baseUrl = _.isUndefined(config.baseUrl) ? '' : config.baseUrl; - object.setBaseUrl = function(newBaseUrl) { - config.baseUrl = /\/$/.test(newBaseUrl) ? - newBaseUrl.substring(0, newBaseUrl.length-1) : - newBaseUrl; - return this; - }; + config.absoluteUrl = _.isUndefined(config.absoluteUrl) ? true : config.absoluteUrl; + object.setSelfLinkAbsoluteUrl = function(value) { + config.absoluteUrl = value; + }; + /** + * This is the BaseURL to be used with Restangular + */ + config.baseUrl = _.isUndefined(config.baseUrl) ? '' : config.baseUrl; + object.setBaseUrl = function(newBaseUrl) { + config.baseUrl = /\/$/.test(newBaseUrl) ? + newBaseUrl.substring(0, newBaseUrl.length - 1) : + newBaseUrl; + return this; + }; - /** - * Sets the extra fields to keep from the parents - */ - config.extraFields = config.extraFields || []; - object.setExtraFields = function(newExtraFields) { - config.extraFields = newExtraFields; - return this; - }; + /** + * Sets the extra fields to keep from the parents + */ + config.extraFields = config.extraFields || []; + object.setExtraFields = function(newExtraFields) { + config.extraFields = newExtraFields; + return this; + }; - /** - * Some default $http parameter to be used in EVERY call - **/ - config.defaultHttpFields = config.defaultHttpFields || {}; - object.setDefaultHttpFields = function(values) { - config.defaultHttpFields = values; - return this; - }; + /** + * Some default $http parameter to be used in EVERY call + **/ + config.defaultHttpFields = config.defaultHttpFields || {}; + object.setDefaultHttpFields = function(values) { + config.defaultHttpFields = values; + return this; + }; - /** - * Always return plain data, no restangularized object - **/ - config.plainByDefault = config.plainByDefault || false; - object.setPlainByDefault = function(value) { - config.plainByDefault = value === true ? true : false; - return this; - }; + /** + * Always return plain data, no restangularized object + **/ + config.plainByDefault = config.plainByDefault || false; + object.setPlainByDefault = function(value) { + config.plainByDefault = value === true ? true : false; + return this; + }; - config.withHttpValues = function(httpLocalConfig, obj) { - return _.defaults(obj, httpLocalConfig, config.defaultHttpFields); - }; + config.withHttpValues = function(httpLocalConfig, obj) { + return _.defaults(obj, httpLocalConfig, config.defaultHttpFields); + }; - config.encodeIds = _.isUndefined(config.encodeIds) ? true : config.encodeIds; - object.setEncodeIds = function(encode) { - config.encodeIds = encode; - }; + config.encodeIds = _.isUndefined(config.encodeIds) ? true : config.encodeIds; + object.setEncodeIds = function(encode) { + config.encodeIds = encode; + }; - config.defaultRequestParams = config.defaultRequestParams || { - get: {}, - post: {}, - put: {}, - remove: {}, - common: {} - }; + config.defaultRequestParams = config.defaultRequestParams || { + get: {}, + post: {}, + put: {}, + remove: {}, + common: {} + }; - object.setDefaultRequestParams = function(param1, param2) { - var methods = [], + object.setDefaultRequestParams = function(param1, param2) { + var methods = [], params = param2 || param1; - if (!_.isUndefined(param2)) { - if (_.isArray(param1)) { - methods = param1; + if (!_.isUndefined(param2)) { + if (_.isArray(param1)) { + methods = param1; + } else { + methods.push(param1); + } } else { - methods.push(param1); + methods.push('common'); } - } else { - methods.push('common'); - } - _.each(methods, function (method) { - config.defaultRequestParams[method] = params; - }); - return this; - }; + _.each(methods, function(method) { + config.defaultRequestParams[method] = params; + }); + return this; + }; - object.requestParams = config.defaultRequestParams; + object.requestParams = config.defaultRequestParams; - config.defaultHeaders = config.defaultHeaders || {}; - object.setDefaultHeaders = function(headers) { - config.defaultHeaders = headers; - object.defaultHeaders = config.defaultHeaders; - return this; - }; - - object.defaultHeaders = config.defaultHeaders; + config.defaultHeaders = config.defaultHeaders || {}; + object.setDefaultHeaders = function(headers) { + config.defaultHeaders = headers; + object.defaultHeaders = config.defaultHeaders; + return this; + }; - /** - * Method overriders will set which methods are sent via POST with an X-HTTP-Method-Override - **/ - config.methodOverriders = config.methodOverriders || []; - object.setMethodOverriders = function(values) { - var overriders = _.extend([], values); - if (config.isOverridenMethod('delete', overriders)) { - overriders.push('remove'); - } - config.methodOverriders = overriders; - return this; - }; + object.defaultHeaders = config.defaultHeaders; - config.jsonp = _.isUndefined(config.jsonp) ? false : config.jsonp; - object.setJsonp = function(active) { - config.jsonp = active; - }; + /** + * Method overriders will set which methods are sent via POST with an X-HTTP-Method-Override + **/ + config.methodOverriders = config.methodOverriders || []; + object.setMethodOverriders = function(values) { + var overriders = _.extend([], values); + if (config.isOverridenMethod('delete', overriders)) { + overriders.push('remove'); + } + config.methodOverriders = overriders; + return this; + }; - config.isOverridenMethod = function(method, values) { - var search = values || config.methodOverriders; - return !_.isUndefined(_.find(search, function(one) { - return one.toLowerCase() === method.toLowerCase(); - })); - }; + config.jsonp = _.isUndefined(config.jsonp) ? false : config.jsonp; + object.setJsonp = function(active) { + config.jsonp = active; + }; - /** - * Sets the URL creator type. For now, only Path is created. In the future we'll have queryParams - **/ - config.urlCreator = config.urlCreator || 'path'; - object.setUrlCreator = function(name) { - if (!_.has(config.urlCreatorFactory, name)) { - throw new Error('URL Path selected isn\'t valid'); - } + config.isOverridenMethod = function(method, values) { + var search = values || config.methodOverriders; + return !_.isUndefined(_.find(search, function(one) { + return one.toLowerCase() === method.toLowerCase(); + })); + }; - config.urlCreator = name; - return this; - }; + /** + * Sets the URL creator type. For now, only Path is created. In the future we'll have queryParams + **/ + config.urlCreator = config.urlCreator || 'path'; + object.setUrlCreator = function(name) { + if (!_.has(config.urlCreatorFactory, name)) { + throw new Error('URL Path selected isn\'t valid'); + } - /** - * You can set the restangular fields here. The 3 required fields for Restangular are: - * - * id: Id of the element - * route: name of the route of this element - * parentResource: the reference to the parent resource - * - * All of this fields except for id, are handled (and created) by Restangular. By default, - * the field values will be id, route and parentResource respectively - */ - config.restangularFields = config.restangularFields || { - id: 'id', - route: 'route', - parentResource: 'parentResource', - restangularCollection: 'restangularCollection', - cannonicalId: '__cannonicalId', - etag: 'restangularEtag', - selfLink: 'href', - get: 'get', - getList: 'getList', - put: 'put', - post: 'post', - remove: 'remove', - head: 'head', - trace: 'trace', - options: 'options', - patch: 'patch', - getRestangularUrl: 'getRestangularUrl', - getRequestedUrl: 'getRequestedUrl', - putElement: 'putElement', - addRestangularMethod: 'addRestangularMethod', - getParentList: 'getParentList', - clone: 'clone', - ids: 'ids', - httpConfig: '_$httpConfig', - reqParams: 'reqParams', - one: 'one', - all: 'all', - several: 'several', - oneUrl: 'oneUrl', - allUrl: 'allUrl', - customPUT: 'customPUT', - customPATCH: 'customPATCH', - customPOST: 'customPOST', - customDELETE: 'customDELETE', - customGET: 'customGET', - customGETLIST: 'customGETLIST', - customOperation: 'customOperation', - doPUT: 'doPUT', - doPATCH: 'doPATCH', - doPOST: 'doPOST', - doDELETE: 'doDELETE', - doGET: 'doGET', - doGETLIST: 'doGETLIST', - fromServer: 'fromServer', - withConfig: 'withConfig', - withHttpConfig: 'withHttpConfig', - singleOne: 'singleOne', - plain: 'plain', - save: 'save', - restangularized: 'restangularized' - }; - object.setRestangularFields = function(resFields) { - config.restangularFields = - _.extend(config.restangularFields, resFields); - return this; - }; + config.urlCreator = name; + return this; + }; - config.isRestangularized = function(obj) { - return !!obj[config.restangularFields.restangularized]; - }; + /** + * You can set the restangular fields here. The 3 required fields for Restangular are: + * + * id: Id of the element + * route: name of the route of this element + * parentResource: the reference to the parent resource + * + * All of this fields except for id, are handled (and created) by Restangular. By default, + * the field values will be id, route and parentResource respectively + */ + config.restangularFields = config.restangularFields || { + id: 'id', + route: 'route', + parentResource: 'parentResource', + restangularCollection: 'restangularCollection', + cannonicalId: '__cannonicalId', + etag: 'restangularEtag', + selfLink: 'href', + get: 'get', + getList: 'getList', + put: 'put', + post: 'post', + remove: 'remove', + head: 'head', + trace: 'trace', + options: 'options', + patch: 'patch', + getRestangularUrl: 'getRestangularUrl', + getRequestedUrl: 'getRequestedUrl', + putElement: 'putElement', + addRestangularMethod: 'addRestangularMethod', + getParentList: 'getParentList', + clone: 'clone', + ids: 'ids', + httpConfig: '_$httpConfig', + reqParams: 'reqParams', + one: 'one', + all: 'all', + several: 'several', + oneUrl: 'oneUrl', + allUrl: 'allUrl', + customPUT: 'customPUT', + customPATCH: 'customPATCH', + customPOST: 'customPOST', + customDELETE: 'customDELETE', + customGET: 'customGET', + customGETLIST: 'customGETLIST', + customOperation: 'customOperation', + doPUT: 'doPUT', + doPATCH: 'doPATCH', + doPOST: 'doPOST', + doDELETE: 'doDELETE', + doGET: 'doGET', + doGETLIST: 'doGETLIST', + fromServer: 'fromServer', + withConfig: 'withConfig', + withHttpConfig: 'withHttpConfig', + singleOne: 'singleOne', + plain: 'plain', + save: 'save', + restangularized: 'restangularized' + }; + object.setRestangularFields = function(resFields) { + config.restangularFields = + _.extend(config.restangularFields, resFields); + return this; + }; - config.setFieldToElem = function(field, elem, value) { - var properties = field.split('.'); - var idValue = elem; - _.each(_.initial(properties), function(prop) { - idValue[prop] = {}; - idValue = idValue[prop]; - }); - idValue[_.last(properties)] = value; - return this; - }; + config.isRestangularized = function(obj) { + return !!obj[config.restangularFields.restangularized]; + }; - config.getFieldFromElem = function(field, elem) { - var properties = field.split('.'); - var idValue = elem; - _.each(properties, function(prop) { - if (idValue) { + config.setFieldToElem = function(field, elem, value) { + var properties = field.split('.'); + var idValue = elem; + _.each(_.initial(properties), function(prop) { + idValue[prop] = {}; idValue = idValue[prop]; - } - }); - return angular.copy(idValue); - }; - - config.setIdToElem = function(elem, id /*, route */) { - config.setFieldToElem(config.restangularFields.id, elem, id); - return this; - }; + }); + idValue[_.last(properties)] = value; + return this; + }; - config.getIdFromElem = function(elem) { - return config.getFieldFromElem(config.restangularFields.id, elem); - }; + config.getFieldFromElem = function(field, elem) { + var properties = field.split('.'); + var idValue = elem; + _.each(properties, function(prop) { + if (idValue) { + idValue = idValue[prop]; + } + }); + return angular.copy(idValue); + }; - config.isValidId = function(elemId) { - return '' !== elemId && !_.isUndefined(elemId) && !_.isNull(elemId); - }; + config.setIdToElem = function(elem, id /*, route */ ) { + config.setFieldToElem(config.restangularFields.id, elem, id); + return this; + }; - config.setUrlToElem = function(elem, url /*, route */) { - config.setFieldToElem(config.restangularFields.selfLink, elem, url); - return this; - }; + config.getIdFromElem = function(elem) { + return config.getFieldFromElem(config.restangularFields.id, elem); + }; - config.getUrlFromElem = function(elem) { - return config.getFieldFromElem(config.restangularFields.selfLink, elem); - }; + config.isValidId = function(elemId) { + return '' !== elemId && !_.isUndefined(elemId) && !_.isNull(elemId); + }; - config.useCannonicalId = _.isUndefined(config.useCannonicalId) ? false : config.useCannonicalId; - object.setUseCannonicalId = function(value) { - config.useCannonicalId = value; - return this; - }; + config.setUrlToElem = function(elem, url /*, route */ ) { + config.setFieldToElem(config.restangularFields.selfLink, elem, url); + return this; + }; - config.getCannonicalIdFromElem = function(elem) { - var cannonicalId = elem[config.restangularFields.cannonicalId]; - var actualId = config.isValidId(cannonicalId) ? cannonicalId : config.getIdFromElem(elem); - return actualId; - }; + config.getUrlFromElem = function(elem) { + return config.getFieldFromElem(config.restangularFields.selfLink, elem); + }; - /** - * Sets the Response parser. This is used in case your response isn't directly the data. - * For example if you have a response like {meta: {'meta'}, data: {name: 'Gonto'}} - * you can extract this data which is the one that needs wrapping - * - * The ResponseExtractor is a function that receives the response and the method executed. - */ + config.useCannonicalId = _.isUndefined(config.useCannonicalId) ? false : config.useCannonicalId; + object.setUseCannonicalId = function(value) { + config.useCannonicalId = value; + return this; + }; - config.responseInterceptors = config.responseInterceptors || []; + config.getCannonicalIdFromElem = function(elem) { + var cannonicalId = elem[config.restangularFields.cannonicalId]; + var actualId = config.isValidId(cannonicalId) ? cannonicalId : config.getIdFromElem(elem); + return actualId; + }; - config.defaultResponseInterceptor = function(data /*, operation, what, url, response, deferred */) { - return data; - }; + /** + * Sets the Response parser. This is used in case your response isn't directly the data. + * For example if you have a response like {meta: {'meta'}, data: {name: 'Gonto'}} + * you can extract this data which is the one that needs wrapping + * + * The ResponseExtractor is a function that receives the response and the method executed. + */ - config.responseExtractor = function(data, operation, what, url, response, deferred) { - var interceptors = angular.copy(config.responseInterceptors); - interceptors.push(config.defaultResponseInterceptor); - var theData = data; - _.each(interceptors, function(interceptor) { - theData = interceptor(theData, operation, - what, url, response, deferred); - }); - return theData; - }; + config.responseInterceptors = config.responseInterceptors || []; - object.addResponseInterceptor = function(extractor) { - config.responseInterceptors.push(extractor); - return this; - }; + config.defaultResponseInterceptor = function(data /*, operation, what, url, response, deferred */ ) { + return data; + }; - config.errorInterceptors = config.errorInterceptors || []; - object.addErrorInterceptor = function(interceptor) { - config.errorInterceptors.push(interceptor); - return this; - }; + config.responseExtractor = function(data, operation, what, url, response, deferred) { + var interceptors = angular.copy(config.responseInterceptors); + interceptors.push(config.defaultResponseInterceptor); + var theData = data; + _.each(interceptors, function(interceptor) { + theData = interceptor(theData, operation, + what, url, response, deferred); + }); + return theData; + }; - object.setResponseInterceptor = object.addResponseInterceptor; - object.setResponseExtractor = object.addResponseInterceptor; - object.setErrorInterceptor = object.addErrorInterceptor; + object.addResponseInterceptor = function(extractor) { + config.responseInterceptors.push(extractor); + return this; + }; - /** - * Response interceptor is called just before resolving promises. - */ + config.errorInterceptors = config.errorInterceptors || []; + object.addErrorInterceptor = function(interceptor) { + config.errorInterceptors.push(interceptor); + return this; + }; + object.setResponseInterceptor = object.addResponseInterceptor; + object.setResponseExtractor = object.addResponseInterceptor; + object.setErrorInterceptor = object.addErrorInterceptor; - /** - * Request interceptor is called before sending an object to the server. - */ - config.requestInterceptors = config.requestInterceptors || []; + /** + * Response interceptor is called just before resolving promises. + */ - config.defaultInterceptor = function(element, operation, path, url, headers, params, httpConfig) { - return { - element: element, - headers: headers, - params: params, - httpConfig: httpConfig - }; - }; - config.fullRequestInterceptor = function(element, operation, path, url, headers, params, httpConfig) { - var interceptors = angular.copy(config.requestInterceptors); - var defaultRequest = config.defaultInterceptor(element, operation, path, url, headers, params, httpConfig); - return _.reduce(interceptors, function(request, interceptor) { - return _.extend(request, interceptor(request.element, operation, - path, url, request.headers, request.params, request.httpConfig)); - }, defaultRequest); - }; + /** + * Request interceptor is called before sending an object to the server. + */ + config.requestInterceptors = config.requestInterceptors || []; - object.addRequestInterceptor = function(interceptor) { - config.requestInterceptors.push(function(elem, operation, path, url, headers, params, httpConfig) { + config.defaultInterceptor = function(element, operation, path, url, headers, params, httpConfig) { return { + element: element, headers: headers, params: params, - element: interceptor(elem, operation, path, url), httpConfig: httpConfig }; - }); - return this; - }; - - object.setRequestInterceptor = object.addRequestInterceptor; + }; - object.addFullRequestInterceptor = function(interceptor) { - config.requestInterceptors.push(interceptor); - return this; - }; + config.fullRequestInterceptor = function(element, operation, path, url, headers, params, httpConfig) { + var interceptors = angular.copy(config.requestInterceptors); + var defaultRequest = config.defaultInterceptor(element, operation, path, url, headers, params, httpConfig); + return _.reduce(interceptors, function(request, interceptor) { + return _.extend(request, interceptor(request.element, operation, + path, url, request.headers, request.params, request.httpConfig)); + }, defaultRequest); + }; - object.setFullRequestInterceptor = object.addFullRequestInterceptor; + object.addRequestInterceptor = function(interceptor) { + config.requestInterceptors.push(function(elem, operation, path, url, headers, params, httpConfig) { + return { + headers: headers, + params: params, + element: interceptor(elem, operation, path, url), + httpConfig: httpConfig + }; + }); + return this; + }; - config.onBeforeElemRestangularized = config.onBeforeElemRestangularized || function(elem) { - return elem; - }; - object.setOnBeforeElemRestangularized = function(post) { - config.onBeforeElemRestangularized = post; - return this; - }; + object.setRequestInterceptor = object.addRequestInterceptor; - object.setRestangularizePromiseInterceptor = function(interceptor) { - config.restangularizePromiseInterceptor = interceptor; - return this; - }; + object.addFullRequestInterceptor = function(interceptor) { + config.requestInterceptors.push(interceptor); + return this; + }; - /** - * This method is called after an element has been "Restangularized". - * - * It receives the element, a boolean indicating if it's an element or a collection - * and the name of the model - * - */ - config.onElemRestangularized = config.onElemRestangularized || function(elem) { - return elem; - }; - object.setOnElemRestangularized = function(post) { - config.onElemRestangularized = post; - return this; - }; + object.setFullRequestInterceptor = object.addFullRequestInterceptor; - config.shouldSaveParent = config.shouldSaveParent || function() { - return true; - }; - object.setParentless = function(values) { - if (_.isArray(values)) { - config.shouldSaveParent = function(route) { - return !_.includes(values, route); - }; - } else if (_.isBoolean(values)) { - config.shouldSaveParent = function() { - return !values; - }; - } - return this; - }; + config.onBeforeElemRestangularized = config.onBeforeElemRestangularized || function(elem) { + return elem; + }; + object.setOnBeforeElemRestangularized = function(post) { + config.onBeforeElemRestangularized = post; + return this; + }; - /** - * This lets you set a suffix to every request. - * - * For example, if your api requires that for JSon requests you do /users/123.json, you can set that - * in here. - * - * - * By default, the suffix is null - */ - config.suffix = _.isUndefined(config.suffix) ? null : config.suffix; - object.setRequestSuffix = function(newSuffix) { - config.suffix = newSuffix; - return this; - }; + object.setRestangularizePromiseInterceptor = function(interceptor) { + config.restangularizePromiseInterceptor = interceptor; + return this; + }; - /** - * Add element transformers for certain routes. - */ - config.transformers = config.transformers || {}; - config.matchTransformers = config.matchTransformers || []; - object.addElementTransformer = function(type, secondArg, thirdArg) { - var isCollection = null; - var transformer = null; - if (arguments.length === 2) { - transformer = secondArg; - } else { - transformer = thirdArg; - isCollection = secondArg; - } + /** + * This method is called after an element has been "Restangularized". + * + * It receives the element, a boolean indicating if it's an element or a collection + * and the name of the model + * + */ + config.onElemRestangularized = config.onElemRestangularized || function(elem) { + return elem; + }; + object.setOnElemRestangularized = function(post) { + config.onElemRestangularized = post; + return this; + }; - var transformerFn = function(coll, elem) { - if (_.isNull(isCollection) || (coll === isCollection)) { - return transformer(elem); + config.shouldSaveParent = config.shouldSaveParent || function() { + return true; + }; + object.setParentless = function(values) { + if (_.isArray(values)) { + config.shouldSaveParent = function(route) { + return !_.includes(values, route); + }; + } else if (_.isBoolean(values)) { + config.shouldSaveParent = function() { + return !values; + }; } - return elem; + return this; }; - if (_.isRegExp(type)) { - config.matchTransformers.push({ - regexp: type, - transformer: transformerFn - }); - } else { - if (!config.transformers[type]) { - config.transformers[type] = []; + /** + * This lets you set a suffix to every request. + * + * For example, if your api requires that for JSon requests you do /users/123.json, you can set that + * in here. + * + * + * By default, the suffix is null + */ + config.suffix = _.isUndefined(config.suffix) ? null : config.suffix; + object.setRequestSuffix = function(newSuffix) { + config.suffix = newSuffix; + return this; + }; + + /** + * Add element transformers for certain routes. + */ + config.transformers = config.transformers || {}; + config.matchTransformers = config.matchTransformers || []; + object.addElementTransformer = function(type, secondArg, thirdArg) { + var isCollection = null; + var transformer = null; + if (arguments.length === 2) { + transformer = secondArg; + } else { + transformer = thirdArg; + isCollection = secondArg; } - config.transformers[type].push(transformerFn); - } - return object; - }; + var transformerFn = function(coll, elem) { + if (_.isNull(isCollection) || (coll === isCollection)) { + return transformer(elem); + } + return elem; + }; - object.extendCollection = function(route, fn) { - return object.addElementTransformer(route, true, fn); - }; + if (_.isRegExp(type)) { + config.matchTransformers.push({ + regexp: type, + transformer: transformerFn + }); + } else { + if (!config.transformers[type]) { + config.transformers[type] = []; + } + config.transformers[type].push(transformerFn); + } - object.extendModel = function(route, fn) { - return object.addElementTransformer(route, false, fn); - }; + return object; + }; - config.transformElem = function(elem, isCollection, route, Restangular, force) { - if (!force && !config.transformLocalElements && !elem[config.restangularFields.fromServer]) { - return elem; - } + object.extendCollection = function(route, fn) { + return object.addElementTransformer(route, true, fn); + }; - var changedElem = elem; + object.extendModel = function(route, fn) { + return object.addElementTransformer(route, false, fn); + }; - var matchTransformers = config.matchTransformers; - if (matchTransformers) { - _.each(matchTransformers, function (transformer) { - if (route.match(transformer.regexp)) { - changedElem = transformer.transformer(isCollection, changedElem); - } - }); - } + config.transformElem = function(elem, isCollection, route, Restangular, force) { + if (!force && !config.transformLocalElements && !elem[config.restangularFields.fromServer]) { + return elem; + } - var typeTransformers = config.transformers[route]; - if (typeTransformers) { - _.each(typeTransformers, function(transformer) { - changedElem = transformer(isCollection, changedElem); - }); - } - return config.onElemRestangularized(changedElem, isCollection, route, Restangular); - }; + var changedElem = elem; - config.transformLocalElements = _.isUndefined(config.transformLocalElements) ? - false : - config.transformLocalElements; + var matchTransformers = config.matchTransformers; + if (matchTransformers) { + _.each(matchTransformers, function(transformer) { + if (route.match(transformer.regexp)) { + changedElem = transformer.transformer(isCollection, changedElem); + } + }); + } - object.setTransformOnlyServerElements = function(active) { - config.transformLocalElements = !active; - }; + var typeTransformers = config.transformers[route]; + if (typeTransformers) { + _.each(typeTransformers, function(transformer) { + changedElem = transformer(isCollection, changedElem); + }); + } + return config.onElemRestangularized(changedElem, isCollection, route, Restangular); + }; - config.fullResponse = _.isUndefined(config.fullResponse) ? false : config.fullResponse; - object.setFullResponse = function(full) { - config.fullResponse = full; - return this; - }; + config.transformLocalElements = _.isUndefined(config.transformLocalElements) ? + false : + config.transformLocalElements; + object.setTransformOnlyServerElements = function(active) { + config.transformLocalElements = !active; + }; - //Internal values and functions - config.urlCreatorFactory = {}; + config.fullResponse = _.isUndefined(config.fullResponse) ? false : config.fullResponse; + object.setFullResponse = function(full) { + config.fullResponse = full; + return this; + }; - /** - * Base URL Creator. Base prototype for everything related to it - **/ - var BaseCreator = function() { - }; + //Internal values and functions + config.urlCreatorFactory = {}; - BaseCreator.prototype.setConfig = function(config) { - this.config = config; - return this; - }; + /** + * Base URL Creator. Base prototype for everything related to it + **/ - BaseCreator.prototype.parentsArray = function(current) { - var parents = []; - while(current) { - parents.push(current); - current = current[this.config.restangularFields.parentResource]; - } - return parents.reverse(); - }; + var BaseCreator = function() {}; - function RestangularResource(config, $http, url, configurer) { - var resource = {}; - _.each(_.keys(configurer), function(key) { - var value = configurer[key]; + BaseCreator.prototype.setConfig = function(config) { + this.config = config; + return this; + }; - // Add default parameters - value.params = _.extend({}, value.params, config.defaultRequestParams[value.method.toLowerCase()]); - // We don't want the ? if no params are there - if (_.isEmpty(value.params)) { - delete value.params; + BaseCreator.prototype.parentsArray = function(current) { + var parents = []; + while (current) { + parents.push(current); + current = current[this.config.restangularFields.parentResource]; } + return parents.reverse(); + }; - if (config.isSafe(value.method)) { + function RestangularResource(config, $http, url, configurer) { + var resource = {}; + _.each(_.keys(configurer), function(key) { + var value = configurer[key]; - resource[key] = function() { - return $http(_.extend(value, { - url: url - })); - }; + // Add default parameters + value.params = _.extend({}, value.params, config.defaultRequestParams[value.method.toLowerCase()]); + // We don't want the ? if no params are there + if (_.isEmpty(value.params)) { + delete value.params; + } - } else { + if (config.isSafe(value.method)) { - resource[key] = function(data) { - return $http(_.extend(value, { - url: url, - data: data - })); - }; + resource[key] = function() { + return $http(_.extend(value, { + url: url + })); + }; - } - }); + } else { - return resource; - } + resource[key] = function(data) { + return $http(_.extend(value, { + url: url, + data: data + })); + }; - BaseCreator.prototype.resource = function(current, $http, localHttpConfig, callHeaders, callParams, what, etag,operation) { + } + }); - var params = _.defaults(callParams || {}, this.config.defaultRequestParams.common); - var headers = _.defaults(callHeaders || {}, this.config.defaultHeaders); + return resource; + } - if (etag) { - if (!config.isSafe(operation)) { - headers['If-Match'] = etag; - } else { - headers['If-None-Match'] = etag; + BaseCreator.prototype.resource = function(current, $http, localHttpConfig, callHeaders, callParams, what, etag, operation) { + + var params = _.defaults(callParams || {}, this.config.defaultRequestParams.common); + var headers = _.defaults(callHeaders || {}, this.config.defaultHeaders); + + if (etag) { + if (!config.isSafe(operation)) { + headers['If-Match'] = etag; + } else { + headers['If-None-Match'] = etag; + } } - } - var url = this.base(current); + var url = this.base(current); - if (what || what === 0) { - var add = ''; - if (!/\/$/.test(url)) { - add += '/'; + if (what || what === 0) { + var add = ''; + if (!/\/$/.test(url)) { + add += '/'; + } + add += what; + url += add; } - add += what; - url += add; - } - if (this.config.suffix && - url.indexOf(this.config.suffix, url.length - this.config.suffix.length) === -1 && - !this.config.getUrlFromElem(current)) { + if (this.config.suffix && + url.indexOf(this.config.suffix, url.length - this.config.suffix.length) === -1 && + !this.config.getUrlFromElem(current)) { url += this.config.suffix; - } + } - current[this.config.restangularFields.httpConfig] = undefined; + current[this.config.restangularFields.httpConfig] = undefined; - return RestangularResource(this.config, $http, url, { - getList: this.config.withHttpValues(localHttpConfig, - {method: 'GET', - params: params, - headers: headers}), - - get: this.config.withHttpValues(localHttpConfig, - {method: 'GET', - params: params, - headers: headers}), + return RestangularResource(this.config, $http, url, { + getList: this.config.withHttpValues(localHttpConfig, { + method: 'GET', + params: params, + headers: headers + }), - jsonp: this.config.withHttpValues(localHttpConfig, - {method: 'jsonp', - params: params, - headers: headers}), + get: this.config.withHttpValues(localHttpConfig, { + method: 'GET', + params: params, + headers: headers + }), - put: this.config.withHttpValues(localHttpConfig, - {method: 'PUT', - params: params, - headers: headers}), + jsonp: this.config.withHttpValues(localHttpConfig, { + method: 'jsonp', + params: params, + headers: headers + }), - post: this.config.withHttpValues(localHttpConfig, - {method: 'POST', - params: params, - headers: headers}), + put: this.config.withHttpValues(localHttpConfig, { + method: 'PUT', + params: params, + headers: headers + }), - remove: this.config.withHttpValues(localHttpConfig, - {method: 'DELETE', - params: params, - headers: headers}), + post: this.config.withHttpValues(localHttpConfig, { + method: 'POST', + params: params, + headers: headers + }), - head: this.config.withHttpValues(localHttpConfig, - {method: 'HEAD', - params: params, - headers: headers}), + remove: this.config.withHttpValues(localHttpConfig, { + method: 'DELETE', + params: params, + headers: headers + }), - trace: this.config.withHttpValues(localHttpConfig, - {method: 'TRACE', - params: params, - headers: headers}), + head: this.config.withHttpValues(localHttpConfig, { + method: 'HEAD', + params: params, + headers: headers + }), - options: this.config.withHttpValues(localHttpConfig, - {method: 'OPTIONS', - params: params, - headers: headers}), + trace: this.config.withHttpValues(localHttpConfig, { + method: 'TRACE', + params: params, + headers: headers + }), - patch: this.config.withHttpValues(localHttpConfig, - {method: 'PATCH', - params: params, - headers: headers}) - }); - }; + options: this.config.withHttpValues(localHttpConfig, { + method: 'OPTIONS', + params: params, + headers: headers + }), - /** - * This is the Path URL creator. It uses Path to show Hierarchy in the Rest API. - * This means that if you have an Account that then has a set of Buildings, a URL to a building - * would be /accounts/123/buildings/456 - **/ - var Path = function() { - }; + patch: this.config.withHttpValues(localHttpConfig, { + method: 'PATCH', + params: params, + headers: headers + }) + }); + }; - Path.prototype = new BaseCreator(); + /** + * This is the Path URL creator. It uses Path to show Hierarchy in the Rest API. + * This means that if you have an Account that then has a set of Buildings, a URL to a building + * would be /accounts/123/buildings/456 + **/ + var Path = function() {}; - Path.prototype.normalizeUrl = function (url){ - var parts = /((?:http[s]?:)?\/\/)?(.*)?/.exec(url); - parts[2] = parts[2].replace(/[\\\/]+/g, '/'); - return (typeof parts[1] !== 'undefined')? parts[1] + parts[2] : parts[2]; - }; + Path.prototype = new BaseCreator(); - Path.prototype.base = function(current) { - var __this = this; - return _.reduce(this.parentsArray(current), function(acum, elem) { - var elemUrl; - var elemSelfLink = __this.config.getUrlFromElem(elem); - if (elemSelfLink) { - if (__this.config.isAbsoluteUrl(elemSelfLink)) { - return elemSelfLink; - } else { - elemUrl = elemSelfLink; - } - } else { - elemUrl = elem[__this.config.restangularFields.route]; + Path.prototype.normalizeUrl = function(url) { + var parts = /((?:http[s]?:)?\/\/)?(.*)?/.exec(url); + parts[2] = parts[2].replace(/[\\\/]+/g, '/'); + return (typeof parts[1] !== 'undefined') ? parts[1] + parts[2] : parts[2]; + }; - if (elem[__this.config.restangularFields.restangularCollection]) { - var ids = elem[__this.config.restangularFields.ids]; - if (ids) { - elemUrl += '/' + ids.join(','); - } - } else { - var elemId; - if (__this.config.useCannonicalId) { - elemId = __this.config.getCannonicalIdFromElem(elem); + Path.prototype.base = function(current) { + var __this = this; + return _.reduce(this.parentsArray(current), function(acum, elem) { + var elemUrl; + var elemSelfLink = __this.config.getUrlFromElem(elem); + if (elemSelfLink) { + if (__this.config.isAbsoluteUrl(elemSelfLink)) { + return elemSelfLink; } else { - elemId = __this.config.getIdFromElem(elem); + elemUrl = elemSelfLink; } + } else { + elemUrl = elem[__this.config.restangularFields.route]; - if (config.isValidId(elemId) && !elem.singleOne) { - elemUrl += '/' + (__this.config.encodeIds ? encodeURIComponent(elemId) : elemId); + if (elem[__this.config.restangularFields.restangularCollection]) { + var ids = elem[__this.config.restangularFields.ids]; + if (ids) { + elemUrl += '/' + ids.join(','); + } + } else { + var elemId; + if (__this.config.useCannonicalId) { + elemId = __this.config.getCannonicalIdFromElem(elem); + } else { + elemId = __this.config.getIdFromElem(elem); + } + + if (config.isValidId(elemId) && !elem.singleOne) { + elemUrl += '/' + (__this.config.encodeIds ? encodeURIComponent(elemId) : elemId); + } } } - } - acum = acum.replace(/\/$/, '') + '/' + elemUrl; - return __this.normalizeUrl(acum); + acum = acum.replace(/\/$/, '') + '/' + elemUrl; + return __this.normalizeUrl(acum); - }, this.config.baseUrl); - }; + }, this.config.baseUrl); + }; - Path.prototype.fetchUrl = function(current, what) { - var baseUrl = this.base(current); - if (what) { - baseUrl += '/' + what; - } - return baseUrl; - }; + Path.prototype.fetchUrl = function(current, what) { + var baseUrl = this.base(current); + if (what) { + baseUrl += '/' + what; + } + return baseUrl; + }; - Path.prototype.fetchRequestedUrl = function(current, what) { - var url = this.fetchUrl(current, what); - var params = current[config.restangularFields.reqParams]; - - // From here on and until the end of fetchRequestedUrl, - // the code has been kindly borrowed from angular.js - // The reason for such code bloating is coherence: - // If the user were to use this for cache management, the - // serialization of parameters would need to be identical - // to the one done by angular for cache keys to match. - function sortedKeys(obj) { - var keys = []; - for (var key in obj) { - if (obj.hasOwnProperty(key)) { - keys.push(key); + Path.prototype.fetchRequestedUrl = function(current, what) { + var url = this.fetchUrl(current, what); + var params = current[config.restangularFields.reqParams]; + + // From here on and until the end of fetchRequestedUrl, + // the code has been kindly borrowed from angular.js + // The reason for such code bloating is coherence: + // If the user were to use this for cache management, the + // serialization of parameters would need to be identical + // to the one done by angular for cache keys to match. + function sortedKeys(obj) { + var keys = []; + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + keys.push(key); + } } + return keys.sort(); } - return keys.sort(); - } - function forEachSorted(obj, iterator, context) { - var keys = sortedKeys(obj); - for ( var i = 0; i < keys.length; i++) { - iterator.call(context, obj[keys[i]], keys[i]); + function forEachSorted(obj, iterator, context) { + var keys = sortedKeys(obj); + for (var i = 0; i < keys.length; i++) { + iterator.call(context, obj[keys[i]], keys[i]); + } + return keys; } - return keys; - } - function encodeUriQuery(val, pctEncodeSpaces) { - return encodeURIComponent(val). - replace(/%40/gi, '@'). - replace(/%3A/gi, ':'). - replace(/%24/g, '$'). - replace(/%2C/gi, ','). - replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); - } - - if (!params) { return url + (this.config.suffix || ''); } + function encodeUriQuery(val, pctEncodeSpaces) { + return encodeURIComponent(val). + replace(/%40/gi, '@'). + replace(/%3A/gi, ':'). + replace(/%24/g, '$'). + replace(/%2C/gi, ','). + replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); + } - var parts = []; - forEachSorted(params, function(value, key) { - if (value === null || value === undefined) { return; } - if (!angular.isArray(value)) { value = [value]; } + if (!params) { + return url + (this.config.suffix || ''); + } - angular.forEach(value, function(v) { - if (angular.isObject(v)) { - v = angular.toJson(v); + var parts = []; + forEachSorted(params, function(value, key) { + if (value === null || value === undefined) { + return; + } + if (!angular.isArray(value)) { + value = [value]; } - parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(v)); + + angular.forEach(value, function(v) { + if (angular.isObject(v)) { + v = angular.toJson(v); + } + parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(v)); + }); }); - }); - return url + (this.config.suffix || '') + ((url.indexOf('?') === -1) ? '?' : '&') + parts.join('&'); + return url + (this.config.suffix || '') + ((url.indexOf('?') === -1) ? '?' : '&') + parts.join('&'); + }; + + config.urlCreatorFactory.path = Path; }; - config.urlCreatorFactory.path = Path; - }; + var globalConfiguration = {}; - var globalConfiguration = {}; + Configurer.init(this, globalConfiguration); - Configurer.init(this, globalConfiguration); + this.$get = ['$http', '$q', function($http, $q) { - this.$get = ['$http', '$q', function($http, $q) { + function createServiceForConfiguration(config) { + var service = {}; - function createServiceForConfiguration(config) { - var service = {}; + var urlHandler = new config.urlCreatorFactory[config.urlCreator](); + urlHandler.setConfig(config); - var urlHandler = new config.urlCreatorFactory[config.urlCreator](); - urlHandler.setConfig(config); + function restangularizeBase(parent, elem, route, reqParams, fromServer) { + elem[config.restangularFields.route] = route; + elem[config.restangularFields.getRestangularUrl] = _.bind(urlHandler.fetchUrl, urlHandler, elem); + elem[config.restangularFields.getRequestedUrl] = _.bind(urlHandler.fetchRequestedUrl, urlHandler, elem); + elem[config.restangularFields.addRestangularMethod] = _.bind(addRestangularMethodFunction, elem); + elem[config.restangularFields.clone] = _.bind(copyRestangularizedElement, elem, elem); + elem[config.restangularFields.reqParams] = _.isEmpty(reqParams) ? null : reqParams; + elem[config.restangularFields.withHttpConfig] = _.bind(withHttpConfig, elem); + elem[config.restangularFields.plain] = _.bind(stripRestangular, elem, elem); - function restangularizeBase(parent, elem, route, reqParams, fromServer) { - elem[config.restangularFields.route] = route; - elem[config.restangularFields.getRestangularUrl] = _.bind(urlHandler.fetchUrl, urlHandler, elem); - elem[config.restangularFields.getRequestedUrl] = _.bind(urlHandler.fetchRequestedUrl, urlHandler, elem); - elem[config.restangularFields.addRestangularMethod] = _.bind(addRestangularMethodFunction, elem); - elem[config.restangularFields.clone] = _.bind(copyRestangularizedElement, elem, elem); - elem[config.restangularFields.reqParams] = _.isEmpty(reqParams) ? null : reqParams; - elem[config.restangularFields.withHttpConfig] = _.bind(withHttpConfig, elem); - elem[config.restangularFields.plain] = _.bind(stripRestangular, elem, elem); + // Tag element as restangularized + elem[config.restangularFields.restangularized] = true; - // Tag element as restangularized - elem[config.restangularFields.restangularized] = true; + // RequestLess connection + elem[config.restangularFields.one] = _.bind(one, elem, elem); + elem[config.restangularFields.all] = _.bind(all, elem, elem); + elem[config.restangularFields.several] = _.bind(several, elem, elem); + elem[config.restangularFields.oneUrl] = _.bind(oneUrl, elem, elem); + elem[config.restangularFields.allUrl] = _.bind(allUrl, elem, elem); - // RequestLess connection - elem[config.restangularFields.one] = _.bind(one, elem, elem); - elem[config.restangularFields.all] = _.bind(all, elem, elem); - elem[config.restangularFields.several] = _.bind(several, elem, elem); - elem[config.restangularFields.oneUrl] = _.bind(oneUrl, elem, elem); - elem[config.restangularFields.allUrl] = _.bind(allUrl, elem, elem); + elem[config.restangularFields.fromServer] = !!fromServer; - elem[config.restangularFields.fromServer] = !!fromServer; + if (parent && config.shouldSaveParent(route)) { + var parentId = config.getIdFromElem(parent); + var parentUrl = config.getUrlFromElem(parent); - if (parent && config.shouldSaveParent(route)) { - var parentId = config.getIdFromElem(parent); - var parentUrl = config.getUrlFromElem(parent); + var restangularFieldsForParent = _.union( + _.values(_.pick(config.restangularFields, ['route', 'singleOne', 'parentResource'])), + config.extraFields + ); + var parentResource = _.pick(parent, restangularFieldsForParent); - var restangularFieldsForParent = _.union( - _.values( _.pick(config.restangularFields, ['route', 'singleOne', 'parentResource']) ), - config.extraFields - ); - var parentResource = _.pick(parent, restangularFieldsForParent); + if (config.isValidId(parentId)) { + config.setIdToElem(parentResource, parentId, route); + } + if (config.isValidId(parentUrl)) { + config.setUrlToElem(parentResource, parentUrl, route); + } - if (config.isValidId(parentId)) { - config.setIdToElem(parentResource, parentId, route); - } - if (config.isValidId(parentUrl)) { - config.setUrlToElem(parentResource, parentUrl, route); + elem[config.restangularFields.parentResource] = parentResource; + } else { + elem[config.restangularFields.parentResource] = null; } - - elem[config.restangularFields.parentResource] = parentResource; - } else { - elem[config.restangularFields.parentResource] = null; + return elem; } - return elem; - } - function one(parent, route, id, singleOne) { - var error; - if (_.isNumber(route) || _.isNumber(parent)) { - error = 'You\'re creating a Restangular entity with the number '; - error += 'instead of the route or the parent. For example, you can\'t call .one(12).'; - throw new Error(error); - } - if (_.isUndefined(route)) { - error = 'You\'re creating a Restangular entity either without the path. '; - error += 'For example you can\'t call .one(). Please check if your arguments are valid.'; - throw new Error(error); + function one(parent, route, id, singleOne) { + var error; + if (_.isNumber(route) || _.isNumber(parent)) { + error = 'You\'re creating a Restangular entity with the number '; + error += 'instead of the route or the parent. For example, you can\'t call .one(12).'; + throw new Error(error); + } + if (_.isUndefined(route)) { + error = 'You\'re creating a Restangular entity either without the path. '; + error += 'For example you can\'t call .one(). Please check if your arguments are valid.'; + throw new Error(error); + } + var elem = {}; + config.setIdToElem(elem, id, route); + config.setFieldToElem(config.restangularFields.singleOne, elem, singleOne); + return restangularizeElem(parent, elem, route, false); } - var elem = {}; - config.setIdToElem(elem, id, route); - config.setFieldToElem(config.restangularFields.singleOne, elem, singleOne); - return restangularizeElem(parent, elem , route, false); - } - function all(parent, route) { - return restangularizeCollection(parent, [] , route, false); - } + function all(parent, route) { + return restangularizeCollection(parent, [], route, false); + } - function several(parent, route /*, ids */) { - var collection = []; - collection[config.restangularFields.ids] = Array.prototype.splice.call(arguments, 2); - return restangularizeCollection(parent, collection , route, false); - } + function several(parent, route /*, ids */ ) { + var collection = []; + collection[config.restangularFields.ids] = Array.prototype.splice.call(arguments, 2); + return restangularizeCollection(parent, collection, route, false); + } - function oneUrl(parent, route, url) { - if (!route) { - throw new Error('Route is mandatory when creating new Restangular objects.'); + function oneUrl(parent, route, url) { + if (!route) { + throw new Error('Route is mandatory when creating new Restangular objects.'); + } + var elem = {}; + config.setUrlToElem(elem, url, route); + return restangularizeElem(parent, elem, route, false); } - var elem = {}; - config.setUrlToElem(elem, url, route); - return restangularizeElem(parent, elem , route, false); - } - function allUrl(parent, route, url) { - if (!route) { - throw new Error('Route is mandatory when creating new Restangular objects.'); + function allUrl(parent, route, url) { + if (!route) { + throw new Error('Route is mandatory when creating new Restangular objects.'); + } + var elem = {}; + config.setUrlToElem(elem, url, route); + return restangularizeCollection(parent, elem, route, false); } - var elem = {}; - config.setUrlToElem(elem, url, route); - return restangularizeCollection(parent, elem , route, false); - } - // Promises - function restangularizePromise(promise, isCollection, valueToFill) { - promise.call = _.bind(promiseCall, promise); - promise.get = _.bind(promiseGet, promise); - promise[config.restangularFields.restangularCollection] = isCollection; - if (isCollection) { + // Promises + function restangularizePromise(promise, isCollection, valueToFill) { + promise.call = _.bind(promiseCall, promise); + promise.get = _.bind(promiseGet, promise); + promise[config.restangularFields.restangularCollection] = isCollection; + if (isCollection) { promise.push = _.bind(promiseCall, promise, 'push'); + } + promise.$object = valueToFill; + if (config.restangularizePromiseInterceptor) { + config.restangularizePromiseInterceptor(promise); + } + return promise; } - promise.$object = valueToFill; - if (config.restangularizePromiseInterceptor) { - config.restangularizePromiseInterceptor(promise); - } - return promise; - } - function promiseCall(method) { - var deferred = $q.defer(); - var callArgs = arguments; - var filledValue = {}; - this.then(function(val) { - var params = Array.prototype.slice.call(callArgs, 1); - var func = val[method]; - func.apply(val, params); - filledValue = val; - deferred.resolve(val); - }); - return restangularizePromise(deferred.promise, this[config.restangularFields.restangularCollection], filledValue); - } + function promiseCall(method) { + var deferred = $q.defer(); + var callArgs = arguments; + var filledValue = {}; + this.then(function(val) { + var params = Array.prototype.slice.call(callArgs, 1); + var func = val[method]; + func.apply(val, params); + filledValue = val; + deferred.resolve(val); + }); + return restangularizePromise(deferred.promise, this[config.restangularFields.restangularCollection], filledValue); + } - function promiseGet(what) { - var deferred = $q.defer(); - var filledValue = {}; - this.then(function(val) { - filledValue = val[what]; - deferred.resolve(filledValue); - }); - return restangularizePromise(deferred.promise, this[config.restangularFields.restangularCollection], filledValue); - } + function promiseGet(what) { + var deferred = $q.defer(); + var filledValue = {}; + this.then(function(val) { + filledValue = val[what]; + deferred.resolve(filledValue); + }); + return restangularizePromise(deferred.promise, this[config.restangularFields.restangularCollection], filledValue); + } - function resolvePromise(deferred, response, data, filledValue) { - _.extend(filledValue, data); + function resolvePromise(deferred, response, data, filledValue) { + _.extend(filledValue, data); - // Trigger the full response interceptor. - if (config.fullResponse) { - return deferred.resolve(_.extend(response, { - data: data - })); - } else { - deferred.resolve(data); + // Trigger the full response interceptor. + if (config.fullResponse) { + return deferred.resolve(_.extend(response, { + data: data + })); + } else { + deferred.resolve(data); + } } - } - // Elements - function stripRestangular(elem) { - if (_.isArray(elem)) { - var array = []; - _.each(elem, function(value) { - array.push(config.isRestangularized(value) ? stripRestangular(value) : value); - }); - return array; - } else { - return _.omit(elem, _.values(_.omit(config.restangularFields, 'id'))); + // Elements + function stripRestangular(elem) { + if (_.isArray(elem)) { + var array = []; + _.each(elem, function(value) { + array.push(config.isRestangularized(value) ? stripRestangular(value) : value); + }); + return array; + } else { + return _.omit(elem, _.values(_.omit(config.restangularFields, 'id'))); + } } - } - function addCustomOperation(elem) { - elem[config.restangularFields.customOperation] = _.bind(customFunction, elem); - var requestMethods = { get: customFunction, delete: customFunction }; - _.each(['put', 'patch', 'post'], function(name) { - requestMethods[name] = function(operation, elem, path, params, headers) { - return _.bind(customFunction, this)(operation, path, params, headers, elem); + function addCustomOperation(elem) { + elem[config.restangularFields.customOperation] = _.bind(customFunction, elem); + var requestMethods = { + get: customFunction, + delete: customFunction }; - }); - _.each(requestMethods, function(requestFunc, name) { - var callOperation = name === 'delete' ? 'remove' : name; - _.each(['do', 'custom'], function(alias) { - elem[alias + name.toUpperCase()] = _.bind(requestFunc, elem, callOperation); + _.each(['put', 'patch', 'post'], function(name) { + requestMethods[name] = function(operation, elem, path, params, headers) { + return _.bind(customFunction, this)(operation, path, params, headers, elem); + }; }); - }); - elem[config.restangularFields.customGETLIST] = _.bind(fetchFunction, elem); - elem[config.restangularFields.doGETLIST] = elem[config.restangularFields.customGETLIST]; - } - - function copyRestangularizedElement(fromElement, toElement) { - var copiedElement = angular.copy(fromElement, toElement); - return restangularizeElem(copiedElement[config.restangularFields.parentResource], - copiedElement, copiedElement[config.restangularFields.route], copiedElement[config.restangularFields.fromServer]); - } - - function restangularizeElem(parent, element, route, fromServer, collection, reqParams) { - var elem = config.onBeforeElemRestangularized(element, false, route); - - var localElem = restangularizeBase(parent, elem, route, reqParams, fromServer); - - if (config.useCannonicalId) { - localElem[config.restangularFields.cannonicalId] = config.getIdFromElem(localElem); + _.each(requestMethods, function(requestFunc, name) { + var callOperation = name === 'delete' ? 'remove' : name; + _.each(['do', 'custom'], function(alias) { + elem[alias + name.toUpperCase()] = _.bind(requestFunc, elem, callOperation); + }); + }); + elem[config.restangularFields.customGETLIST] = _.bind(fetchFunction, elem); + elem[config.restangularFields.doGETLIST] = elem[config.restangularFields.customGETLIST]; } - if (collection) { - localElem[config.restangularFields.getParentList] = function() { - return collection; - }; + function copyRestangularizedElement(fromElement, toElement) { + var copiedElement = angular.copy(fromElement, toElement); + return restangularizeElem(copiedElement[config.restangularFields.parentResource], + copiedElement, copiedElement[config.restangularFields.route], copiedElement[config.restangularFields.fromServer]); } - localElem[config.restangularFields.restangularCollection] = false; - localElem[config.restangularFields.get] = _.bind(getFunction, localElem); - localElem[config.restangularFields.getList] = _.bind(fetchFunction, localElem); - localElem[config.restangularFields.put] = _.bind(putFunction, localElem); - localElem[config.restangularFields.post] = _.bind(postFunction, localElem); - localElem[config.restangularFields.remove] = _.bind(deleteFunction, localElem); - localElem[config.restangularFields.head] = _.bind(headFunction, localElem); - localElem[config.restangularFields.trace] = _.bind(traceFunction, localElem); - localElem[config.restangularFields.options] = _.bind(optionsFunction, localElem); - localElem[config.restangularFields.patch] = _.bind(patchFunction, localElem); - localElem[config.restangularFields.save] = _.bind(save, localElem); - - addCustomOperation(localElem); - return config.transformElem(localElem, false, route, service, true); - } + function restangularizeElem(parent, element, route, fromServer, collection, reqParams) { + var elem = config.onBeforeElemRestangularized(element, false, route); - function restangularizeCollection(parent, element, route, fromServer, reqParams) { - var elem = config.onBeforeElemRestangularized(element, true, route); - - var localElem = restangularizeBase(parent, elem, route, reqParams, fromServer); - localElem[config.restangularFields.restangularCollection] = true; - localElem[config.restangularFields.post] = _.bind(postFunction, localElem, null); - localElem[config.restangularFields.remove] = _.bind(deleteFunction, localElem); - localElem[config.restangularFields.head] = _.bind(headFunction, localElem); - localElem[config.restangularFields.trace] = _.bind(traceFunction, localElem); - localElem[config.restangularFields.putElement] = _.bind(putElementFunction, localElem); - localElem[config.restangularFields.options] = _.bind(optionsFunction, localElem); - localElem[config.restangularFields.patch] = _.bind(patchFunction, localElem); - localElem[config.restangularFields.get] = _.bind(getById, localElem); - localElem[config.restangularFields.getList] = _.bind(fetchFunction, localElem, null); - - addCustomOperation(localElem); - return config.transformElem(localElem, true, route, service, true); - } + var localElem = restangularizeBase(parent, elem, route, reqParams, fromServer); - function restangularizeCollectionAndElements(parent, element, route, fromServer) { - var collection = restangularizeCollection(parent, element, route, fromServer); - _.each(collection, function(elem) { - if (elem) { - restangularizeElem(parent, elem, route, fromServer); + if (config.useCannonicalId) { + localElem[config.restangularFields.cannonicalId] = config.getIdFromElem(localElem); } - }); - return collection; - } - function getById(id, reqParams, headers){ - return this.customGET(id.toString(), reqParams, headers); - } - - function putElementFunction(idx, params, headers) { - var __this = this; - var elemToPut = this[idx]; - var deferred = $q.defer(); - var filledArray = []; - filledArray = config.transformElem(filledArray, true, elemToPut[config.restangularFields.route], service); - elemToPut.put(params, headers).then(function(serverElem) { - var newArray = copyRestangularizedElement(__this); - newArray[idx] = serverElem; - filledArray = newArray; - deferred.resolve(newArray); - }, function(response) { - deferred.reject(response); - }); - - return restangularizePromise(deferred.promise, true, filledArray); - } + if (collection) { + localElem[config.restangularFields.getParentList] = function() { + return collection; + }; + } - function parseResponse(resData, operation, route, fetchUrl, response, deferred) { - var data = config.responseExtractor(resData, operation, route, fetchUrl, response, deferred); - var etag = response.headers('ETag'); - if (data && etag) { - data[config.restangularFields.etag] = etag; + localElem[config.restangularFields.restangularCollection] = false; + localElem[config.restangularFields.get] = _.bind(getFunction, localElem); + localElem[config.restangularFields.getList] = _.bind(fetchFunction, localElem); + localElem[config.restangularFields.put] = _.bind(putFunction, localElem); + localElem[config.restangularFields.post] = _.bind(postFunction, localElem); + localElem[config.restangularFields.remove] = _.bind(deleteFunction, localElem); + localElem[config.restangularFields.head] = _.bind(headFunction, localElem); + localElem[config.restangularFields.trace] = _.bind(traceFunction, localElem); + localElem[config.restangularFields.options] = _.bind(optionsFunction, localElem); + localElem[config.restangularFields.patch] = _.bind(patchFunction, localElem); + localElem[config.restangularFields.save] = _.bind(save, localElem); + + addCustomOperation(localElem); + return config.transformElem(localElem, false, route, service, true); } - return data; - } - - function fetchFunction(what, reqParams, headers) { - var __this = this; - var deferred = $q.defer(); - var operation = 'getList'; - var url = urlHandler.fetchUrl(this, what); - var whatFetched = what || __this[config.restangularFields.route]; - - var request = config.fullRequestInterceptor(null, operation, - whatFetched, url, headers || {}, reqParams || {}, this[config.restangularFields.httpConfig] || {}); - - var filledArray = []; - filledArray = config.transformElem(filledArray, true, whatFetched, service); - - var method = 'getList'; - - if (config.jsonp) { - method = 'jsonp'; + function restangularizeCollection(parent, element, route, fromServer, reqParams) { + var elem = config.onBeforeElemRestangularized(element, true, route); + + var localElem = restangularizeBase(parent, elem, route, reqParams, fromServer); + localElem[config.restangularFields.restangularCollection] = true; + localElem[config.restangularFields.post] = _.bind(postFunction, localElem, null); + localElem[config.restangularFields.remove] = _.bind(deleteFunction, localElem); + localElem[config.restangularFields.head] = _.bind(headFunction, localElem); + localElem[config.restangularFields.trace] = _.bind(traceFunction, localElem); + localElem[config.restangularFields.putElement] = _.bind(putElementFunction, localElem); + localElem[config.restangularFields.options] = _.bind(optionsFunction, localElem); + localElem[config.restangularFields.patch] = _.bind(patchFunction, localElem); + localElem[config.restangularFields.get] = _.bind(getById, localElem); + localElem[config.restangularFields.getList] = _.bind(fetchFunction, localElem, null); + + addCustomOperation(localElem); + return config.transformElem(localElem, true, route, service, true); } - var okCallback = function(response) { - var resData = response.data; - var fullParams = response.config.params; - var data = parseResponse(resData, operation, whatFetched, url, response, deferred); - - // support empty response for getList() calls (some APIs respond with 204 and empty body) - if (_.isUndefined(data) || '' === data) { - data = []; - } - if (!_.isArray(data)) { - throw new Error('Response for getList SHOULD be an array and not an object or something else'); - } - - if (true === config.plainByDefault) { - return resolvePromise(deferred, response, data, filledArray); - } - - var processedData = _.map(data, function(elem) { - if (!__this[config.restangularFields.restangularCollection]) { - return restangularizeElem(__this, elem, what, true, data); - } else { - return restangularizeElem(__this[config.restangularFields.parentResource], - elem, __this[config.restangularFields.route], true, data); + function restangularizeCollectionAndElements(parent, element, route, fromServer) { + var collection = restangularizeCollection(parent, element, route, fromServer); + _.each(collection, function(elem) { + if (elem) { + restangularizeElem(parent, elem, route, fromServer); } }); + return collection; + } - processedData = _.extend(data, processedData); - - if (!__this[config.restangularFields.restangularCollection]) { - resolvePromise( - deferred, - response, - restangularizeCollection( - __this, - processedData, - what, - true, - fullParams - ), - filledArray - ); - } else { - resolvePromise( - deferred, - response, - restangularizeCollection( - __this[config.restangularFields.parentResource], - processedData, - __this[config.restangularFields.route], - true, - fullParams - ), - filledArray - ); - } - }; + function getById(id, reqParams, headers) { + return this.customGET(id.toString(), reqParams, headers); + } - urlHandler.resource(this, $http, request.httpConfig, request.headers, request.params, what, - this[config.restangularFields.etag], operation)[method]().then(okCallback, function error(response) { - if (response.status === 304 && __this[config.restangularFields.restangularCollection]) { - resolvePromise(deferred, response, __this, filledArray); - } else if ( _.every(config.errorInterceptors, function(cb) { return cb(response, deferred, okCallback) !== false; }) ) { - // triggered if no callback returns false + function putElementFunction(idx, params, headers) { + var __this = this; + var elemToPut = this[idx]; + var deferred = $q.defer(); + var filledArray = []; + filledArray = config.transformElem(filledArray, true, elemToPut[config.restangularFields.route], service); + elemToPut.put(params, headers).then(function(serverElem) { + var newArray = copyRestangularizedElement(__this); + newArray[idx] = serverElem; + filledArray = newArray; + deferred.resolve(newArray); + }, function(response) { deferred.reject(response); - } - }); - - return restangularizePromise(deferred.promise, true, filledArray); - } + }); - function withHttpConfig(httpConfig) { - this[config.restangularFields.httpConfig] = httpConfig; - return this; - } + return restangularizePromise(deferred.promise, true, filledArray); + } - function save(params, headers) { - if (this[config.restangularFields.fromServer]) { - return this[config.restangularFields.put](params, headers); - } else { - return _.bind(elemFunction, this)('post', undefined, params, undefined, headers); + function parseResponse(resData, operation, route, fetchUrl, response, deferred) { + var data = config.responseExtractor(resData, operation, route, fetchUrl, response, deferred); + var etag = response.headers('ETag'); + if (data && etag) { + data[config.restangularFields.etag] = etag; + } + return data; } - } - function elemFunction(operation, what, params, obj, headers) { - var __this = this; - var deferred = $q.defer(); - var resParams = params || {}; - var route = what || this[config.restangularFields.route]; - var fetchUrl = urlHandler.fetchUrl(this, what); - var callObj = obj || this; - // fallback to etag on restangular object (since for custom methods we probably don't explicitly specify the etag field) - var etag = callObj[config.restangularFields.etag] || (operation !== 'post' ? this[config.restangularFields.etag] : null); + function fetchFunction(what, reqParams, headers) { + var __this = this; + var deferred = $q.defer(); + var operation = 'getList'; + var url = urlHandler.fetchUrl(this, what); + var whatFetched = what || __this[config.restangularFields.route]; - if (_.isObject(callObj) && config.isRestangularized(callObj)) { - callObj = stripRestangular(callObj); - } - var request = config.fullRequestInterceptor(callObj, operation, route, fetchUrl, - headers || {}, resParams || {}, this[config.restangularFields.httpConfig] || {}); + var request = config.fullRequestInterceptor(null, operation, + whatFetched, url, headers || {}, reqParams || {}, this[config.restangularFields.httpConfig] || {}); - var filledObject = {}; - filledObject = config.transformElem(filledObject, false, route, service); + var filledArray = []; + filledArray = config.transformElem(filledArray, true, whatFetched, service); - var okCallback = function(response) { - var resData = response.data; - var fullParams = response.config.params; - var elem = parseResponse(resData, operation, route, fetchUrl, response, deferred); + var method = 'getList'; - // accept 0 as response - if (elem !== null && elem !== undefined && elem !== '') { - var data; + if (config.jsonp) { + method = 'jsonp'; + } + + var okCallback = function(response) { + var resData = response.data; + var fullParams = response.config.params; + var data = parseResponse(resData, operation, whatFetched, url, response, deferred); + + // support empty response for getList() calls (some APIs respond with 204 and empty body) + if (_.isUndefined(data) || '' === data) { + data = []; + } + if (!_.isArray(data)) { + throw new Error('Response for getList SHOULD be an array and not an object or something else'); + } if (true === config.plainByDefault) { - return resolvePromise(deferred, response, elem, filledObject); + return resolvePromise(deferred, response, data, filledArray); } - if (operation === 'post' && !__this[config.restangularFields.restangularCollection]) { - data = restangularizeElem( - __this[config.restangularFields.parentResource], - elem, - route, - true, - null, - fullParams + var processedData = _.map(data, function(elem) { + if (!__this[config.restangularFields.restangularCollection]) { + return restangularizeElem(__this, elem, what, true, data); + } else { + return restangularizeElem(__this[config.restangularFields.parentResource], + elem, __this[config.restangularFields.route], true, data); + } + }); + + processedData = _.extend(data, processedData); + + if (!__this[config.restangularFields.restangularCollection]) { + resolvePromise( + deferred, + response, + restangularizeCollection( + __this, + processedData, + what, + true, + fullParams + ), + filledArray ); - resolvePromise(deferred, response, data, filledObject); } else { - data = restangularizeElem( - __this[config.restangularFields.parentResource], - elem, - __this[config.restangularFields.route], - true, - null, - fullParams + resolvePromise( + deferred, + response, + restangularizeCollection( + __this[config.restangularFields.parentResource], + processedData, + __this[config.restangularFields.route], + true, + fullParams + ), + filledArray ); + } + }; - data[config.restangularFields.singleOne] = __this[config.restangularFields.singleOne]; - resolvePromise(deferred, response, data, filledObject); + urlHandler.resource(this, $http, request.httpConfig, request.headers, request.params, what, + this[config.restangularFields.etag], operation)[method]().then(okCallback, function error(response) { + if (response.status === 304 && __this[config.restangularFields.restangularCollection]) { + resolvePromise(deferred, response, __this, filledArray); + } else if (_.every(config.errorInterceptors, function(cb) { + return cb(response, deferred, okCallback) !== false; + })) { + // triggered if no callback returns false + deferred.reject(response); } + }); + return restangularizePromise(deferred.promise, true, filledArray); + } + + function withHttpConfig(httpConfig) { + this[config.restangularFields.httpConfig] = httpConfig; + return this; + } + + function save(params, headers) { + if (this[config.restangularFields.fromServer]) { + return this[config.restangularFields.put](params, headers); } else { - resolvePromise(deferred, response, undefined, filledObject); + return _.bind(elemFunction, this)('post', undefined, params, undefined, headers); } - }; + } - var errorCallback = function(response) { - if (response.status === 304 && config.isSafe(operation)) { - resolvePromise(deferred, response, __this, filledObject); - } else if ( _.every(config.errorInterceptors, function(cb) { return cb(response, deferred, okCallback) !== false; }) ) { - // triggered if no callback returns false - deferred.reject(response); + function elemFunction(operation, what, params, obj, headers) { + var __this = this; + var deferred = $q.defer(); + var resParams = params || {}; + var route = what || this[config.restangularFields.route]; + var fetchUrl = urlHandler.fetchUrl(this, what); + + var callObj = obj || this; + // fallback to etag on restangular object (since for custom methods we probably don't explicitly specify the etag field) + var etag = callObj[config.restangularFields.etag] || (operation !== 'post' ? this[config.restangularFields.etag] : null); + + if (_.isObject(callObj) && config.isRestangularized(callObj)) { + callObj = stripRestangular(callObj); } - }; - // Overriding HTTP Method - var callOperation = operation; - var callHeaders = _.extend({}, request.headers); - var isOverrideOperation = config.isOverridenMethod(operation); - if (isOverrideOperation) { - callOperation = 'post'; - callHeaders = _.extend(callHeaders, {'X-HTTP-Method-Override': operation === 'remove' ? 'DELETE' : operation.toUpperCase()}); - } else if (config.jsonp && callOperation === 'get') { - callOperation = 'jsonp'; - } + var request = config.fullRequestInterceptor(callObj, operation, route, fetchUrl, + headers || {}, resParams || {}, this[config.restangularFields.httpConfig] || {}); + + var filledObject = {}; + filledObject = config.transformElem(filledObject, false, route, service); + + var okCallback = function(response) { + var resData = response.data; + var fullParams = response.config.params; + var elem = parseResponse(resData, operation, route, fetchUrl, response, deferred); + + // accept 0 as response + if (elem !== null && elem !== undefined && elem !== '') { + var data; + + if (true === config.plainByDefault) { + return resolvePromise(deferred, response, elem, filledObject); + } + + if (operation === 'post' && !__this[config.restangularFields.restangularCollection]) { + data = restangularizeElem( + __this[config.restangularFields.parentResource], + elem, + route, + true, + null, + fullParams + ); + resolvePromise(deferred, response, data, filledObject); + } else { + data = restangularizeElem( + __this[config.restangularFields.parentResource], + elem, + __this[config.restangularFields.route], + true, + null, + fullParams + ); + + data[config.restangularFields.singleOne] = __this[config.restangularFields.singleOne]; + resolvePromise(deferred, response, data, filledObject); + } - if (config.isSafe(operation)) { + } else { + resolvePromise(deferred, response, undefined, filledObject); + } + }; + + var errorCallback = function(response) { + if (response.status === 304 && config.isSafe(operation)) { + resolvePromise(deferred, response, __this, filledObject); + } else if (_.every(config.errorInterceptors, function(cb) { + return cb(response, deferred, okCallback) !== false; + })) { + // triggered if no callback returns false + deferred.reject(response); + } + }; + // Overriding HTTP Method + var callOperation = operation; + var callHeaders = _.extend({}, request.headers); + var isOverrideOperation = config.isOverridenMethod(operation); if (isOverrideOperation) { - urlHandler.resource(this, $http, request.httpConfig, callHeaders, request.params, - what, etag, callOperation)[callOperation]({}).then(okCallback, errorCallback); + callOperation = 'post'; + callHeaders = _.extend(callHeaders, { + 'X-HTTP-Method-Override': operation === 'remove' ? 'DELETE' : operation.toUpperCase() + }); + } else if (config.jsonp && callOperation === 'get') { + callOperation = 'jsonp'; + } + + if (config.isSafe(operation)) { + if (isOverrideOperation) { + urlHandler.resource(this, $http, request.httpConfig, callHeaders, request.params, + what, etag, callOperation)[callOperation]({}).then(okCallback, errorCallback); + } else { + urlHandler.resource(this, $http, request.httpConfig, callHeaders, request.params, + what, etag, callOperation)[callOperation]().then(okCallback, errorCallback); + } } else { urlHandler.resource(this, $http, request.httpConfig, callHeaders, request.params, - what, etag, callOperation)[callOperation]().then(okCallback, errorCallback); + what, etag, callOperation)[callOperation](request.element).then(okCallback, errorCallback); } - } else { - urlHandler.resource(this, $http, request.httpConfig, callHeaders, request.params, - what, etag, callOperation)[callOperation](request.element).then(okCallback, errorCallback); - } - - return restangularizePromise(deferred.promise, false, filledObject); - } - function getFunction(params, headers) { - return _.bind(elemFunction, this)('get', undefined, params, undefined, headers); - } + return restangularizePromise(deferred.promise, false, filledObject); + } - function deleteFunction(params, headers) { - return _.bind(elemFunction, this)('remove', undefined, params, undefined, headers); - } + function getFunction(params, headers) { + return _.bind(elemFunction, this)('get', undefined, params, undefined, headers); + } - function putFunction(params, headers) { - return _.bind(elemFunction, this)('put', undefined, params, undefined, headers); - } + function deleteFunction(params, headers) { + return _.bind(elemFunction, this)('remove', undefined, params, undefined, headers); + } - function postFunction(what, elem, params, headers) { - return _.bind(elemFunction, this)('post', what, params, elem, headers); - } + function putFunction(params, headers) { + return _.bind(elemFunction, this)('put', undefined, params, undefined, headers); + } - function headFunction(params, headers) { - return _.bind(elemFunction, this)('head', undefined, params, undefined, headers); - } + function postFunction(what, elem, params, headers) { + return _.bind(elemFunction, this)('post', what, params, elem, headers); + } - function traceFunction(params, headers) { - return _.bind(elemFunction, this)('trace', undefined, params, undefined, headers); - } + function headFunction(params, headers) { + return _.bind(elemFunction, this)('head', undefined, params, undefined, headers); + } - function optionsFunction(params, headers) { - return _.bind(elemFunction, this)('options', undefined, params, undefined, headers); - } + function traceFunction(params, headers) { + return _.bind(elemFunction, this)('trace', undefined, params, undefined, headers); + } - function patchFunction(elem, params, headers) { - return _.bind(elemFunction, this)('patch', undefined, params, elem, headers); - } + function optionsFunction(params, headers) { + return _.bind(elemFunction, this)('options', undefined, params, undefined, headers); + } - function customFunction(operation, path, params, headers, elem) { - return _.bind(elemFunction, this)(operation, path, params, elem, headers); - } + function patchFunction(elem, params, headers) { + return _.bind(elemFunction, this)('patch', undefined, params, elem, headers); + } - function addRestangularMethodFunction(name, operation, path, defaultParams, defaultHeaders, defaultElem) { - var bindedFunction; - if (operation === 'getList') { - bindedFunction = _.bind(fetchFunction, this, path); - } else { - bindedFunction = _.bind(customFunction, this, operation, path); + function customFunction(operation, path, params, headers, elem) { + return _.bind(elemFunction, this)(operation, path, params, elem, headers); } - var createdFunction = function(params, headers, elem) { - var callParams = _.defaults({ - params: params, - headers: headers, - elem: elem - }, { - params: defaultParams, - headers: defaultHeaders, - elem: defaultElem - }); - return bindedFunction(callParams.params, callParams.headers, callParams.elem); - }; + function addRestangularMethodFunction(name, operation, path, defaultParams, defaultHeaders, defaultElem) { + var bindedFunction; + if (operation === 'getList') { + bindedFunction = _.bind(fetchFunction, this, path); + } else { + bindedFunction = _.bind(customFunction, this, operation, path); + } - if (config.isSafe(operation)) { - this[name] = createdFunction; - } else { - this[name] = function(elem, params, headers) { - return createdFunction(params, headers, elem); + var createdFunction = function(params, headers, elem) { + var callParams = _.defaults({ + params: params, + headers: headers, + elem: elem + }, { + params: defaultParams, + headers: defaultHeaders, + elem: defaultElem + }); + return bindedFunction(callParams.params, callParams.headers, callParams.elem); }; + + if (config.isSafe(operation)) { + this[name] = createdFunction; + } else { + this[name] = function(elem, params, headers) { + return createdFunction(params, headers, elem); + }; + } } - } - function withConfigurationFunction(configurer) { - var newConfig = angular.copy(_.omit(config, 'configuration')); - Configurer.init(newConfig, newConfig); - configurer(newConfig); - return createServiceForConfiguration(newConfig); - } + function withConfigurationFunction(configurer) { + var newConfig = angular.copy(_.omit(config, 'configuration')); + Configurer.init(newConfig, newConfig); + configurer(newConfig); + return createServiceForConfiguration(newConfig); + } - function toService(route, parent) { - var knownCollectionMethods = _.values(config.restangularFields); - var serv = {}; - var collection = (parent || service).all(route); - serv.one = _.bind(one, (parent || service), parent, route); - serv.post = _.bind(collection.post, collection); - serv.getList = _.bind(collection.getList, collection); - serv.withHttpConfig = _.bind(collection.withHttpConfig, collection); - serv.get = _.bind(collection.get, collection); - - for (var prop in collection) { - if (collection.hasOwnProperty(prop) && _.isFunction(collection[prop]) && !_.includes(knownCollectionMethods, prop)) { - serv[prop] = _.bind(collection[prop], collection); + function toService(route, parent) { + var knownCollectionMethods = _.values(config.restangularFields); + var serv = {}; + var collection = (parent || service).all(route); + serv.one = _.bind(one, (parent || service), parent, route); + serv.post = _.bind(collection.post, collection); + serv.getList = _.bind(collection.getList, collection); + serv.withHttpConfig = _.bind(collection.withHttpConfig, collection); + serv.get = _.bind(collection.get, collection); + + for (var prop in collection) { + if (collection.hasOwnProperty(prop) && _.isFunction(collection[prop]) && !_.includes(knownCollectionMethods, prop)) { + serv[prop] = _.bind(collection[prop], collection); + } } - } - return serv; - } + return serv; + } - Configurer.init(service, config); + Configurer.init(service, config); - service.copy = _.bind(copyRestangularizedElement, service); + service.copy = _.bind(copyRestangularizedElement, service); - service.service = _.bind(toService, service); + service.service = _.bind(toService, service); - service.withConfig = _.bind(withConfigurationFunction, service); + service.withConfig = _.bind(withConfigurationFunction, service); - service.one = _.bind(one, service, null); + service.one = _.bind(one, service, null); - service.all = _.bind(all, service, null); + service.all = _.bind(all, service, null); - service.several = _.bind(several, service, null); + service.several = _.bind(several, service, null); - service.oneUrl = _.bind(oneUrl, service, null); + service.oneUrl = _.bind(oneUrl, service, null); - service.allUrl = _.bind(allUrl, service, null); + service.allUrl = _.bind(allUrl, service, null); - service.stripRestangular = _.bind(stripRestangular, service); + service.stripRestangular = _.bind(stripRestangular, service); - service.restangularizeElement = _.bind(restangularizeElem, service); + service.restangularizeElement = _.bind(restangularizeElem, service); - service.restangularizeCollection = _.bind(restangularizeCollectionAndElements, service); + service.restangularizeCollection = _.bind(restangularizeCollectionAndElements, service); - return service; - } + return service; + } - return createServiceForConfiguration(globalConfiguration); - }]; -}); + return createServiceForConfiguration(globalConfiguration); + }]; + }); return restangular.name; })); diff --git a/test/restangularSpec.js b/test/restangularSpec.js index 47fecfdb..384c20b3 100644 --- a/test/restangularSpec.js +++ b/test/restangularSpec.js @@ -11,30 +11,82 @@ describe('Restangular', function() { // Init HTTP mock backend and Restangular resources beforeEach(inject(function($injector) { // Model - accountsModel = [ - {id: 0, user: 'Martin ', amount: 42, transactions: []}, - {id: 1, user: 'Paul', amount: 3.1416, transactions: [{from: 'Martin', amount: 3, id: 0}, {from: 'Anonymous', amount: 0.1416, id:1}]} - ]; + accountsModel = [{ + id: 0, + user: 'Martin ', + amount: 42, + transactions: [] + }, { + id: 1, + user: 'Paul', + amount: 3.1416, + transactions: [{ + from: 'Martin', + amount: 3, + id: 0 + }, { + from: 'Anonymous', + amount: 0.1416, + id: 1 + }] + }]; nextAccountId = 22; // HAL model (http://stateless.co/hal_specification.html) - accountsHalModel = [ - {id: 0, user: 'Martin', amount: 42, transaction: [], _links: {self: '/accountsHAL/martin'}}, - {id: 1, user: 'Paul', amount: 3.1416, transaction: [ - {from: 'Martin', amount: 3, id: 0, _links: {self: '/accountsHAL/paul/transactions/0'}}, - {from: 'Anonymous', amount: 0.1416, id: 1, _links: {self: '/accountsHAL/paul/transactions/1'}} - ], _links: {self: '/accountsHAL/paul'}} - ]; + accountsHalModel = [{ + id: 0, + user: 'Martin', + amount: 42, + transaction: [], + _links: { + self: '/accountsHAL/martin' + } + }, { + id: 1, + user: 'Paul', + amount: 3.1416, + transaction: [{ + from: 'Martin', + amount: 3, + id: 0, + _links: { + self: '/accountsHAL/paul/transactions/0' + } + }, { + from: 'Anonymous', + amount: 0.1416, + id: 1, + _links: { + self: '/accountsHAL/paul/transactions/1' + } + }], + _links: { + self: '/accountsHAL/paul' + } + }]; infoModel = { - id: 0, text: 'Some additional account information' + id: 0, + text: 'Some additional account information' }; - newAccount = {user: 'First User', amount: 45, transactions: []}; + newAccount = { + user: 'First User', + amount: 45, + transactions: [] + }; - messages = [{id: 23, name: 'Gonto'}, {id: 45, name: 'John'}]; + messages = [{ + id: 23, + name: 'Gonto' + }, { + id: 45, + name: 'John' + }]; - accountsDoSomethingModel = { result: 1 }; + accountsDoSomethingModel = { + result: 1 + }; $httpBackend = $injector.get('$httpBackend'); @@ -129,37 +181,31 @@ describe('Restangular', function() { // Another API for testing - customers = [ - { - id: 0, - name: 'Alice', - status: 'active', - credit: 4000.0 - }, - { - id: 1, - name: 'Bob', - status: 'active', - credit: 4000.0 - }, - { - id: 2, - name: 'Carl', - status: 'active', - credit: 4000.0 - } - ]; - publications = [ - { - id: 1, - title: 'Sample', - content: 'Rich data', - tags: [ - 'science', - 'chemistry' - ] - } - ]; + customers = [{ + id: 0, + name: 'Alice', + status: 'active', + credit: 4000.0 + }, { + id: 1, + name: 'Bob', + status: 'active', + credit: 4000.0 + }, { + id: 2, + name: 'Carl', + status: 'active', + credit: 4000.0 + }]; + publications = [{ + id: 1, + title: 'Sample', + content: 'Rich data', + tags: [ + 'science', + 'chemistry' + ] + }]; newCustomer = { id: 3, name: 'New', @@ -172,11 +218,11 @@ describe('Restangular', function() { $httpBackend.whenGET('api.new.domain/customers/').respond(customers); $httpBackend.whenGET('/customers/?active=true').respond(customers); $httpBackend.whenGET('/customers/publications/?tags=chemistry').respond(publications); - $httpBackend.whenPUT('/customers/0').respond(function (method, url, data) { + $httpBackend.whenPUT('/customers/0').respond(function(method, url, data) { customers[0] = angular.fromJson(data); return [200, data, '']; }); - $httpBackend.whenPOST('/customers/').respond(function (method, url, data, headers) { + $httpBackend.whenPOST('/customers/').respond(function(method, url, data, headers) { var newData = angular.fromJson(data); newData.fromServer = true; return [201, JSON.stringify(newData), '']; @@ -235,12 +281,14 @@ describe('Restangular', function() { $httpBackend.expectPOST('/list'); - Restangular.all('list').post({name: 'Gonto'}).then(function(elem) { + Restangular.all('list').post({ + name: 'Gonto' + }).then(function(elem) { expect(elem.firstResponseInterceptor).toBeDefined(); expect(elem.secondResponseInterceptor).toBeDefined(); - }); + }); - $httpBackend.flush(); + $httpBackend.flush(); }); it('Should add multiple error interceptors', function() { @@ -328,8 +376,8 @@ describe('Restangular', function() { Restangular.addErrorInterceptor(CallbackManager.secondErrorInterceptor); Restangular.one('error', 404).get() - .then(CallbackManager.successCallback) - .catch(CallbackManager.errorCallback); + .then(CallbackManager.successCallback) + .catch(CallbackManager.errorCallback); $httpBackend.flush(); }); @@ -339,7 +387,9 @@ describe('Restangular', function() { it('Should decorate element both on server and local by default', function() { Restangular.extendModel('accounts', function(account) { - account.extended = function() {return true;}; + account.extended = function() { + return true; + }; return account; }); @@ -416,7 +466,10 @@ describe('Restangular', function() { it('shouldn\'t override post', function() { Restangular.setJsonp(true); - restangularAccounts.post({id: 2, user: 'Someone'}); + restangularAccounts.post({ + id: 2, + user: 'Someone' + }); $httpBackend.expectPOST('/accounts').respond(201, ''); $httpBackend.flush(); @@ -440,7 +493,7 @@ describe('Restangular', function() { }); it('Should restangularize a function with arguments OK', function() { - var collection = function(a, b) { }; + var collection = function(a, b) {}; Restangular.restangularizeCollection(null, collection, 'accounts'); @@ -505,7 +558,9 @@ describe('Restangular', function() { it('Shouldn\'t be restangularized by default', function() { Restangular.extendModel('accounts', function(account) { - account.extended = function() {return true;}; + account.extended = function() { + return true; + }; return account; }); @@ -578,7 +633,10 @@ describe('Restangular', function() { }); it('post() should add a new item', function() { - restangularAccounts.post({id: 2, user: 'Someone'}).then(function() { + restangularAccounts.post({ + id: 2, + user: 'Someone' + }).then(function() { expect(accountsModel.length).toEqual(2); }); @@ -587,7 +645,10 @@ describe('Restangular', function() { }); it('customPOST() should add a new item', function() { - restangularAccounts.customPOST({id: 2, user: 'Someone'}).then(function() { + restangularAccounts.customPOST({ + id: 2, + user: 'Someone' + }).then(function() { expect(accountsModel.length).toEqual(2); }); @@ -596,32 +657,36 @@ describe('Restangular', function() { }); it('post() should work with arrays', function() { - Restangular.all('places').post([{name: 'Gonto'}, {name: 'John'}]).then(function(value) { - expect(value.length).toEqual(2); - }); + Restangular.all('places').post([{ + name: 'Gonto' + }, { + name: 'John' + }]).then(function(value) { + expect(value.length).toEqual(2); + }); - $httpBackend.expectPOST('/places').respond(function(method, url, data, headers) { - return [201, angular.fromJson(data), '']; - }); + $httpBackend.expectPOST('/places').respond(function(method, url, data, headers) { + return [201, angular.fromJson(data), '']; + }); - $httpBackend.flush(); - }); + $httpBackend.flush(); + }); it('post() should add a new item with data and return the data from the server', function() { - restangularAccounts.post(newAccount).then(function(added) { - expect(added.fromServer).toEqual(true); - expect(added.id).toEqual(nextAccountId); - expect(added.user).toEqual(newAccount.user); - }); + restangularAccounts.post(newAccount).then(function(added) { + expect(added.fromServer).toEqual(true); + expect(added.id).toEqual(nextAccountId); + expect(added.user).toEqual(newAccount.user); + }); - $httpBackend.expectPOST('/accounts'); - $httpBackend.flush(); - }); + $httpBackend.expectPOST('/accounts'); + $httpBackend.flush(); + }); it('Doing a post and then other operation (delete) should call right URLs', function() { restangularAccounts.post(newAccount).then(function(added) { added.remove(); - $httpBackend.expectDELETE('/accounts/'+nextAccountId).respond(201, ''); + $httpBackend.expectDELETE('/accounts/' + nextAccountId).respond(201, ''); }); $httpBackend.flush(); @@ -629,7 +694,10 @@ describe('Restangular', function() { it('Doing a post to a server that returns no element will return undefined', function() { restangularAccounts.getList().then(function(accounts) { - var newTransaction = {id: 1, name: 'Gonto'}; + var newTransaction = { + id: 1, + name: 'Gonto' + }; accounts[1].post('transactions', newTransaction).then(function(transaction) { expect(transaction).toBeUndefined(); }); @@ -655,13 +723,17 @@ describe('Restangular', function() { it('customPUT should work', function() { $httpBackend.expectPUT('/accounts/hey').respond(accountsModel); - restangularAccounts.customPUT({key: 'value'}, 'hey'); + restangularAccounts.customPUT({ + key: 'value' + }, 'hey'); $httpBackend.flush(); }); it('customPATCH should work', function() { - var data = { foo: 'bar' }; + var data = { + foo: 'bar' + }; $httpBackend.expectPATCH('/accounts/hey', data).respond(accountsModel); restangularAccounts.customPATCH(data, 'hey'); $httpBackend.flush(); @@ -675,17 +747,23 @@ describe('Restangular', function() { $httpBackend.flush(); }); - it('getList() should correctly handle params after customDELETE', function() { + it('getList() should correctly handle params after customDELETE', function() { $httpBackend.expectGET('/accounts?foo=1').respond(accountsModel); - restangularAccounts.getList({foo: 1}).then(function(){ + restangularAccounts.getList({ + foo: 1 + }).then(function() { $httpBackend.expectDELETE('/accounts?id=1').respond(201, ''); - return restangularAccounts.customDELETE('', {id: 1}); + return restangularAccounts.customDELETE('', { + id: 1 + }); }).then(function() { - $httpBackend.expectGET('/accounts?foo=1').respond(accountsModel); - return restangularAccounts.getList({foo: 1}); - }).then(function(accounts) { - expect(Restangular.stripRestangular(accounts)).toEqual(Restangular.stripRestangular(accountsModel)); + $httpBackend.expectGET('/accounts?foo=1').respond(accountsModel); + return restangularAccounts.getList({ + foo: 1 }); + }).then(function(accounts) { + expect(Restangular.stripRestangular(accounts)).toEqual(Restangular.stripRestangular(accountsModel)); + }); $httpBackend.flush(); }); @@ -703,7 +781,7 @@ describe('Restangular', function() { $httpBackend.expectGET('/accounts/0'); $httpBackend.expectGET('/accounts'); $httpBackend.flush(); - }); + }); it('should correctly work with children', function() { var Transactions = Restangular.service('transactions', restangularAccount1); @@ -717,7 +795,7 @@ describe('Restangular', function() { $httpBackend.expectGET('/accounts/1/transactions'); $httpBackend.expectGET('/accounts/1/transactions/1'); $httpBackend.flush(); - }); + }); it('should add custom collection method added with withConfig', function() { var Accounts = Restangular.withConfig(function(RestangularConfigurer) { @@ -746,7 +824,9 @@ describe('Restangular', function() { it('should provide a one-off $http configuration method', function() { var Accounts = Restangular.service('accounts'); - Accounts.withHttpConfig({transformRequest: angular.identity}); + Accounts.withHttpConfig({ + transformRequest: angular.identity + }); Accounts.post(newAccount); $httpBackend.expectPOST('/accounts'); $httpBackend.flush(); @@ -877,7 +957,7 @@ describe('Restangular', function() { // with fromServer=false var account = Restangular.one('accounts', 123), - copiedAccount = Restangular.copy(account); + copiedAccount = Restangular.copy(account); expect(account.fromServer).toEqual(copiedAccount.fromServer); $httpBackend.flush(); @@ -886,7 +966,7 @@ describe('Restangular', function() { describe('getRestangularUrl', function() { it('should return the generated URL when you chain Restangular methods together', function() { - var restangularSpaces = Restangular.one('accounts',123).one('buildings', 456).all('spaces'); + var restangularSpaces = Restangular.one('accounts', 123).one('buildings', 456).all('spaces'); expect(restangularSpaces.getRestangularUrl()).toEqual('/accounts/123/buildings/456/spaces'); }); }); @@ -896,7 +976,7 @@ describe('Restangular', function() { var R = Restangular.withConfig(function(config) { config.setUseCannonicalId(true); }); - var restangularSpaces = R.one('accounts',123).one('buildings', 456).all('spaces'); + var restangularSpaces = R.one('accounts', 123).one('buildings', 456).all('spaces'); expect(restangularSpaces.getRestangularUrl()).toEqual('/accounts/123/buildings/456/spaces'); }); }); @@ -907,8 +987,8 @@ describe('Restangular', function() { var accountsPromise; Restangular.addElementTransformer('accounts', true, function(collection) { - collection.totalAmount = function() {}; - return collection; + collection.totalAmount = function() {}; + return collection; }); accountsPromise = Restangular.all('accounts').getList(); @@ -924,8 +1004,8 @@ describe('Restangular', function() { var accountPromise; Restangular.addElementTransformer('accounts', false, function(model) { - model.prettifyAmount = function() {}; - return model; + model.prettifyAmount = function() {}; + return model; }); accountPromise = Restangular.one('accounts', 1).get(); @@ -941,8 +1021,8 @@ describe('Restangular', function() { var accountPromise; Restangular.addElementTransformer('accounts', false, function(model) { - model.prettifyAmount = function() {}; - return model; + model.prettifyAmount = function() {}; + return model; }); accountsPromise = Restangular.all('accounts', 1).getList(); @@ -956,12 +1036,12 @@ describe('Restangular', function() { $httpBackend.flush(); }); - it("should allow for a custom method to be placed at the collection level using a regexp matching the route", function () { + it("should allow for a custom method to be placed at the collection level using a regexp matching the route", function() { var accountPromise; Restangular.addElementTransformer(/^accounts/, false, function(model) { - model.prettifyAmount = function() {}; - return model; + model.prettifyAmount = function() {}; + return model; }); accountsPromise = Restangular.all('accounts/search/byOwner', 1).getList(); @@ -979,8 +1059,8 @@ describe('Restangular', function() { var accountPromise; Restangular.addElementTransformer(/^accounts/, false, function(model) { - model.prettifyAmount = function() {}; - return model; + model.prettifyAmount = function() {}; + return model; }); accountPromise = Restangular.one('accounts', 1).get(); @@ -996,8 +1076,8 @@ describe('Restangular', function() { var accountPromise; Restangular.addElementTransformer(/^accounts/, false, function(model) { - model.prettifyAmount = function() {}; - return model; + model.prettifyAmount = function() {}; + return model; }); accountsPromise = Restangular.all('accounts', 1).getList(); @@ -1045,7 +1125,9 @@ describe('Restangular', function() { describe('headers', function() { it('should return defaultHeaders', function() { - var defaultHeaders = {testheader:'header value'}; + var defaultHeaders = { + testheader: 'header value' + }; Restangular.setDefaultHeaders(defaultHeaders); expect(Restangular.defaultHeaders).toEqual(defaultHeaders); }); @@ -1063,7 +1145,9 @@ describe('Restangular', function() { describe('defaultRequestParams', function() { it('should return defaultRequestParams', function() { - var defaultRequestParams = {param:'value'}; + var defaultRequestParams = { + param: 'value' + }; Restangular.setDefaultRequestParams(defaultRequestParams); @@ -1071,8 +1155,12 @@ describe('Restangular', function() { }); it('should be able to set default params for get, post, put.. methods separately', function() { - var postParams = {post:'value'}, - putParams = {put:'value'}; + var postParams = { + post: 'value' + }, + putParams = { + put: 'value' + }; Restangular.setDefaultRequestParams('post', postParams); expect(Restangular.requestParams.post).toEqual(postParams); @@ -1084,7 +1172,9 @@ describe('Restangular', function() { }); it('should be able to set default params for multiple methods with array', function() { - var defaultParams = {param:'value'}; + var defaultParams = { + param: 'value' + }; Restangular.setDefaultRequestParams(['post', 'put'], defaultParams); @@ -1097,7 +1187,7 @@ describe('Restangular', function() { describe('withConfig', function() { it('should create new service with scoped configuration', function() { - var childRestangular = Restangular.withConfig(function(RestangularConfigurer){ + var childRestangular = Restangular.withConfig(function(RestangularConfigurer) { RestangularConfigurer.setBaseUrl('/api/v1'); }); @@ -1107,11 +1197,11 @@ describe('Restangular', function() { }); it('should allow nested configurations', function() { - var childRestangular = Restangular.withConfig(function(RestangularConfigurer){ + var childRestangular = Restangular.withConfig(function(RestangularConfigurer) { RestangularConfigurer.setBaseUrl('/api/v1'); }); - var grandchildRestangular = childRestangular.withConfig(function(RestangularConfigurer){ + var grandchildRestangular = childRestangular.withConfig(function(RestangularConfigurer) { RestangularConfigurer.setRequestSuffix('.json'); }); @@ -1155,7 +1245,7 @@ describe('Restangular', function() { it('getRestangularUrl() returns still the url without id after GET', function() { record = Restangular.one('info', 0, true); - record.get().then(function(data){ + record.get().then(function(data) { expect(data.getRestangularUrl()).toEqual('/info'); }); $httpBackend.expectGET('/info'); @@ -1177,7 +1267,7 @@ describe('Restangular', function() { describe('setSelfLinkAbsoluteUrl', function() { it('works', function() { - var childRestangular = Restangular.withConfig(function(RestangularConfigurer){ + var childRestangular = Restangular.withConfig(function(RestangularConfigurer) { RestangularConfigurer.setSelfLinkAbsoluteUrl(false); }); @@ -1186,39 +1276,44 @@ describe('Restangular', function() { }); }); - describe('Misc', function () { - it('should not strip [one] or [all] key from plain object', function () { - Restangular.all('customs').customPOST({one: 'I am here', two: 'I am also here'}).then(function () { + describe('Misc', function() { + it('should not strip [one] or [all] key from plain object', function() { + Restangular.all('customs').customPOST({ + one: 'I am here', + two: 'I am also here' + }).then(function() { expect(1).toBe(1); - }, function () { + }, function() { expect('Promise').toBe('correctly fulfilled'); }); $httpBackend.flush(); }); - it('should not stip non-restangularized elements', function () { - expect(Restangular.stripRestangular(['test','test2'])).toEqual(['test','test2']); + it('should not stip non-restangularized elements', function() { + expect(Restangular.stripRestangular(['test', 'test2'])).toEqual(['test', 'test2']); }); - it('should accept 0 as response', function () { + it('should accept 0 as response', function() { Restangular.one('misc', 'zero').get().then(function(res) { expect(res).toEqual(0); }); $httpBackend.flush(); }); - it('Should accept 0 as a proper id in custom requests', function () { + it('Should accept 0 as a proper id in custom requests', function() { $httpBackend.expectDELETE('/accounts/0').respond(202); Restangular.all('accounts').customDELETE(0); $httpBackend.flush(); }); }); - describe('testing normalize url', function () { + describe('testing normalize url', function() { - it('should get a list of objects', function () { - Restangular.all('customers/').getList().then(function(res){ - res.getList({active: true}); + it('should get a list of objects', function() { + Restangular.all('customers/').getList().then(function(res) { + res.getList({ + active: true + }); $httpBackend.expectGET('/customers/?active=true'); //res.getList('publications/', {tags: 'chemistry'}); //$httpBackend.expectGET('/customers/publications/?tags=chemistry'); @@ -1227,17 +1322,19 @@ describe('Restangular', function() { $httpBackend.flush(); }); - it('should get a list of objects even if the path has extra slashes', function () { - Restangular.all('customers///').getList().then(function(res){ - res.getList({active: true}); + it('should get a list of objects even if the path has extra slashes', function() { + Restangular.all('customers///').getList().then(function(res) { + res.getList({ + active: true + }); $httpBackend.expectGET('/customers/?active=true'); }); $httpBackend.expectGET('/customers/'); $httpBackend.flush(); }); - it('should post with slash at the end', function () { - Restangular.all('customers/').getList().then(function(res){ + it('should post with slash at the end', function() { + Restangular.all('customers/').getList().then(function(res) { res.post(newCustomer); $httpBackend.expectPOST('/customers/'); }); @@ -1245,8 +1342,8 @@ describe('Restangular', function() { $httpBackend.flush(); }); - it('should put with slash at the end', function () { - Restangular.all('customers/').getList().then(function(customers){ + it('should put with slash at the end', function() { + Restangular.all('customers/').getList().then(function(customers) { customers[0].put(); $httpBackend.expectPUT('/customers/0'); }); @@ -1259,14 +1356,14 @@ describe('Restangular', function() { }); it('should create a new service and still working normalized URL', function() { - var newRes = Restangular.withConfig(function(RestangularConfigurer){ + var newRes = Restangular.withConfig(function(RestangularConfigurer) { RestangularConfigurer.setBaseUrl('http://localhost:8080'); }); expect(newRes.configuration.baseUrl).toEqual('http://localhost:8080'); newRes.all('customers////').getList(); $httpBackend.expectGET('http://localhost:8080/customers/'); - var newApi = Restangular.withConfig(function(RestangularConfigurer){ + var newApi = Restangular.withConfig(function(RestangularConfigurer) { RestangularConfigurer.setBaseUrl('api.new.domain'); }); @@ -1278,7 +1375,7 @@ describe('Restangular', function() { }); it('Should work with absolute URL with //authority', function() { - var newRes = Restangular.withConfig(function(RestangularConfigurer){ + var newRes = Restangular.withConfig(function(RestangularConfigurer) { RestangularConfigurer.setBaseUrl('//localhost:8080'); }); expect(newRes.configuration.baseUrl).toEqual('//localhost:8080'); @@ -1298,7 +1395,7 @@ describe('Restangular', function() { expect(newRes.configuration.plainByDefault).toEqual(true); - newRes.one('accounts',0).get().then(function(account) { + newRes.one('accounts', 0).get().then(function(account) { expect(account).toEqual(accountsModel[0]); }); @@ -1310,7 +1407,7 @@ describe('Restangular', function() { RestangularConfigurer.setPlainByDefault(true); }); - newRes.all('accounts').getList().then(function(accounts){ + newRes.all('accounts').getList().then(function(accounts) { expect(accounts).toEqual(accountsModel); }); $httpBackend.flush(); From 8bfa6858139010cb5bfea9044fa289e451088e6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Mon, 26 Dec 2016 11:06:54 +0200 Subject: [PATCH 27/55] chore(docs) Reformat changelog, add unreleased section --- CHANGELOG.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0602d85..ef816d33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# Unreleased + + # 1.6.0 (2016-12-25) * Url now supports unescaped suffix (0350bcd) @@ -20,14 +23,11 @@ * Added context/explanation of when to use JSONP. (fec9b27) * Add regexp matching for route to element transformers (#1430) (de8f561) - -1.5.2 / 2016-02-15 -================== +# 1.5.2 (2016-02-15) * Change \_.contains to \_.includes for compatability with lodash >= 4.0 -1.5.1 / 2015-04-03 -================== +# 1.5.1 (2015-04-03) * Release 1.5.0 * Updated zip @@ -39,7 +39,7 @@ * Update README.md * Update README.md -1.5.0 / 2015-04-03 -================== +# 1.5.0 (2015-04-03) + * Tons of bug fixes * Upgraded Lodash to 1.3.0 From 7c30615f5c4cd8bd84b87b05c3deb9745fc1802a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Thu, 29 Dec 2016 23:31:26 +0200 Subject: [PATCH 28/55] chore(docs) Update link to demo Plunker, rephrase --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4a96a815..770b1321 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,9 @@ Restangular is an AngularJS service that simplifies common GET, POST, DELETE, and UPDATE requests with a minimum of client code. It's a perfect fit for any WebApp that consumes data from a RESTful API. -**Check out a, [live demo on plunkr](http://plnkr.co/edit/d6yDka?p=preview).** It uses the same example as the official [Angular Javascript Project](http://angularjs.org/#wire-up-a-backend)... but Restangularized! +Try the [live demo on plunkr](http://plnkr.co/edit/8qrGeE?p=preview). It uses the same example as the official [Angular Javascript Project](http://angularjs.org/#wire-up-a-backend), but with Restangular! -You can also **check out [a video introduction of a talk I gave at Devoxx France](http://www.parleys.com/play/535a189ee4b0c5ba17d43455/chapter1/about)** about Restangular +Watch [a video introduction of a talk I gave at Devoxx France](http://www.parleys.com/play/535a189ee4b0c5ba17d43455/chapter1/about) about Restangular. #Table of contents From 34b0e9a587b75c1e3f4b3dd2ec031b5eb7038e0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Thu, 29 Dec 2016 23:35:13 +0200 Subject: [PATCH 29/55] chore(docs) Update issue guidelines to include StackOverflow as source for solutions to problems --- .github/ISSUE_TEMPLATE.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index c43fb629..21495e4d 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,4 +1,17 @@ -To open an issue, please keep in mind a few important things. First, take a look at the README docs and make sure that your question isn't already answered in the documentation. In particular, review [the configuration methods](https://github.com/mgonto/restangular#configuring-restangular) and [methods description](https://github.com/mgonto/restangular#methods-description). Then make sure you search the issues list to see if there's already an issue open that solves your problem. Then, once you've determined that your issue isn't a duplicate, here a couple guidelines to opening an issue that will be addressed easily and quickly: +The project lacks resources to answer questions regarding general usage and problems occurring from misconfiguration and or improper use. That's why we kindly ask you to following the following guidelines when considering opening an issue. -- Please make sure your issue is written in a clear and succint manner, so that all the details of your issue are easy to understand and fully explained. Also, please make sure enclose your code in [fenced blocks](https://help.github.com/articles/creating-and-highlighting-code-blocks/) for readability. -- Make sure your issue includes a live Plunker (fork [this Plunker](http://plnkr.co/edit/26Heuv5F6hUgxpNWNTee?p=info)), and all relevant code samples (as well as information about server responses, if relevant) +**Before opening an issue:** + +* Please **carefully read the README** and make sure that your question isn't already answered in the documentation. +* Please **review [the configuration methods](https://github.com/mgonto/restangular#configuring-restangular) and [methods description](https://github.com/mgonto/restangular#methods-description)** once more. +* Please **search the existing issue list** to see if there's already an issue **open or closed** that solves your problem. +* Please **search for and/or ask your question on [StackOverflow](http://stackoverflow.com/search?q=restangular)**. You will get answers more quickly there than here. +* Please **debug your problem long and hard** enough to make sure the issue is in Restangular and not in your code and/or backend. + +**When opening an issue:** + +* Please **create a live Plunker** (fork [this Plunker](http://plnkr.co/edit/26Heuv5F6hUgxpNWNTee?p=info)) demonstrating the problem, use [httpbin](https://httpbin.org/) or [JSONPlaceholder](https://jsonplaceholder.typicode.com/) or similar as backend if needed. +* Please **write your issue in a clear and succint manner**, so that all the details of your issue are easy to understand and fully explained. +* Please **enclose your code in [fenced blocks](https://help.github.com/articles/creating-and-highlighting-code-blocks/)** for readability. + +The maintainers may opt to close issues that don't follow these guidelines. From 6883075b5818f66dfdb5a92c3f3901d63dd2c2eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Thu, 29 Dec 2016 23:43:52 +0200 Subject: [PATCH 30/55] chore(docs) Add note about pull requests and github preview tab --- .github/ISSUE_TEMPLATE.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 21495e4d..c220e3e6 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,3 +1,5 @@ +*Switch to the Preview tab to read this message in the Github issue form.* + The project lacks resources to answer questions regarding general usage and problems occurring from misconfiguration and or improper use. That's why we kindly ask you to following the following guidelines when considering opening an issue. **Before opening an issue:** From 8552c519c525d27fd6e2559b9f931b20e9625e24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Fri, 30 Dec 2016 13:15:18 +0200 Subject: [PATCH 31/55] feat(docs) Add FAQ about cancelling request Closes #926, closes #1145, closes #1377, closes #1391 --- .github/ISSUE_TEMPLATE.md | 5 +++++ README.md | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index c220e3e6..044bad5c 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -17,3 +17,8 @@ The project lacks resources to answer questions regarding general usage and prob * Please **enclose your code in [fenced blocks](https://help.github.com/articles/creating-and-highlighting-code-blocks/)** for readability. The maintainers may opt to close issues that don't follow these guidelines. + +**Also remember:** + +* Pull requests fixing problems are highly appreciated ;) + diff --git a/README.md b/README.md index 770b1321..359bf272 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,7 @@ Watch [a video introduction of a talk I gave at Devoxx France](http://www.parley - [How can I access the unrestangularized element as well as the restangularized one?](#how-can-i-access-the-unrestangularized-element-as-well-as-the-restangularized-one) - [Restangular fails with status code 0](#restangular-fails-with-status-code-0) - [Why does this depend on Lodash / Underscore?](#why-does-this-depend-on-lodash--underscore) + - [How do I cancel a request?](#how-do-i-cancel-a-request) - [Supported Angular versions](#supported-angular-versions) - [Server Frameworks](#server-frameworks) - [Releases Notes](#releases-notes) @@ -1306,6 +1307,24 @@ So, why not use it? If you've never heard of them, by using Restangular, you cou **[Back to top](#table-of-contents)** +#### How do I cancel a request? + +Sometimes you may wish to cancel a request, this is how you would do it: + +``` +var canceler = $q.defer(); +Restangular.all('users').withHttpConfig({timeout: canceler.promise}).get(); +canceler.resolve(); // cancels the request +``` + +This is a little counterintuitive, so let me explain. Restangular is built on top of `$http`, which takes a timeout parameter. As per the $http docs: + + timeout in milliseconds, or promise that should abort the request when resolved. + +Resolving the promise (canceler in this case), will cancel the request. + +**[Back to top](#table-of-contents)** + # Supported Angular versions Restangular supports all Angular versions from 1.0.X - 1.5.X From 1a988cb508def7ac333d10f787ba159725d73cd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Wed, 4 Jan 2017 20:48:55 +0200 Subject: [PATCH 32/55] chore(test) fix jshint errors in spec file --- test/restangularSpec.js | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/test/restangularSpec.js b/test/restangularSpec.js index 384c20b3..a94dff14 100644 --- a/test/restangularSpec.js +++ b/test/restangularSpec.js @@ -1,9 +1,12 @@ +/* global describe, beforeEach, inject, afterEach, it, expect, spyOn */ +/* jshint unused: false */ describe('Restangular', function() { // API - var Restangular, $httpBackend; - var accountsModel, restangularAccounts, restangularAccount0, restangularAccount1; - var accountsHalModel; - var messages, newAccount, nextAccountId; + var Restangular, $httpBackend, + accountsModel, restangularAccounts, restangularAccount0, restangularAccount1, + infoModel, accountsDoSomethingModel, customers, publications, newCustomer, + accountsHalModel, + messages, newAccount, nextAccountId; // Load required modules beforeEach(angular.mock.module('restangular')); @@ -873,7 +876,7 @@ describe('Restangular', function() { }).then(function(accountFromServer2) { expect(accountFromServer2.route).toBe(account1.route); }); - $httpBackend.flush() + $httpBackend.flush(); }); it('Should make RequestLess connections with one', function() { @@ -1018,7 +1021,7 @@ describe('Restangular', function() { }); it('should allow for a custom method to be placed at the model level when several models are requested', function() { - var accountPromise; + var accountsPromise; Restangular.addElementTransformer('accounts', false, function(model) { model.prettifyAmount = function() {}; @@ -1028,7 +1031,7 @@ describe('Restangular', function() { accountsPromise = Restangular.all('accounts', 1).getList(); accountsPromise.then(function(accounts) { - accounts.forEach(function(account, index) { + accounts.forEach(function(account) { expect(typeof account.prettifyAmount).toEqual('function'); }); }); @@ -1036,8 +1039,8 @@ describe('Restangular', function() { $httpBackend.flush(); }); - it("should allow for a custom method to be placed at the collection level using a regexp matching the route", function() { - var accountPromise; + it('should allow for a custom method to be placed at the collection level using a regexp matching the route', function() { + var accountsPromise; Restangular.addElementTransformer(/^accounts/, false, function(model) { model.prettifyAmount = function() {}; @@ -1047,15 +1050,15 @@ describe('Restangular', function() { accountsPromise = Restangular.all('accounts/search/byOwner', 1).getList(); accountsPromise.then(function(accounts) { - accounts.forEach(function(account, index) { - expect(typeof account.prettifyAmount).toEqual("function"); + accounts.forEach(function(account) { + expect(typeof account.prettifyAmount).toEqual('function'); }); }); $httpBackend.flush(); }); - it("should allow for a custom method to be placed at the model level using regexp route when one model is requested", function() { + it('should allow for a custom method to be placed at the model level using regexp route when one model is requested', function() { var accountPromise; Restangular.addElementTransformer(/^accounts/, false, function(model) { @@ -1066,14 +1069,14 @@ describe('Restangular', function() { accountPromise = Restangular.one('accounts', 1).get(); accountPromise.then(function(account) { - expect(typeof account.prettifyAmount).toEqual("function"); + expect(typeof account.prettifyAmount).toEqual('function'); }); $httpBackend.flush(); }); - it("should allow for a custom method to be placed at the model level using regexp when several models are requested", function() { - var accountPromise; + it('should allow for a custom method to be placed at the model level using regexp when several models are requested', function() { + var accountsPromise; Restangular.addElementTransformer(/^accounts/, false, function(model) { model.prettifyAmount = function() {}; @@ -1083,8 +1086,8 @@ describe('Restangular', function() { accountsPromise = Restangular.all('accounts', 1).getList(); accountsPromise.then(function(accounts) { - accounts.forEach(function(account, index) { - expect(typeof account.prettifyAmount).toEqual("function"); + accounts.forEach(function(account) { + expect(typeof account.prettifyAmount).toEqual('function'); }); }); @@ -1244,7 +1247,7 @@ describe('Restangular', function() { }); it('getRestangularUrl() returns still the url without id after GET', function() { - record = Restangular.one('info', 0, true); + var record = Restangular.one('info', 0, true); record.get().then(function(data) { expect(data.getRestangularUrl()).toEqual('/info'); }); From 7fd668b099b61c45916621ed5ead3dc2da8f64b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Wed, 4 Jan 2017 22:38:45 +0200 Subject: [PATCH 33/55] fix(copy) Copying collections now correctly sets route, fromServer and parent on the copy --- src/restangular.js | 4 +-- test/restangularSpec.js | 61 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/src/restangular.js b/src/restangular.js index 64a08df2..62fdd1d7 100644 --- a/src/restangular.js +++ b/src/restangular.js @@ -998,8 +998,8 @@ function copyRestangularizedElement(fromElement, toElement) { var copiedElement = angular.copy(fromElement, toElement); - return restangularizeElem(copiedElement[config.restangularFields.parentResource], - copiedElement, copiedElement[config.restangularFields.route], copiedElement[config.restangularFields.fromServer]); + return restangularizeElem(fromElement[config.restangularFields.parentResource], + copiedElement, fromElement[config.restangularFields.route], fromElement[config.restangularFields.fromServer]); } function restangularizeElem(parent, element, route, fromServer, collection, reqParams) { diff --git a/test/restangularSpec.js b/test/restangularSpec.js index a94dff14..354c630e 100644 --- a/test/restangularSpec.js +++ b/test/restangularSpec.js @@ -1,4 +1,4 @@ -/* global describe, beforeEach, inject, afterEach, it, expect, spyOn */ +/* global describe, beforeEach, inject, afterEach, it, expect, spyOn, jasmine */ /* jshint unused: false */ describe('Restangular', function() { // API @@ -952,18 +952,65 @@ describe('Restangular', function() { }); it('should copy an object and "fromServer" param should be the same with the copied object', function() { + var responseHandler = jasmine.createSpy(); + // with fromServer=true - restangularAccount1.get().then(function(account) { - var copiedAccount = Restangular.copy(account); - expect(account.fromServer).toEqual(copiedAccount.fromServer); - }); + restangularAccount1.get().then(responseHandler); + $httpBackend.flush(); + var account = responseHandler.calls[0].args[0], + copiedAccount = Restangular.copy(account); + expect(account.fromServer).toEqual(true); + expect(copiedAccount.fromServer).toEqual(true); // with fromServer=false - var account = Restangular.one('accounts', 123), + account = Restangular.one('accounts', 123), copiedAccount = Restangular.copy(account); - expect(account.fromServer).toEqual(copiedAccount.fromServer); + expect(account.fromServer).toEqual(false); + expect(copiedAccount.fromServer).toEqual(false); + }); + it('should copy a collection and "fromServer" param should stay the same', function () { + var responseHandler = jasmine.createSpy(); + + // with collections, fromServer=false + var accounts = Restangular.all('accounts'), + copiedAccounts = Restangular.copy(accounts); + expect(accounts.fromServer).toEqual(false); + expect(copiedAccounts.fromServer).toEqual(false); + + // with collections, fromServer = true; + restangularAccounts.getList().then(responseHandler); $httpBackend.flush(); + accounts = responseHandler.calls[0].args[0], + copiedAccounts = Restangular.copy(accounts); + expect(accounts.fromServer).toEqual(true); + expect(copiedAccounts.fromServer).toEqual(true); + }); + + it('should copy an object and "route" param should be the same in the copied object', function () { + // for element + var account = Restangular.one('accounts', 123), + copiedAccount = Restangular.copy(account); + expect(account.route).toEqual(copiedAccount.route); + + // for collection + var accounts = Restangular.all('accounts'), + copiedAccounts = Restangular.copy(accounts); + expect(accounts.route).toEqual(copiedAccounts.route); + }); + + it('should copy an object and the parent property should stay the same', function () { + // element + var user = Restangular.one('account', 12).one('user', 14), + userCopy = Restangular.copy(user); + expect(user.parentResource.route).toEqual('account'); + expect(userCopy.parentResource.route).toEqual('account'); + + // collection + var users = Restangular.one('account', 12).all('users'), + usersCopy = Restangular.copy(users); + expect(user.parentResource.route).toEqual('account'); + expect(usersCopy.parentResource.route).toEqual('account'); }); }); From fb242ae8b807a534ed5d68311eb94684cd78af67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Wed, 4 Jan 2017 15:03:28 +0200 Subject: [PATCH 34/55] fix(elementTransformer) matchTransformer now doesn't throw if route is undefined --- src/restangular.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/restangular.js b/src/restangular.js index 62fdd1d7..f0aa392d 100644 --- a/src/restangular.js +++ b/src/restangular.js @@ -492,7 +492,7 @@ var matchTransformers = config.matchTransformers; if (matchTransformers) { _.each(matchTransformers, function(transformer) { - if (route.match(transformer.regexp)) { + if (transformer.regexp.test(route)) { changedElem = transformer.transformer(isCollection, changedElem); } }); From c92b1388a70bdad3e1cf392aa9f5a7f8e1611b5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Thu, 5 Jan 2017 10:54:29 +0200 Subject: [PATCH 35/55] fix(copy) Collections are now copied/cloned properly Collection elementTransformers didn't get triggered earlier, as restangularizeElem was used for both elements and collections. Now restangularizeCollection is used when copying collections. --- src/restangular.js | 29 +++++++++++++++++++++++++---- test/restangularSpec.js | 18 ++++++++++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/restangular.js b/src/restangular.js index f0aa392d..644a2b8b 100644 --- a/src/restangular.js +++ b/src/restangular.js @@ -996,10 +996,31 @@ elem[config.restangularFields.doGETLIST] = elem[config.restangularFields.customGETLIST]; } - function copyRestangularizedElement(fromElement, toElement) { - var copiedElement = angular.copy(fromElement, toElement); - return restangularizeElem(fromElement[config.restangularFields.parentResource], - copiedElement, fromElement[config.restangularFields.route], fromElement[config.restangularFields.fromServer]); + function copyRestangularizedElement(element) { + var copiedElement = angular.copy(element); + + // check if we're dealing with a collection (i.e. an array) + // and restangularize the element using the proper restangularizer, + // element / collection + if (_.isArray(element)) { + return restangularizeCollection( + element[config.restangularFields.parentResource], + copiedElement, + element[config.restangularFields.route], + element[config.restangularFields.fromServer], + element[config.restangularFields.reqParams] + ); + } + + // not a collection, restangularize it as an element + return restangularizeElem( + element[config.restangularFields.parentResource], + copiedElement, + element[config.restangularFields.route], + element[config.restangularFields.fromServer], + element[config.restangularFields.restangularCollection], + element[config.restangularFields.reqParams] + ); } function restangularizeElem(parent, element, route, fromServer, collection, reqParams) { diff --git a/test/restangularSpec.js b/test/restangularSpec.js index 354c630e..a6a08221 100644 --- a/test/restangularSpec.js +++ b/test/restangularSpec.js @@ -1105,6 +1105,24 @@ describe('Restangular', function() { $httpBackend.flush(); }); + it('should work with cloned collections', function () { + var responseHandler = jasmine.createSpy(); + + Restangular.addElementTransformer(/^accounts/, true, function(collection) { + collection.customThing = 'customValue'; + return collection; + }); + + Restangular.all('accounts').getList().then(responseHandler); + $httpBackend.flush(); + + var accounts = responseHandler.calls[0].args[0]; + var accountsCopy = accounts.clone(); + + expect(accounts.customThing).toEqual('customValue'); + expect(accountsCopy.customThing).toEqual('customValue'); + }); + it('should allow for a custom method to be placed at the model level using regexp route when one model is requested', function() { var accountPromise; From 7c245a28f22e369d6b396e161bae9e7ffa226ae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Thu, 5 Jan 2017 11:55:22 +0200 Subject: [PATCH 36/55] Update dist files --- dist/restangular.js | 2323 ++++++++++++++++++++------------------- dist/restangular.min.js | 4 +- dist/restangular.zip | Bin 70984 -> 74515 bytes 3 files changed, 1186 insertions(+), 1141 deletions(-) diff --git a/dist/restangular.js b/dist/restangular.js index bff968f9..f014fd39 100644 --- a/dist/restangular.js +++ b/dist/restangular.js @@ -1,9 +1,10 @@ /** * Restful Resources service for AngularJS apps - * @version v1.6.0 - 2016-12-25 * @link https://github.com/mgonto/restangular + * @version v1.6.0 - 2017-01-05 * @link https://github.com/mgonto/restangular * @author Martin Gontovnikas * @license MIT License, http://www.opensource.org/licenses/MIT - */(function (root, factory) { + */(function(root, factory) { + /* global define, require */ // https://github.com/umdjs/umd/blob/master/templates/returnExports.js if (typeof define === 'function' && define.amd) { define(['lodash', 'angular'], factory); @@ -13,1395 +14,1439 @@ // No global export, Restangular will register itself as Angular.js module factory(root._, root.angular); } -}(this, function (_, angular) { - -var restangular = angular.module('restangular', []); - -restangular.provider('Restangular', function() { - // Configuration - var Configurer = {}; - Configurer.init = function(object, config) { - object.configuration = config; - - /** - * Those are HTTP safe methods for which there is no need to pass any data with the request. - */ - var safeMethods= ['get', 'head', 'options', 'trace', 'getlist']; - config.isSafe = function(operation) { - return _.includes(safeMethods, operation.toLowerCase()); - }; +}(this, function(_, angular) { + + var restangular = angular.module('restangular', []); + + restangular.provider('Restangular', function() { + // Configuration + var Configurer = {}; + Configurer.init = function(object, config) { + object.configuration = config; + + /** + * Those are HTTP safe methods for which there is no need to pass any data with the request. + */ + var safeMethods = ['get', 'head', 'options', 'trace', 'getlist']; + config.isSafe = function(operation) { + return _.includes(safeMethods, operation.toLowerCase()); + }; - var absolutePattern = /^https?:\/\//i; - config.isAbsoluteUrl = function(string) { - return _.isUndefined(config.absoluteUrl) || _.isNull(config.absoluteUrl) ? - string && absolutePattern.test(string) : - config.absoluteUrl; - }; + var absolutePattern = /^https?:\/\//i; + config.isAbsoluteUrl = function(string) { + return _.isUndefined(config.absoluteUrl) || _.isNull(config.absoluteUrl) ? + string && absolutePattern.test(string) : + config.absoluteUrl; + }; - config.absoluteUrl = _.isUndefined(config.absoluteUrl) ? true : config.absoluteUrl; - object.setSelfLinkAbsoluteUrl = function(value) { - config.absoluteUrl = value; - }; - /** - * This is the BaseURL to be used with Restangular - */ - config.baseUrl = _.isUndefined(config.baseUrl) ? '' : config.baseUrl; - object.setBaseUrl = function(newBaseUrl) { - config.baseUrl = /\/$/.test(newBaseUrl) ? - newBaseUrl.substring(0, newBaseUrl.length-1) : - newBaseUrl; - return this; - }; + config.absoluteUrl = _.isUndefined(config.absoluteUrl) ? true : config.absoluteUrl; + object.setSelfLinkAbsoluteUrl = function(value) { + config.absoluteUrl = value; + }; + /** + * This is the BaseURL to be used with Restangular + */ + config.baseUrl = _.isUndefined(config.baseUrl) ? '' : config.baseUrl; + object.setBaseUrl = function(newBaseUrl) { + config.baseUrl = /\/$/.test(newBaseUrl) ? + newBaseUrl.substring(0, newBaseUrl.length - 1) : + newBaseUrl; + return this; + }; - /** - * Sets the extra fields to keep from the parents - */ - config.extraFields = config.extraFields || []; - object.setExtraFields = function(newExtraFields) { - config.extraFields = newExtraFields; - return this; - }; + /** + * Sets the extra fields to keep from the parents + */ + config.extraFields = config.extraFields || []; + object.setExtraFields = function(newExtraFields) { + config.extraFields = newExtraFields; + return this; + }; - /** - * Some default $http parameter to be used in EVERY call - **/ - config.defaultHttpFields = config.defaultHttpFields || {}; - object.setDefaultHttpFields = function(values) { - config.defaultHttpFields = values; - return this; - }; + /** + * Some default $http parameter to be used in EVERY call + **/ + config.defaultHttpFields = config.defaultHttpFields || {}; + object.setDefaultHttpFields = function(values) { + config.defaultHttpFields = values; + return this; + }; - /** - * Always return plain data, no restangularized object - **/ - config.plainByDefault = config.plainByDefault || false; - object.setPlainByDefault = function(value) { - config.plainByDefault = value === true ? true : false; - return this; - }; + /** + * Always return plain data, no restangularized object + **/ + config.plainByDefault = config.plainByDefault || false; + object.setPlainByDefault = function(value) { + config.plainByDefault = value === true ? true : false; + return this; + }; - config.withHttpValues = function(httpLocalConfig, obj) { - return _.defaults(obj, httpLocalConfig, config.defaultHttpFields); - }; + config.withHttpValues = function(httpLocalConfig, obj) { + return _.defaults(obj, httpLocalConfig, config.defaultHttpFields); + }; - config.encodeIds = _.isUndefined(config.encodeIds) ? true : config.encodeIds; - object.setEncodeIds = function(encode) { - config.encodeIds = encode; - }; + config.encodeIds = _.isUndefined(config.encodeIds) ? true : config.encodeIds; + object.setEncodeIds = function(encode) { + config.encodeIds = encode; + }; - config.defaultRequestParams = config.defaultRequestParams || { - get: {}, - post: {}, - put: {}, - remove: {}, - common: {} - }; + config.defaultRequestParams = config.defaultRequestParams || { + get: {}, + post: {}, + put: {}, + remove: {}, + common: {} + }; - object.setDefaultRequestParams = function(param1, param2) { - var methods = [], + object.setDefaultRequestParams = function(param1, param2) { + var methods = [], params = param2 || param1; - if (!_.isUndefined(param2)) { - if (_.isArray(param1)) { - methods = param1; + if (!_.isUndefined(param2)) { + if (_.isArray(param1)) { + methods = param1; + } else { + methods.push(param1); + } } else { - methods.push(param1); + methods.push('common'); } - } else { - methods.push('common'); - } - - _.each(methods, function (method) { - config.defaultRequestParams[method] = params; - }); - return this; - }; - object.requestParams = config.defaultRequestParams; + _.each(methods, function(method) { + config.defaultRequestParams[method] = params; + }); + return this; + }; - config.defaultHeaders = config.defaultHeaders || {}; - object.setDefaultHeaders = function(headers) { - config.defaultHeaders = headers; - object.defaultHeaders = config.defaultHeaders; - return this; - }; + object.requestParams = config.defaultRequestParams; - object.defaultHeaders = config.defaultHeaders; + config.defaultHeaders = config.defaultHeaders || {}; + object.setDefaultHeaders = function(headers) { + config.defaultHeaders = headers; + object.defaultHeaders = config.defaultHeaders; + return this; + }; - /** - * Method overriders will set which methods are sent via POST with an X-HTTP-Method-Override - **/ - config.methodOverriders = config.methodOverriders || []; - object.setMethodOverriders = function(values) { - var overriders = _.extend([], values); - if (config.isOverridenMethod('delete', overriders)) { - overriders.push('remove'); - } - config.methodOverriders = overriders; - return this; - }; + object.defaultHeaders = config.defaultHeaders; - config.jsonp = _.isUndefined(config.jsonp) ? false : config.jsonp; - object.setJsonp = function(active) { - config.jsonp = active; - }; + /** + * Method overriders will set which methods are sent via POST with an X-HTTP-Method-Override + **/ + config.methodOverriders = config.methodOverriders || []; + object.setMethodOverriders = function(values) { + var overriders = _.extend([], values); + if (config.isOverridenMethod('delete', overriders)) { + overriders.push('remove'); + } + config.methodOverriders = overriders; + return this; + }; - config.isOverridenMethod = function(method, values) { - var search = values || config.methodOverriders; - return !_.isUndefined(_.find(search, function(one) { - return one.toLowerCase() === method.toLowerCase(); - })); - }; + config.jsonp = _.isUndefined(config.jsonp) ? false : config.jsonp; + object.setJsonp = function(active) { + config.jsonp = active; + }; - /** - * Sets the URL creator type. For now, only Path is created. In the future we'll have queryParams - **/ - config.urlCreator = config.urlCreator || 'path'; - object.setUrlCreator = function(name) { - if (!_.has(config.urlCreatorFactory, name)) { - throw new Error('URL Path selected isn\'t valid'); - } + config.isOverridenMethod = function(method, values) { + var search = values || config.methodOverriders; + return !_.isUndefined(_.find(search, function(one) { + return one.toLowerCase() === method.toLowerCase(); + })); + }; - config.urlCreator = name; - return this; - }; + /** + * Sets the URL creator type. For now, only Path is created. In the future we'll have queryParams + **/ + config.urlCreator = config.urlCreator || 'path'; + object.setUrlCreator = function(name) { + if (!_.has(config.urlCreatorFactory, name)) { + throw new Error('URL Path selected isn\'t valid'); + } - /** - * You can set the restangular fields here. The 3 required fields for Restangular are: - * - * id: Id of the element - * route: name of the route of this element - * parentResource: the reference to the parent resource - * - * All of this fields except for id, are handled (and created) by Restangular. By default, - * the field values will be id, route and parentResource respectively - */ - config.restangularFields = config.restangularFields || { - id: 'id', - route: 'route', - parentResource: 'parentResource', - restangularCollection: 'restangularCollection', - cannonicalId: '__cannonicalId', - etag: 'restangularEtag', - selfLink: 'href', - get: 'get', - getList: 'getList', - put: 'put', - post: 'post', - remove: 'remove', - head: 'head', - trace: 'trace', - options: 'options', - patch: 'patch', - getRestangularUrl: 'getRestangularUrl', - getRequestedUrl: 'getRequestedUrl', - putElement: 'putElement', - addRestangularMethod: 'addRestangularMethod', - getParentList: 'getParentList', - clone: 'clone', - ids: 'ids', - httpConfig: '_$httpConfig', - reqParams: 'reqParams', - one: 'one', - all: 'all', - several: 'several', - oneUrl: 'oneUrl', - allUrl: 'allUrl', - customPUT: 'customPUT', - customPATCH: 'customPATCH', - customPOST: 'customPOST', - customDELETE: 'customDELETE', - customGET: 'customGET', - customGETLIST: 'customGETLIST', - customOperation: 'customOperation', - doPUT: 'doPUT', - doPATCH: 'doPATCH', - doPOST: 'doPOST', - doDELETE: 'doDELETE', - doGET: 'doGET', - doGETLIST: 'doGETLIST', - fromServer: 'fromServer', - withConfig: 'withConfig', - withHttpConfig: 'withHttpConfig', - singleOne: 'singleOne', - plain: 'plain', - save: 'save', - restangularized: 'restangularized' - }; - object.setRestangularFields = function(resFields) { - config.restangularFields = - _.extend(config.restangularFields, resFields); - return this; - }; + config.urlCreator = name; + return this; + }; - config.isRestangularized = function(obj) { - return !!obj[config.restangularFields.restangularized]; - }; + /** + * You can set the restangular fields here. The 3 required fields for Restangular are: + * + * id: Id of the element + * route: name of the route of this element + * parentResource: the reference to the parent resource + * + * All of this fields except for id, are handled (and created) by Restangular. By default, + * the field values will be id, route and parentResource respectively + */ + config.restangularFields = config.restangularFields || { + id: 'id', + route: 'route', + parentResource: 'parentResource', + restangularCollection: 'restangularCollection', + cannonicalId: '__cannonicalId', + etag: 'restangularEtag', + selfLink: 'href', + get: 'get', + getList: 'getList', + put: 'put', + post: 'post', + remove: 'remove', + head: 'head', + trace: 'trace', + options: 'options', + patch: 'patch', + getRestangularUrl: 'getRestangularUrl', + getRequestedUrl: 'getRequestedUrl', + putElement: 'putElement', + addRestangularMethod: 'addRestangularMethod', + getParentList: 'getParentList', + clone: 'clone', + ids: 'ids', + httpConfig: '_$httpConfig', + reqParams: 'reqParams', + one: 'one', + all: 'all', + several: 'several', + oneUrl: 'oneUrl', + allUrl: 'allUrl', + customPUT: 'customPUT', + customPATCH: 'customPATCH', + customPOST: 'customPOST', + customDELETE: 'customDELETE', + customGET: 'customGET', + customGETLIST: 'customGETLIST', + customOperation: 'customOperation', + doPUT: 'doPUT', + doPATCH: 'doPATCH', + doPOST: 'doPOST', + doDELETE: 'doDELETE', + doGET: 'doGET', + doGETLIST: 'doGETLIST', + fromServer: 'fromServer', + withConfig: 'withConfig', + withHttpConfig: 'withHttpConfig', + singleOne: 'singleOne', + plain: 'plain', + save: 'save', + restangularized: 'restangularized' + }; + object.setRestangularFields = function(resFields) { + config.restangularFields = + _.extend(config.restangularFields, resFields); + return this; + }; - config.setFieldToElem = function(field, elem, value) { - var properties = field.split('.'); - var idValue = elem; - _.each(_.initial(properties), function(prop) { - idValue[prop] = {}; - idValue = idValue[prop]; - }); - idValue[_.last(properties)] = value; - return this; - }; + config.isRestangularized = function(obj) { + return !!obj[config.restangularFields.restangularized]; + }; - config.getFieldFromElem = function(field, elem) { - var properties = field.split('.'); - var idValue = elem; - _.each(properties, function(prop) { - if (idValue) { + config.setFieldToElem = function(field, elem, value) { + var properties = field.split('.'); + var idValue = elem; + _.each(_.initial(properties), function(prop) { + idValue[prop] = {}; idValue = idValue[prop]; - } - }); - return angular.copy(idValue); - }; - - config.setIdToElem = function(elem, id /*, route */) { - config.setFieldToElem(config.restangularFields.id, elem, id); - return this; - }; + }); + idValue[_.last(properties)] = value; + return this; + }; - config.getIdFromElem = function(elem) { - return config.getFieldFromElem(config.restangularFields.id, elem); - }; + config.getFieldFromElem = function(field, elem) { + var properties = field.split('.'); + var idValue = elem; + _.each(properties, function(prop) { + if (idValue) { + idValue = idValue[prop]; + } + }); + return angular.copy(idValue); + }; - config.isValidId = function(elemId) { - return '' !== elemId && !_.isUndefined(elemId) && !_.isNull(elemId); - }; + config.setIdToElem = function(elem, id /*, route */ ) { + config.setFieldToElem(config.restangularFields.id, elem, id); + return this; + }; - config.setUrlToElem = function(elem, url /*, route */) { - config.setFieldToElem(config.restangularFields.selfLink, elem, url); - return this; - }; + config.getIdFromElem = function(elem) { + return config.getFieldFromElem(config.restangularFields.id, elem); + }; - config.getUrlFromElem = function(elem) { - return config.getFieldFromElem(config.restangularFields.selfLink, elem); - }; + config.isValidId = function(elemId) { + return '' !== elemId && !_.isUndefined(elemId) && !_.isNull(elemId); + }; - config.useCannonicalId = _.isUndefined(config.useCannonicalId) ? false : config.useCannonicalId; - object.setUseCannonicalId = function(value) { - config.useCannonicalId = value; - return this; - }; + config.setUrlToElem = function(elem, url /*, route */ ) { + config.setFieldToElem(config.restangularFields.selfLink, elem, url); + return this; + }; - config.getCannonicalIdFromElem = function(elem) { - var cannonicalId = elem[config.restangularFields.cannonicalId]; - var actualId = config.isValidId(cannonicalId) ? cannonicalId : config.getIdFromElem(elem); - return actualId; - }; + config.getUrlFromElem = function(elem) { + return config.getFieldFromElem(config.restangularFields.selfLink, elem); + }; - /** - * Sets the Response parser. This is used in case your response isn't directly the data. - * For example if you have a response like {meta: {'meta'}, data: {name: 'Gonto'}} - * you can extract this data which is the one that needs wrapping - * - * The ResponseExtractor is a function that receives the response and the method executed. - */ + config.useCannonicalId = _.isUndefined(config.useCannonicalId) ? false : config.useCannonicalId; + object.setUseCannonicalId = function(value) { + config.useCannonicalId = value; + return this; + }; - config.responseInterceptors = config.responseInterceptors || []; + config.getCannonicalIdFromElem = function(elem) { + var cannonicalId = elem[config.restangularFields.cannonicalId]; + var actualId = config.isValidId(cannonicalId) ? cannonicalId : config.getIdFromElem(elem); + return actualId; + }; - config.defaultResponseInterceptor = function(data /*, operation, what, url, response, deferred */) { - return data; - }; + /** + * Sets the Response parser. This is used in case your response isn't directly the data. + * For example if you have a response like {meta: {'meta'}, data: {name: 'Gonto'}} + * you can extract this data which is the one that needs wrapping + * + * The ResponseExtractor is a function that receives the response and the method executed. + */ - config.responseExtractor = function(data, operation, what, url, response, deferred) { - var interceptors = angular.copy(config.responseInterceptors); - interceptors.push(config.defaultResponseInterceptor); - var theData = data; - _.each(interceptors, function(interceptor) { - theData = interceptor(theData, operation, - what, url, response, deferred); - }); - return theData; - }; + config.responseInterceptors = config.responseInterceptors || []; - object.addResponseInterceptor = function(extractor) { - config.responseInterceptors.push(extractor); - return this; - }; + config.defaultResponseInterceptor = function(data /*, operation, what, url, response, deferred */ ) { + return data; + }; - config.errorInterceptors = config.errorInterceptors || []; - object.addErrorInterceptor = function(interceptor) { - config.errorInterceptors.push(interceptor); - return this; - }; + config.responseExtractor = function(data, operation, what, url, response, deferred) { + var interceptors = angular.copy(config.responseInterceptors); + interceptors.push(config.defaultResponseInterceptor); + var theData = data; + _.each(interceptors, function(interceptor) { + theData = interceptor(theData, operation, + what, url, response, deferred); + }); + return theData; + }; - object.setResponseInterceptor = object.addResponseInterceptor; - object.setResponseExtractor = object.addResponseInterceptor; - object.setErrorInterceptor = object.addErrorInterceptor; + object.addResponseInterceptor = function(extractor) { + config.responseInterceptors.push(extractor); + return this; + }; - /** - * Response interceptor is called just before resolving promises. - */ + config.errorInterceptors = config.errorInterceptors || []; + object.addErrorInterceptor = function(interceptor) { + config.errorInterceptors.push(interceptor); + return this; + }; + object.setResponseInterceptor = object.addResponseInterceptor; + object.setResponseExtractor = object.addResponseInterceptor; + object.setErrorInterceptor = object.addErrorInterceptor; - /** - * Request interceptor is called before sending an object to the server. - */ - config.requestInterceptors = config.requestInterceptors || []; + /** + * Response interceptor is called just before resolving promises. + */ - config.defaultInterceptor = function(element, operation, path, url, headers, params, httpConfig) { - return { - element: element, - headers: headers, - params: params, - httpConfig: httpConfig - }; - }; - config.fullRequestInterceptor = function(element, operation, path, url, headers, params, httpConfig) { - var interceptors = angular.copy(config.requestInterceptors); - var defaultRequest = config.defaultInterceptor(element, operation, path, url, headers, params, httpConfig); - return _.reduce(interceptors, function(request, interceptor) { - return _.extend(request, interceptor(request.element, operation, - path, url, request.headers, request.params, request.httpConfig)); - }, defaultRequest); - }; + /** + * Request interceptor is called before sending an object to the server. + */ + config.requestInterceptors = config.requestInterceptors || []; - object.addRequestInterceptor = function(interceptor) { - config.requestInterceptors.push(function(elem, operation, path, url, headers, params, httpConfig) { + config.defaultInterceptor = function(element, operation, path, url, headers, params, httpConfig) { return { + element: element, headers: headers, params: params, - element: interceptor(elem, operation, path, url), httpConfig: httpConfig }; - }); - return this; - }; + }; - object.setRequestInterceptor = object.addRequestInterceptor; + config.fullRequestInterceptor = function(element, operation, path, url, headers, params, httpConfig) { + var interceptors = angular.copy(config.requestInterceptors); + var defaultRequest = config.defaultInterceptor(element, operation, path, url, headers, params, httpConfig); + return _.reduce(interceptors, function(request, interceptor) { + return _.extend(request, interceptor(request.element, operation, + path, url, request.headers, request.params, request.httpConfig)); + }, defaultRequest); + }; - object.addFullRequestInterceptor = function(interceptor) { - config.requestInterceptors.push(interceptor); - return this; - }; + object.addRequestInterceptor = function(interceptor) { + config.requestInterceptors.push(function(elem, operation, path, url, headers, params, httpConfig) { + return { + headers: headers, + params: params, + element: interceptor(elem, operation, path, url), + httpConfig: httpConfig + }; + }); + return this; + }; - object.setFullRequestInterceptor = object.addFullRequestInterceptor; + object.setRequestInterceptor = object.addRequestInterceptor; - config.onBeforeElemRestangularized = config.onBeforeElemRestangularized || function(elem) { - return elem; - }; - object.setOnBeforeElemRestangularized = function(post) { - config.onBeforeElemRestangularized = post; - return this; - }; - - object.setRestangularizePromiseInterceptor = function(interceptor) { - config.restangularizePromiseInterceptor = interceptor; - return this; - }; + object.addFullRequestInterceptor = function(interceptor) { + config.requestInterceptors.push(interceptor); + return this; + }; - /** - * This method is called after an element has been "Restangularized". - * - * It receives the element, a boolean indicating if it's an element or a collection - * and the name of the model - * - */ - config.onElemRestangularized = config.onElemRestangularized || function(elem) { - return elem; - }; - object.setOnElemRestangularized = function(post) { - config.onElemRestangularized = post; - return this; - }; + object.setFullRequestInterceptor = object.addFullRequestInterceptor; - config.shouldSaveParent = config.shouldSaveParent || function() { - return true; - }; - object.setParentless = function(values) { - if (_.isArray(values)) { - config.shouldSaveParent = function(route) { - return !_.includes(values, route); - }; - } else if (_.isBoolean(values)) { - config.shouldSaveParent = function() { - return !values; - }; - } - return this; - }; + config.onBeforeElemRestangularized = config.onBeforeElemRestangularized || function(elem) { + return elem; + }; + object.setOnBeforeElemRestangularized = function(post) { + config.onBeforeElemRestangularized = post; + return this; + }; - /** - * This lets you set a suffix to every request. - * - * For example, if your api requires that for JSon requests you do /users/123.json, you can set that - * in here. - * - * - * By default, the suffix is null - */ - config.suffix = _.isUndefined(config.suffix) ? null : config.suffix; - object.setRequestSuffix = function(newSuffix) { - config.suffix = newSuffix; - return this; - }; + object.setRestangularizePromiseInterceptor = function(interceptor) { + config.restangularizePromiseInterceptor = interceptor; + return this; + }; - /** - * Add element transformers for certain routes. - */ - config.transformers = config.transformers || {}; - config.matchTransformers = config.matchTransformers || []; - object.addElementTransformer = function(type, secondArg, thirdArg) { - var isCollection = null; - var transformer = null; - if (arguments.length === 2) { - transformer = secondArg; - } else { - transformer = thirdArg; - isCollection = secondArg; - } + /** + * This method is called after an element has been "Restangularized". + * + * It receives the element, a boolean indicating if it's an element or a collection + * and the name of the model + * + */ + config.onElemRestangularized = config.onElemRestangularized || function(elem) { + return elem; + }; + object.setOnElemRestangularized = function(post) { + config.onElemRestangularized = post; + return this; + }; - var transformerFn = function(coll, elem) { - if (_.isNull(isCollection) || (coll === isCollection)) { - return transformer(elem); + config.shouldSaveParent = config.shouldSaveParent || function() { + return true; + }; + object.setParentless = function(values) { + if (_.isArray(values)) { + config.shouldSaveParent = function(route) { + return !_.includes(values, route); + }; + } else if (_.isBoolean(values)) { + config.shouldSaveParent = function() { + return !values; + }; } - return elem; + return this; }; - if (_.isRegExp(type)) { - config.matchTransformers.push({ - regexp: type, - transformer: transformerFn - }); - } else { - if (!config.transformers[type]) { - config.transformers[type] = []; + /** + * This lets you set a suffix to every request. + * + * For example, if your api requires that for JSon requests you do /users/123.json, you can set that + * in here. + * + * + * By default, the suffix is null + */ + config.suffix = _.isUndefined(config.suffix) ? null : config.suffix; + object.setRequestSuffix = function(newSuffix) { + config.suffix = newSuffix; + return this; + }; + + /** + * Add element transformers for certain routes. + */ + config.transformers = config.transformers || {}; + config.matchTransformers = config.matchTransformers || []; + object.addElementTransformer = function(type, secondArg, thirdArg) { + var isCollection = null; + var transformer = null; + if (arguments.length === 2) { + transformer = secondArg; + } else { + transformer = thirdArg; + isCollection = secondArg; } - config.transformers[type].push(transformerFn); - } - return object; - }; + var transformerFn = function(coll, elem) { + if (_.isNull(isCollection) || (coll === isCollection)) { + return transformer(elem); + } + return elem; + }; - object.extendCollection = function(route, fn) { - return object.addElementTransformer(route, true, fn); - }; + if (_.isRegExp(type)) { + config.matchTransformers.push({ + regexp: type, + transformer: transformerFn + }); + } else { + if (!config.transformers[type]) { + config.transformers[type] = []; + } + config.transformers[type].push(transformerFn); + } - object.extendModel = function(route, fn) { - return object.addElementTransformer(route, false, fn); - }; + return object; + }; - config.transformElem = function(elem, isCollection, route, Restangular, force) { - if (!force && !config.transformLocalElements && !elem[config.restangularFields.fromServer]) { - return elem; - } + object.extendCollection = function(route, fn) { + return object.addElementTransformer(route, true, fn); + }; - var changedElem = elem; + object.extendModel = function(route, fn) { + return object.addElementTransformer(route, false, fn); + }; - var matchTransformers = config.matchTransformers; - if (matchTransformers) { - _.each(matchTransformers, function (transformer) { - if (route.match(transformer.regexp)) { - changedElem = transformer.transformer(isCollection, changedElem); - } - }); - } + config.transformElem = function(elem, isCollection, route, Restangular, force) { + if (!force && !config.transformLocalElements && !elem[config.restangularFields.fromServer]) { + return elem; + } - var typeTransformers = config.transformers[route]; - if (typeTransformers) { - _.each(typeTransformers, function(transformer) { - changedElem = transformer(isCollection, changedElem); - }); - } - return config.onElemRestangularized(changedElem, isCollection, route, Restangular); - }; + var changedElem = elem; - config.transformLocalElements = _.isUndefined(config.transformLocalElements) ? - false : - config.transformLocalElements; + var matchTransformers = config.matchTransformers; + if (matchTransformers) { + _.each(matchTransformers, function(transformer) { + if (transformer.regexp.test(route)) { + changedElem = transformer.transformer(isCollection, changedElem); + } + }); + } - object.setTransformOnlyServerElements = function(active) { - config.transformLocalElements = !active; - }; + var typeTransformers = config.transformers[route]; + if (typeTransformers) { + _.each(typeTransformers, function(transformer) { + changedElem = transformer(isCollection, changedElem); + }); + } + return config.onElemRestangularized(changedElem, isCollection, route, Restangular); + }; - config.fullResponse = _.isUndefined(config.fullResponse) ? false : config.fullResponse; - object.setFullResponse = function(full) { - config.fullResponse = full; - return this; - }; + config.transformLocalElements = _.isUndefined(config.transformLocalElements) ? + false : + config.transformLocalElements; + object.setTransformOnlyServerElements = function(active) { + config.transformLocalElements = !active; + }; - //Internal values and functions - config.urlCreatorFactory = {}; + config.fullResponse = _.isUndefined(config.fullResponse) ? false : config.fullResponse; + object.setFullResponse = function(full) { + config.fullResponse = full; + return this; + }; - /** - * Base URL Creator. Base prototype for everything related to it - **/ - var BaseCreator = function() { - }; + //Internal values and functions + config.urlCreatorFactory = {}; - BaseCreator.prototype.setConfig = function(config) { - this.config = config; - return this; - }; + /** + * Base URL Creator. Base prototype for everything related to it + **/ - BaseCreator.prototype.parentsArray = function(current) { - var parents = []; - while(current) { - parents.push(current); - current = current[this.config.restangularFields.parentResource]; - } - return parents.reverse(); - }; + var BaseCreator = function() {}; - function RestangularResource(config, $http, url, configurer) { - var resource = {}; - _.each(_.keys(configurer), function(key) { - var value = configurer[key]; + BaseCreator.prototype.setConfig = function(config) { + this.config = config; + return this; + }; - // Add default parameters - value.params = _.extend({}, value.params, config.defaultRequestParams[value.method.toLowerCase()]); - // We don't want the ? if no params are there - if (_.isEmpty(value.params)) { - delete value.params; + BaseCreator.prototype.parentsArray = function(current) { + var parents = []; + while (current) { + parents.push(current); + current = current[this.config.restangularFields.parentResource]; } + return parents.reverse(); + }; - if (config.isSafe(value.method)) { + function RestangularResource(config, $http, url, configurer) { + var resource = {}; + _.each(_.keys(configurer), function(key) { + var value = configurer[key]; - resource[key] = function() { - return $http(_.extend(value, { - url: url - })); - }; + // Add default parameters + value.params = _.extend({}, value.params, config.defaultRequestParams[value.method.toLowerCase()]); + // We don't want the ? if no params are there + if (_.isEmpty(value.params)) { + delete value.params; + } - } else { + if (config.isSafe(value.method)) { - resource[key] = function(data) { - return $http(_.extend(value, { - url: url, - data: data - })); - }; + resource[key] = function() { + return $http(_.extend(value, { + url: url + })); + }; - } - }); + } else { - return resource; - } + resource[key] = function(data) { + return $http(_.extend(value, { + url: url, + data: data + })); + }; - BaseCreator.prototype.resource = function(current, $http, localHttpConfig, callHeaders, callParams, what, etag,operation) { + } + }); - var params = _.defaults(callParams || {}, this.config.defaultRequestParams.common); - var headers = _.defaults(callHeaders || {}, this.config.defaultHeaders); + return resource; + } - if (etag) { - if (!config.isSafe(operation)) { - headers['If-Match'] = etag; - } else { - headers['If-None-Match'] = etag; + BaseCreator.prototype.resource = function(current, $http, localHttpConfig, callHeaders, callParams, what, etag, operation) { + + var params = _.defaults(callParams || {}, this.config.defaultRequestParams.common); + var headers = _.defaults(callHeaders || {}, this.config.defaultHeaders); + + if (etag) { + if (!config.isSafe(operation)) { + headers['If-Match'] = etag; + } else { + headers['If-None-Match'] = etag; + } } - } - var url = this.base(current); + var url = this.base(current); - if (what || what === 0) { - var add = ''; - if (!/\/$/.test(url)) { - add += '/'; + if (what || what === 0) { + var add = ''; + if (!/\/$/.test(url)) { + add += '/'; + } + add += what; + url += add; } - add += what; - url += add; - } - if (this.config.suffix && - url.indexOf(this.config.suffix, url.length - this.config.suffix.length) === -1 && - !this.config.getUrlFromElem(current)) { + if (this.config.suffix && + url.indexOf(this.config.suffix, url.length - this.config.suffix.length) === -1 && + !this.config.getUrlFromElem(current)) { url += this.config.suffix; - } - - current[this.config.restangularFields.httpConfig] = undefined; + } - return RestangularResource(this.config, $http, url, { - getList: this.config.withHttpValues(localHttpConfig, - {method: 'GET', - params: params, - headers: headers}), + current[this.config.restangularFields.httpConfig] = undefined; - get: this.config.withHttpValues(localHttpConfig, - {method: 'GET', - params: params, - headers: headers}), + return RestangularResource(this.config, $http, url, { + getList: this.config.withHttpValues(localHttpConfig, { + method: 'GET', + params: params, + headers: headers + }), - jsonp: this.config.withHttpValues(localHttpConfig, - {method: 'jsonp', - params: params, - headers: headers}), + get: this.config.withHttpValues(localHttpConfig, { + method: 'GET', + params: params, + headers: headers + }), - put: this.config.withHttpValues(localHttpConfig, - {method: 'PUT', - params: params, - headers: headers}), + jsonp: this.config.withHttpValues(localHttpConfig, { + method: 'jsonp', + params: params, + headers: headers + }), - post: this.config.withHttpValues(localHttpConfig, - {method: 'POST', - params: params, - headers: headers}), + put: this.config.withHttpValues(localHttpConfig, { + method: 'PUT', + params: params, + headers: headers + }), - remove: this.config.withHttpValues(localHttpConfig, - {method: 'DELETE', - params: params, - headers: headers}), + post: this.config.withHttpValues(localHttpConfig, { + method: 'POST', + params: params, + headers: headers + }), - head: this.config.withHttpValues(localHttpConfig, - {method: 'HEAD', - params: params, - headers: headers}), + remove: this.config.withHttpValues(localHttpConfig, { + method: 'DELETE', + params: params, + headers: headers + }), - trace: this.config.withHttpValues(localHttpConfig, - {method: 'TRACE', - params: params, - headers: headers}), + head: this.config.withHttpValues(localHttpConfig, { + method: 'HEAD', + params: params, + headers: headers + }), - options: this.config.withHttpValues(localHttpConfig, - {method: 'OPTIONS', - params: params, - headers: headers}), + trace: this.config.withHttpValues(localHttpConfig, { + method: 'TRACE', + params: params, + headers: headers + }), - patch: this.config.withHttpValues(localHttpConfig, - {method: 'PATCH', - params: params, - headers: headers}) - }); - }; + options: this.config.withHttpValues(localHttpConfig, { + method: 'OPTIONS', + params: params, + headers: headers + }), - /** - * This is the Path URL creator. It uses Path to show Hierarchy in the Rest API. - * This means that if you have an Account that then has a set of Buildings, a URL to a building - * would be /accounts/123/buildings/456 - **/ - var Path = function() { - }; + patch: this.config.withHttpValues(localHttpConfig, { + method: 'PATCH', + params: params, + headers: headers + }) + }); + }; - Path.prototype = new BaseCreator(); + /** + * This is the Path URL creator. It uses Path to show Hierarchy in the Rest API. + * This means that if you have an Account that then has a set of Buildings, a URL to a building + * would be /accounts/123/buildings/456 + **/ + var Path = function() {}; - Path.prototype.normalizeUrl = function (url){ - var parts = /((?:http[s]?:)?\/\/)?(.*)?/.exec(url); - parts[2] = parts[2].replace(/[\\\/]+/g, '/'); - return (typeof parts[1] !== 'undefined')? parts[1] + parts[2] : parts[2]; - }; + Path.prototype = new BaseCreator(); - Path.prototype.base = function(current) { - var __this = this; - return _.reduce(this.parentsArray(current), function(acum, elem) { - var elemUrl; - var elemSelfLink = __this.config.getUrlFromElem(elem); - if (elemSelfLink) { - if (__this.config.isAbsoluteUrl(elemSelfLink)) { - return elemSelfLink; - } else { - elemUrl = elemSelfLink; - } - } else { - elemUrl = elem[__this.config.restangularFields.route]; + Path.prototype.normalizeUrl = function(url) { + var parts = /((?:http[s]?:)?\/\/)?(.*)?/.exec(url); + parts[2] = parts[2].replace(/[\\\/]+/g, '/'); + return (typeof parts[1] !== 'undefined') ? parts[1] + parts[2] : parts[2]; + }; - if (elem[__this.config.restangularFields.restangularCollection]) { - var ids = elem[__this.config.restangularFields.ids]; - if (ids) { - elemUrl += '/' + ids.join(','); - } - } else { - var elemId; - if (__this.config.useCannonicalId) { - elemId = __this.config.getCannonicalIdFromElem(elem); + Path.prototype.base = function(current) { + var __this = this; + return _.reduce(this.parentsArray(current), function(acum, elem) { + var elemUrl; + var elemSelfLink = __this.config.getUrlFromElem(elem); + if (elemSelfLink) { + if (__this.config.isAbsoluteUrl(elemSelfLink)) { + return elemSelfLink; } else { - elemId = __this.config.getIdFromElem(elem); + elemUrl = elemSelfLink; } + } else { + elemUrl = elem[__this.config.restangularFields.route]; - if (config.isValidId(elemId) && !elem.singleOne) { - elemUrl += '/' + (__this.config.encodeIds ? encodeURIComponent(elemId) : elemId); + if (elem[__this.config.restangularFields.restangularCollection]) { + var ids = elem[__this.config.restangularFields.ids]; + if (ids) { + elemUrl += '/' + ids.join(','); + } + } else { + var elemId; + if (__this.config.useCannonicalId) { + elemId = __this.config.getCannonicalIdFromElem(elem); + } else { + elemId = __this.config.getIdFromElem(elem); + } + + if (config.isValidId(elemId) && !elem.singleOne) { + elemUrl += '/' + (__this.config.encodeIds ? encodeURIComponent(elemId) : elemId); + } } } - } - acum = acum.replace(/\/$/, '') + '/' + elemUrl; - return __this.normalizeUrl(acum); + acum = acum.replace(/\/$/, '') + '/' + elemUrl; + return __this.normalizeUrl(acum); - }, this.config.baseUrl); - }; + }, this.config.baseUrl); + }; - Path.prototype.fetchUrl = function(current, what) { - var baseUrl = this.base(current); - if (what) { - baseUrl += '/' + what; - } - return baseUrl; - }; + Path.prototype.fetchUrl = function(current, what) { + var baseUrl = this.base(current); + if (what) { + baseUrl += '/' + what; + } + return baseUrl; + }; - Path.prototype.fetchRequestedUrl = function(current, what) { - var url = this.fetchUrl(current, what); - var params = current[config.restangularFields.reqParams]; - - // From here on and until the end of fetchRequestedUrl, - // the code has been kindly borrowed from angular.js - // The reason for such code bloating is coherence: - // If the user were to use this for cache management, the - // serialization of parameters would need to be identical - // to the one done by angular for cache keys to match. - function sortedKeys(obj) { - var keys = []; - for (var key in obj) { - if (obj.hasOwnProperty(key)) { - keys.push(key); + Path.prototype.fetchRequestedUrl = function(current, what) { + var url = this.fetchUrl(current, what); + var params = current[config.restangularFields.reqParams]; + + // From here on and until the end of fetchRequestedUrl, + // the code has been kindly borrowed from angular.js + // The reason for such code bloating is coherence: + // If the user were to use this for cache management, the + // serialization of parameters would need to be identical + // to the one done by angular for cache keys to match. + function sortedKeys(obj) { + var keys = []; + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + keys.push(key); + } } + return keys.sort(); } - return keys.sort(); - } - function forEachSorted(obj, iterator, context) { - var keys = sortedKeys(obj); - for ( var i = 0; i < keys.length; i++) { - iterator.call(context, obj[keys[i]], keys[i]); + function forEachSorted(obj, iterator, context) { + var keys = sortedKeys(obj); + for (var i = 0; i < keys.length; i++) { + iterator.call(context, obj[keys[i]], keys[i]); + } + return keys; } - return keys; - } - - function encodeUriQuery(val, pctEncodeSpaces) { - return encodeURIComponent(val). - replace(/%40/gi, '@'). - replace(/%3A/gi, ':'). - replace(/%24/g, '$'). - replace(/%2C/gi, ','). - replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); - } - if (!params) { return url + (this.config.suffix || ''); } + function encodeUriQuery(val, pctEncodeSpaces) { + return encodeURIComponent(val). + replace(/%40/gi, '@'). + replace(/%3A/gi, ':'). + replace(/%24/g, '$'). + replace(/%2C/gi, ','). + replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); + } - var parts = []; - forEachSorted(params, function(value, key) { - if (value === null || value === undefined) { return; } - if (!angular.isArray(value)) { value = [value]; } + if (!params) { + return url + (this.config.suffix || ''); + } - angular.forEach(value, function(v) { - if (angular.isObject(v)) { - v = angular.toJson(v); + var parts = []; + forEachSorted(params, function(value, key) { + if (value === null || value === undefined) { + return; + } + if (!angular.isArray(value)) { + value = [value]; } - parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(v)); + + angular.forEach(value, function(v) { + if (angular.isObject(v)) { + v = angular.toJson(v); + } + parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(v)); + }); }); - }); - return url + (this.config.suffix || '') + ((url.indexOf('?') === -1) ? '?' : '&') + parts.join('&'); + return url + (this.config.suffix || '') + ((url.indexOf('?') === -1) ? '?' : '&') + parts.join('&'); + }; + + config.urlCreatorFactory.path = Path; }; - config.urlCreatorFactory.path = Path; - }; + var globalConfiguration = {}; - var globalConfiguration = {}; + Configurer.init(this, globalConfiguration); - Configurer.init(this, globalConfiguration); + this.$get = ['$http', '$q', function($http, $q) { - this.$get = ['$http', '$q', function($http, $q) { + function createServiceForConfiguration(config) { + var service = {}; - function createServiceForConfiguration(config) { - var service = {}; + var urlHandler = new config.urlCreatorFactory[config.urlCreator](); + urlHandler.setConfig(config); - var urlHandler = new config.urlCreatorFactory[config.urlCreator](); - urlHandler.setConfig(config); + function restangularizeBase(parent, elem, route, reqParams, fromServer) { + elem[config.restangularFields.route] = route; + elem[config.restangularFields.getRestangularUrl] = _.bind(urlHandler.fetchUrl, urlHandler, elem); + elem[config.restangularFields.getRequestedUrl] = _.bind(urlHandler.fetchRequestedUrl, urlHandler, elem); + elem[config.restangularFields.addRestangularMethod] = _.bind(addRestangularMethodFunction, elem); + elem[config.restangularFields.clone] = _.bind(copyRestangularizedElement, elem, elem); + elem[config.restangularFields.reqParams] = _.isEmpty(reqParams) ? null : reqParams; + elem[config.restangularFields.withHttpConfig] = _.bind(withHttpConfig, elem); + elem[config.restangularFields.plain] = _.bind(stripRestangular, elem, elem); - function restangularizeBase(parent, elem, route, reqParams, fromServer) { - elem[config.restangularFields.route] = route; - elem[config.restangularFields.getRestangularUrl] = _.bind(urlHandler.fetchUrl, urlHandler, elem); - elem[config.restangularFields.getRequestedUrl] = _.bind(urlHandler.fetchRequestedUrl, urlHandler, elem); - elem[config.restangularFields.addRestangularMethod] = _.bind(addRestangularMethodFunction, elem); - elem[config.restangularFields.clone] = _.bind(copyRestangularizedElement, elem, elem); - elem[config.restangularFields.reqParams] = _.isEmpty(reqParams) ? null : reqParams; - elem[config.restangularFields.withHttpConfig] = _.bind(withHttpConfig, elem); - elem[config.restangularFields.plain] = _.bind(stripRestangular, elem, elem); + // Tag element as restangularized + elem[config.restangularFields.restangularized] = true; - // Tag element as restangularized - elem[config.restangularFields.restangularized] = true; + // RequestLess connection + elem[config.restangularFields.one] = _.bind(one, elem, elem); + elem[config.restangularFields.all] = _.bind(all, elem, elem); + elem[config.restangularFields.several] = _.bind(several, elem, elem); + elem[config.restangularFields.oneUrl] = _.bind(oneUrl, elem, elem); + elem[config.restangularFields.allUrl] = _.bind(allUrl, elem, elem); - // RequestLess connection - elem[config.restangularFields.one] = _.bind(one, elem, elem); - elem[config.restangularFields.all] = _.bind(all, elem, elem); - elem[config.restangularFields.several] = _.bind(several, elem, elem); - elem[config.restangularFields.oneUrl] = _.bind(oneUrl, elem, elem); - elem[config.restangularFields.allUrl] = _.bind(allUrl, elem, elem); + elem[config.restangularFields.fromServer] = !!fromServer; - elem[config.restangularFields.fromServer] = !!fromServer; + if (parent && config.shouldSaveParent(route)) { + var parentId = config.getIdFromElem(parent); + var parentUrl = config.getUrlFromElem(parent); - if (parent && config.shouldSaveParent(route)) { - var parentId = config.getIdFromElem(parent); - var parentUrl = config.getUrlFromElem(parent); + var restangularFieldsForParent = _.union( + _.values(_.pick(config.restangularFields, ['route', 'singleOne', 'parentResource'])), + config.extraFields + ); + var parentResource = _.pick(parent, restangularFieldsForParent); - var restangularFieldsForParent = _.union( - _.values( _.pick(config.restangularFields, ['route', 'singleOne', 'parentResource']) ), - config.extraFields - ); - var parentResource = _.pick(parent, restangularFieldsForParent); + if (config.isValidId(parentId)) { + config.setIdToElem(parentResource, parentId, route); + } + if (config.isValidId(parentUrl)) { + config.setUrlToElem(parentResource, parentUrl, route); + } - if (config.isValidId(parentId)) { - config.setIdToElem(parentResource, parentId, route); - } - if (config.isValidId(parentUrl)) { - config.setUrlToElem(parentResource, parentUrl, route); + elem[config.restangularFields.parentResource] = parentResource; + } else { + elem[config.restangularFields.parentResource] = null; } - - elem[config.restangularFields.parentResource] = parentResource; - } else { - elem[config.restangularFields.parentResource] = null; + return elem; } - return elem; - } - function one(parent, route, id, singleOne) { - var error; - if (_.isNumber(route) || _.isNumber(parent)) { - error = 'You\'re creating a Restangular entity with the number '; - error += 'instead of the route or the parent. For example, you can\'t call .one(12).'; - throw new Error(error); - } - if (_.isUndefined(route)) { - error = 'You\'re creating a Restangular entity either without the path. '; - error += 'For example you can\'t call .one(). Please check if your arguments are valid.'; - throw new Error(error); + function one(parent, route, id, singleOne) { + var error; + if (_.isNumber(route) || _.isNumber(parent)) { + error = 'You\'re creating a Restangular entity with the number '; + error += 'instead of the route or the parent. For example, you can\'t call .one(12).'; + throw new Error(error); + } + if (_.isUndefined(route)) { + error = 'You\'re creating a Restangular entity either without the path. '; + error += 'For example you can\'t call .one(). Please check if your arguments are valid.'; + throw new Error(error); + } + var elem = {}; + config.setIdToElem(elem, id, route); + config.setFieldToElem(config.restangularFields.singleOne, elem, singleOne); + return restangularizeElem(parent, elem, route, false); } - var elem = {}; - config.setIdToElem(elem, id, route); - config.setFieldToElem(config.restangularFields.singleOne, elem, singleOne); - return restangularizeElem(parent, elem , route, false); - } - function all(parent, route) { - return restangularizeCollection(parent, [] , route, false); - } + function all(parent, route) { + return restangularizeCollection(parent, [], route, false); + } - function several(parent, route /*, ids */) { - var collection = []; - collection[config.restangularFields.ids] = Array.prototype.splice.call(arguments, 2); - return restangularizeCollection(parent, collection , route, false); - } + function several(parent, route /*, ids */ ) { + var collection = []; + collection[config.restangularFields.ids] = Array.prototype.splice.call(arguments, 2); + return restangularizeCollection(parent, collection, route, false); + } - function oneUrl(parent, route, url) { - if (!route) { - throw new Error('Route is mandatory when creating new Restangular objects.'); + function oneUrl(parent, route, url) { + if (!route) { + throw new Error('Route is mandatory when creating new Restangular objects.'); + } + var elem = {}; + config.setUrlToElem(elem, url, route); + return restangularizeElem(parent, elem, route, false); } - var elem = {}; - config.setUrlToElem(elem, url, route); - return restangularizeElem(parent, elem , route, false); - } - function allUrl(parent, route, url) { - if (!route) { - throw new Error('Route is mandatory when creating new Restangular objects.'); + function allUrl(parent, route, url) { + if (!route) { + throw new Error('Route is mandatory when creating new Restangular objects.'); + } + var elem = {}; + config.setUrlToElem(elem, url, route); + return restangularizeCollection(parent, elem, route, false); } - var elem = {}; - config.setUrlToElem(elem, url, route); - return restangularizeCollection(parent, elem , route, false); - } - // Promises - function restangularizePromise(promise, isCollection, valueToFill) { - promise.call = _.bind(promiseCall, promise); - promise.get = _.bind(promiseGet, promise); - promise[config.restangularFields.restangularCollection] = isCollection; - if (isCollection) { + // Promises + function restangularizePromise(promise, isCollection, valueToFill) { + promise.call = _.bind(promiseCall, promise); + promise.get = _.bind(promiseGet, promise); + promise[config.restangularFields.restangularCollection] = isCollection; + if (isCollection) { promise.push = _.bind(promiseCall, promise, 'push'); + } + promise.$object = valueToFill; + if (config.restangularizePromiseInterceptor) { + config.restangularizePromiseInterceptor(promise); + } + return promise; } - promise.$object = valueToFill; - if (config.restangularizePromiseInterceptor) { - config.restangularizePromiseInterceptor(promise); - } - return promise; - } - - function promiseCall(method) { - var deferred = $q.defer(); - var callArgs = arguments; - var filledValue = {}; - this.then(function(val) { - var params = Array.prototype.slice.call(callArgs, 1); - var func = val[method]; - func.apply(val, params); - filledValue = val; - deferred.resolve(val); - }); - return restangularizePromise(deferred.promise, this[config.restangularFields.restangularCollection], filledValue); - } - - function promiseGet(what) { - var deferred = $q.defer(); - var filledValue = {}; - this.then(function(val) { - filledValue = val[what]; - deferred.resolve(filledValue); - }); - return restangularizePromise(deferred.promise, this[config.restangularFields.restangularCollection], filledValue); - } - - function resolvePromise(deferred, response, data, filledValue) { - _.extend(filledValue, data); - - // Trigger the full response interceptor. - if (config.fullResponse) { - return deferred.resolve(_.extend(response, { - data: data - })); - } else { - deferred.resolve(data); - } - } - - // Elements - function stripRestangular(elem) { - if (_.isArray(elem)) { - var array = []; - _.each(elem, function(value) { - array.push(config.isRestangularized(value) ? stripRestangular(value) : value); + function promiseCall(method) { + var deferred = $q.defer(); + var callArgs = arguments; + var filledValue = {}; + this.then(function(val) { + var params = Array.prototype.slice.call(callArgs, 1); + var func = val[method]; + func.apply(val, params); + filledValue = val; + deferred.resolve(val); }); - return array; - } else { - return _.omit(elem, _.values(_.omit(config.restangularFields, 'id'))); + return restangularizePromise(deferred.promise, this[config.restangularFields.restangularCollection], filledValue); } - } - function addCustomOperation(elem) { - elem[config.restangularFields.customOperation] = _.bind(customFunction, elem); - var requestMethods = { get: customFunction, delete: customFunction }; - _.each(['put', 'patch', 'post'], function(name) { - requestMethods[name] = function(operation, elem, path, params, headers) { - return _.bind(customFunction, this)(operation, path, params, headers, elem); - }; - }); - _.each(requestMethods, function(requestFunc, name) { - var callOperation = name === 'delete' ? 'remove' : name; - _.each(['do', 'custom'], function(alias) { - elem[alias + name.toUpperCase()] = _.bind(requestFunc, elem, callOperation); + function promiseGet(what) { + var deferred = $q.defer(); + var filledValue = {}; + this.then(function(val) { + filledValue = val[what]; + deferred.resolve(filledValue); }); - }); - elem[config.restangularFields.customGETLIST] = _.bind(fetchFunction, elem); - elem[config.restangularFields.doGETLIST] = elem[config.restangularFields.customGETLIST]; - } + return restangularizePromise(deferred.promise, this[config.restangularFields.restangularCollection], filledValue); + } - function copyRestangularizedElement(fromElement, toElement) { - var copiedElement = angular.copy(fromElement, toElement); - return restangularizeElem(copiedElement[config.restangularFields.parentResource], - copiedElement, copiedElement[config.restangularFields.route], copiedElement[config.restangularFields.fromServer]); - } + function resolvePromise(deferred, response, data, filledValue) { + _.extend(filledValue, data); - function restangularizeElem(parent, element, route, fromServer, collection, reqParams) { - var elem = config.onBeforeElemRestangularized(element, false, route); + // Trigger the full response interceptor. + if (config.fullResponse) { + return deferred.resolve(_.extend(response, { + data: data + })); + } else { + deferred.resolve(data); + } + } - var localElem = restangularizeBase(parent, elem, route, reqParams, fromServer); - if (config.useCannonicalId) { - localElem[config.restangularFields.cannonicalId] = config.getIdFromElem(localElem); + // Elements + function stripRestangular(elem) { + if (_.isArray(elem)) { + var array = []; + _.each(elem, function(value) { + array.push(config.isRestangularized(value) ? stripRestangular(value) : value); + }); + return array; + } else { + return _.omit(elem, _.values(_.omit(config.restangularFields, 'id'))); + } } - if (collection) { - localElem[config.restangularFields.getParentList] = function() { - return collection; + function addCustomOperation(elem) { + elem[config.restangularFields.customOperation] = _.bind(customFunction, elem); + var requestMethods = { + get: customFunction, + delete: customFunction }; + _.each(['put', 'patch', 'post'], function(name) { + requestMethods[name] = function(operation, elem, path, params, headers) { + return _.bind(customFunction, this)(operation, path, params, headers, elem); + }; + }); + _.each(requestMethods, function(requestFunc, name) { + var callOperation = name === 'delete' ? 'remove' : name; + _.each(['do', 'custom'], function(alias) { + elem[alias + name.toUpperCase()] = _.bind(requestFunc, elem, callOperation); + }); + }); + elem[config.restangularFields.customGETLIST] = _.bind(fetchFunction, elem); + elem[config.restangularFields.doGETLIST] = elem[config.restangularFields.customGETLIST]; } - localElem[config.restangularFields.restangularCollection] = false; - localElem[config.restangularFields.get] = _.bind(getFunction, localElem); - localElem[config.restangularFields.getList] = _.bind(fetchFunction, localElem); - localElem[config.restangularFields.put] = _.bind(putFunction, localElem); - localElem[config.restangularFields.post] = _.bind(postFunction, localElem); - localElem[config.restangularFields.remove] = _.bind(deleteFunction, localElem); - localElem[config.restangularFields.head] = _.bind(headFunction, localElem); - localElem[config.restangularFields.trace] = _.bind(traceFunction, localElem); - localElem[config.restangularFields.options] = _.bind(optionsFunction, localElem); - localElem[config.restangularFields.patch] = _.bind(patchFunction, localElem); - localElem[config.restangularFields.save] = _.bind(save, localElem); - - addCustomOperation(localElem); - return config.transformElem(localElem, false, route, service, true); - } - - function restangularizeCollection(parent, element, route, fromServer, reqParams) { - var elem = config.onBeforeElemRestangularized(element, true, route); - - var localElem = restangularizeBase(parent, elem, route, reqParams, fromServer); - localElem[config.restangularFields.restangularCollection] = true; - localElem[config.restangularFields.post] = _.bind(postFunction, localElem, null); - localElem[config.restangularFields.remove] = _.bind(deleteFunction, localElem); - localElem[config.restangularFields.head] = _.bind(headFunction, localElem); - localElem[config.restangularFields.trace] = _.bind(traceFunction, localElem); - localElem[config.restangularFields.putElement] = _.bind(putElementFunction, localElem); - localElem[config.restangularFields.options] = _.bind(optionsFunction, localElem); - localElem[config.restangularFields.patch] = _.bind(patchFunction, localElem); - localElem[config.restangularFields.get] = _.bind(getById, localElem); - localElem[config.restangularFields.getList] = _.bind(fetchFunction, localElem, null); - - addCustomOperation(localElem); - return config.transformElem(localElem, true, route, service, true); - } - - function restangularizeCollectionAndElements(parent, element, route, fromServer) { - var collection = restangularizeCollection(parent, element, route, fromServer); - _.each(collection, function(elem) { - if (elem) { - restangularizeElem(parent, elem, route, fromServer); + function copyRestangularizedElement(element) { + var copiedElement = angular.copy(element); + + // check if we're dealing with a collection (i.e. an array) + // and restangularize the element using the proper restangularizer, + // element / collection + if (_.isArray(element)) { + return restangularizeCollection( + element[config.restangularFields.parentResource], + copiedElement, + element[config.restangularFields.route], + element[config.restangularFields.fromServer], + element[config.restangularFields.reqParams] + ); } - }); - return collection; - } - - function getById(id, reqParams, headers){ - return this.customGET(id.toString(), reqParams, headers); - } - function putElementFunction(idx, params, headers) { - var __this = this; - var elemToPut = this[idx]; - var deferred = $q.defer(); - var filledArray = []; - filledArray = config.transformElem(filledArray, true, elemToPut[config.restangularFields.route], service); - elemToPut.put(params, headers).then(function(serverElem) { - var newArray = copyRestangularizedElement(__this); - newArray[idx] = serverElem; - filledArray = newArray; - deferred.resolve(newArray); - }, function(response) { - deferred.reject(response); - }); - - return restangularizePromise(deferred.promise, true, filledArray); - } - - function parseResponse(resData, operation, route, fetchUrl, response, deferred) { - var data = config.responseExtractor(resData, operation, route, fetchUrl, response, deferred); - var etag = response.headers('ETag'); - if (data && etag) { - data[config.restangularFields.etag] = etag; + // not a collection, restangularize it as an element + return restangularizeElem( + element[config.restangularFields.parentResource], + copiedElement, + element[config.restangularFields.route], + element[config.restangularFields.fromServer], + element[config.restangularFields.restangularCollection], + element[config.restangularFields.reqParams] + ); } - return data; - } + function restangularizeElem(parent, element, route, fromServer, collection, reqParams) { + var elem = config.onBeforeElemRestangularized(element, false, route); - function fetchFunction(what, reqParams, headers) { - var __this = this; - var deferred = $q.defer(); - var operation = 'getList'; - var url = urlHandler.fetchUrl(this, what); - var whatFetched = what || __this[config.restangularFields.route]; + var localElem = restangularizeBase(parent, elem, route, reqParams, fromServer); - var request = config.fullRequestInterceptor(null, operation, - whatFetched, url, headers || {}, reqParams || {}, this[config.restangularFields.httpConfig] || {}); - - var filledArray = []; - filledArray = config.transformElem(filledArray, true, whatFetched, service); + if (config.useCannonicalId) { + localElem[config.restangularFields.cannonicalId] = config.getIdFromElem(localElem); + } - var method = 'getList'; + if (collection) { + localElem[config.restangularFields.getParentList] = function() { + return collection; + }; + } - if (config.jsonp) { - method = 'jsonp'; + localElem[config.restangularFields.restangularCollection] = false; + localElem[config.restangularFields.get] = _.bind(getFunction, localElem); + localElem[config.restangularFields.getList] = _.bind(fetchFunction, localElem); + localElem[config.restangularFields.put] = _.bind(putFunction, localElem); + localElem[config.restangularFields.post] = _.bind(postFunction, localElem); + localElem[config.restangularFields.remove] = _.bind(deleteFunction, localElem); + localElem[config.restangularFields.head] = _.bind(headFunction, localElem); + localElem[config.restangularFields.trace] = _.bind(traceFunction, localElem); + localElem[config.restangularFields.options] = _.bind(optionsFunction, localElem); + localElem[config.restangularFields.patch] = _.bind(patchFunction, localElem); + localElem[config.restangularFields.save] = _.bind(save, localElem); + + addCustomOperation(localElem); + return config.transformElem(localElem, false, route, service, true); } - var okCallback = function(response) { - var resData = response.data; - var fullParams = response.config.params; - var data = parseResponse(resData, operation, whatFetched, url, response, deferred); - - // support empty response for getList() calls (some APIs respond with 204 and empty body) - if (_.isUndefined(data) || '' === data) { - data = []; - } - if (!_.isArray(data)) { - throw new Error('Response for getList SHOULD be an array and not an object or something else'); - } - - if (true === config.plainByDefault) { - return resolvePromise(deferred, response, data, filledArray); - } + function restangularizeCollection(parent, element, route, fromServer, reqParams) { + var elem = config.onBeforeElemRestangularized(element, true, route); + + var localElem = restangularizeBase(parent, elem, route, reqParams, fromServer); + localElem[config.restangularFields.restangularCollection] = true; + localElem[config.restangularFields.post] = _.bind(postFunction, localElem, null); + localElem[config.restangularFields.remove] = _.bind(deleteFunction, localElem); + localElem[config.restangularFields.head] = _.bind(headFunction, localElem); + localElem[config.restangularFields.trace] = _.bind(traceFunction, localElem); + localElem[config.restangularFields.putElement] = _.bind(putElementFunction, localElem); + localElem[config.restangularFields.options] = _.bind(optionsFunction, localElem); + localElem[config.restangularFields.patch] = _.bind(patchFunction, localElem); + localElem[config.restangularFields.get] = _.bind(getById, localElem); + localElem[config.restangularFields.getList] = _.bind(fetchFunction, localElem, null); + + addCustomOperation(localElem); + return config.transformElem(localElem, true, route, service, true); + } - var processedData = _.map(data, function(elem) { - if (!__this[config.restangularFields.restangularCollection]) { - return restangularizeElem(__this, elem, what, true, data); - } else { - return restangularizeElem(__this[config.restangularFields.parentResource], - elem, __this[config.restangularFields.route], true, data); + function restangularizeCollectionAndElements(parent, element, route, fromServer) { + var collection = restangularizeCollection(parent, element, route, fromServer); + _.each(collection, function(elem) { + if (elem) { + restangularizeElem(parent, elem, route, fromServer); } }); + return collection; + } - processedData = _.extend(data, processedData); - - if (!__this[config.restangularFields.restangularCollection]) { - resolvePromise( - deferred, - response, - restangularizeCollection( - __this, - processedData, - what, - true, - fullParams - ), - filledArray - ); - } else { - resolvePromise( - deferred, - response, - restangularizeCollection( - __this[config.restangularFields.parentResource], - processedData, - __this[config.restangularFields.route], - true, - fullParams - ), - filledArray - ); - } - }; + function getById(id, reqParams, headers) { + return this.customGET(id.toString(), reqParams, headers); + } - urlHandler.resource(this, $http, request.httpConfig, request.headers, request.params, what, - this[config.restangularFields.etag], operation)[method]().then(okCallback, function error(response) { - if (response.status === 304 && __this[config.restangularFields.restangularCollection]) { - resolvePromise(deferred, response, __this, filledArray); - } else if ( _.every(config.errorInterceptors, function(cb) { return cb(response, deferred, okCallback) !== false; }) ) { - // triggered if no callback returns false + function putElementFunction(idx, params, headers) { + var __this = this; + var elemToPut = this[idx]; + var deferred = $q.defer(); + var filledArray = []; + filledArray = config.transformElem(filledArray, true, elemToPut[config.restangularFields.route], service); + elemToPut.put(params, headers).then(function(serverElem) { + var newArray = copyRestangularizedElement(__this); + newArray[idx] = serverElem; + filledArray = newArray; + deferred.resolve(newArray); + }, function(response) { deferred.reject(response); - } - }); - - return restangularizePromise(deferred.promise, true, filledArray); - } + }); - function withHttpConfig(httpConfig) { - this[config.restangularFields.httpConfig] = httpConfig; - return this; - } + return restangularizePromise(deferred.promise, true, filledArray); + } - function save(params, headers) { - if (this[config.restangularFields.fromServer]) { - return this[config.restangularFields.put](params, headers); - } else { - return _.bind(elemFunction, this)('post', undefined, params, undefined, headers); + function parseResponse(resData, operation, route, fetchUrl, response, deferred) { + var data = config.responseExtractor(resData, operation, route, fetchUrl, response, deferred); + var etag = response.headers('ETag'); + if (data && etag) { + data[config.restangularFields.etag] = etag; + } + return data; } - } - function elemFunction(operation, what, params, obj, headers) { - var __this = this; - var deferred = $q.defer(); - var resParams = params || {}; - var route = what || this[config.restangularFields.route]; - var fetchUrl = urlHandler.fetchUrl(this, what); - var callObj = obj || this; - // fallback to etag on restangular object (since for custom methods we probably don't explicitly specify the etag field) - var etag = callObj[config.restangularFields.etag] || (operation !== 'post' ? this[config.restangularFields.etag] : null); + function fetchFunction(what, reqParams, headers) { + var __this = this; + var deferred = $q.defer(); + var operation = 'getList'; + var url = urlHandler.fetchUrl(this, what); + var whatFetched = what || __this[config.restangularFields.route]; - if (_.isObject(callObj) && config.isRestangularized(callObj)) { - callObj = stripRestangular(callObj); - } - var request = config.fullRequestInterceptor(callObj, operation, route, fetchUrl, - headers || {}, resParams || {}, this[config.restangularFields.httpConfig] || {}); + var request = config.fullRequestInterceptor(null, operation, + whatFetched, url, headers || {}, reqParams || {}, this[config.restangularFields.httpConfig] || {}); + + var filledArray = []; + filledArray = config.transformElem(filledArray, true, whatFetched, service); - var filledObject = {}; - filledObject = config.transformElem(filledObject, false, route, service); + var method = 'getList'; - var okCallback = function(response) { - var resData = response.data; - var fullParams = response.config.params; - var elem = parseResponse(resData, operation, route, fetchUrl, response, deferred); + if (config.jsonp) { + method = 'jsonp'; + } - // accept 0 as response - if (elem !== null && elem !== undefined && elem !== '') { - var data; + var okCallback = function(response) { + var resData = response.data; + var fullParams = response.config.params; + var data = parseResponse(resData, operation, whatFetched, url, response, deferred); + + // support empty response for getList() calls (some APIs respond with 204 and empty body) + if (_.isUndefined(data) || '' === data) { + data = []; + } + if (!_.isArray(data)) { + throw new Error('Response for getList SHOULD be an array and not an object or something else'); + } if (true === config.plainByDefault) { - return resolvePromise(deferred, response, elem, filledObject); + return resolvePromise(deferred, response, data, filledArray); } - if (operation === 'post' && !__this[config.restangularFields.restangularCollection]) { - data = restangularizeElem( - __this[config.restangularFields.parentResource], - elem, - route, - true, - null, - fullParams + var processedData = _.map(data, function(elem) { + if (!__this[config.restangularFields.restangularCollection]) { + return restangularizeElem(__this, elem, what, true, data); + } else { + return restangularizeElem(__this[config.restangularFields.parentResource], + elem, __this[config.restangularFields.route], true, data); + } + }); + + processedData = _.extend(data, processedData); + + if (!__this[config.restangularFields.restangularCollection]) { + resolvePromise( + deferred, + response, + restangularizeCollection( + __this, + processedData, + what, + true, + fullParams + ), + filledArray ); - resolvePromise(deferred, response, data, filledObject); } else { - data = restangularizeElem( - __this[config.restangularFields.parentResource], - elem, - __this[config.restangularFields.route], - true, - null, - fullParams + resolvePromise( + deferred, + response, + restangularizeCollection( + __this[config.restangularFields.parentResource], + processedData, + __this[config.restangularFields.route], + true, + fullParams + ), + filledArray ); + } + }; - data[config.restangularFields.singleOne] = __this[config.restangularFields.singleOne]; - resolvePromise(deferred, response, data, filledObject); + urlHandler.resource(this, $http, request.httpConfig, request.headers, request.params, what, + this[config.restangularFields.etag], operation)[method]().then(okCallback, function error(response) { + if (response.status === 304 && __this[config.restangularFields.restangularCollection]) { + resolvePromise(deferred, response, __this, filledArray); + } else if (_.every(config.errorInterceptors, function(cb) { + return cb(response, deferred, okCallback) !== false; + })) { + // triggered if no callback returns false + deferred.reject(response); } + }); + + return restangularizePromise(deferred.promise, true, filledArray); + } + function withHttpConfig(httpConfig) { + this[config.restangularFields.httpConfig] = httpConfig; + return this; + } + + function save(params, headers) { + if (this[config.restangularFields.fromServer]) { + return this[config.restangularFields.put](params, headers); } else { - resolvePromise(deferred, response, undefined, filledObject); + return _.bind(elemFunction, this)('post', undefined, params, undefined, headers); } - }; + } - var errorCallback = function(response) { - if (response.status === 304 && config.isSafe(operation)) { - resolvePromise(deferred, response, __this, filledObject); - } else if ( _.every(config.errorInterceptors, function(cb) { return cb(response, deferred, okCallback) !== false; }) ) { - // triggered if no callback returns false - deferred.reject(response); + function elemFunction(operation, what, params, obj, headers) { + var __this = this; + var deferred = $q.defer(); + var resParams = params || {}; + var route = what || this[config.restangularFields.route]; + var fetchUrl = urlHandler.fetchUrl(this, what); + + var callObj = obj || this; + // fallback to etag on restangular object (since for custom methods we probably don't explicitly specify the etag field) + var etag = callObj[config.restangularFields.etag] || (operation !== 'post' ? this[config.restangularFields.etag] : null); + + if (_.isObject(callObj) && config.isRestangularized(callObj)) { + callObj = stripRestangular(callObj); } - }; - // Overriding HTTP Method - var callOperation = operation; - var callHeaders = _.extend({}, request.headers); - var isOverrideOperation = config.isOverridenMethod(operation); - if (isOverrideOperation) { - callOperation = 'post'; - callHeaders = _.extend(callHeaders, {'X-HTTP-Method-Override': operation === 'remove' ? 'DELETE' : operation.toUpperCase()}); - } else if (config.jsonp && callOperation === 'get') { - callOperation = 'jsonp'; - } + var request = config.fullRequestInterceptor(callObj, operation, route, fetchUrl, + headers || {}, resParams || {}, this[config.restangularFields.httpConfig] || {}); + + var filledObject = {}; + filledObject = config.transformElem(filledObject, false, route, service); + + var okCallback = function(response) { + var resData = response.data; + var fullParams = response.config.params; + var elem = parseResponse(resData, operation, route, fetchUrl, response, deferred); + + // accept 0 as response + if (elem !== null && elem !== undefined && elem !== '') { + var data; + + if (true === config.plainByDefault) { + return resolvePromise(deferred, response, elem, filledObject); + } + + if (operation === 'post' && !__this[config.restangularFields.restangularCollection]) { + data = restangularizeElem( + __this[config.restangularFields.parentResource], + elem, + route, + true, + null, + fullParams + ); + resolvePromise(deferred, response, data, filledObject); + } else { + data = restangularizeElem( + __this[config.restangularFields.parentResource], + elem, + __this[config.restangularFields.route], + true, + null, + fullParams + ); + + data[config.restangularFields.singleOne] = __this[config.restangularFields.singleOne]; + resolvePromise(deferred, response, data, filledObject); + } + + } else { + resolvePromise(deferred, response, undefined, filledObject); + } + }; - if (config.isSafe(operation)) { + var errorCallback = function(response) { + if (response.status === 304 && config.isSafe(operation)) { + resolvePromise(deferred, response, __this, filledObject); + } else if (_.every(config.errorInterceptors, function(cb) { + return cb(response, deferred, okCallback) !== false; + })) { + // triggered if no callback returns false + deferred.reject(response); + } + }; + // Overriding HTTP Method + var callOperation = operation; + var callHeaders = _.extend({}, request.headers); + var isOverrideOperation = config.isOverridenMethod(operation); if (isOverrideOperation) { - urlHandler.resource(this, $http, request.httpConfig, callHeaders, request.params, - what, etag, callOperation)[callOperation]({}).then(okCallback, errorCallback); + callOperation = 'post'; + callHeaders = _.extend(callHeaders, { + 'X-HTTP-Method-Override': operation === 'remove' ? 'DELETE' : operation.toUpperCase() + }); + } else if (config.jsonp && callOperation === 'get') { + callOperation = 'jsonp'; + } + + if (config.isSafe(operation)) { + if (isOverrideOperation) { + urlHandler.resource(this, $http, request.httpConfig, callHeaders, request.params, + what, etag, callOperation)[callOperation]({}).then(okCallback, errorCallback); + } else { + urlHandler.resource(this, $http, request.httpConfig, callHeaders, request.params, + what, etag, callOperation)[callOperation]().then(okCallback, errorCallback); + } } else { urlHandler.resource(this, $http, request.httpConfig, callHeaders, request.params, - what, etag, callOperation)[callOperation]().then(okCallback, errorCallback); + what, etag, callOperation)[callOperation](request.element).then(okCallback, errorCallback); } - } else { - urlHandler.resource(this, $http, request.httpConfig, callHeaders, request.params, - what, etag, callOperation)[callOperation](request.element).then(okCallback, errorCallback); - } - - return restangularizePromise(deferred.promise, false, filledObject); - } - function getFunction(params, headers) { - return _.bind(elemFunction, this)('get', undefined, params, undefined, headers); - } + return restangularizePromise(deferred.promise, false, filledObject); + } - function deleteFunction(params, headers) { - return _.bind(elemFunction, this)('remove', undefined, params, undefined, headers); - } + function getFunction(params, headers) { + return _.bind(elemFunction, this)('get', undefined, params, undefined, headers); + } - function putFunction(params, headers) { - return _.bind(elemFunction, this)('put', undefined, params, undefined, headers); - } + function deleteFunction(params, headers) { + return _.bind(elemFunction, this)('remove', undefined, params, undefined, headers); + } - function postFunction(what, elem, params, headers) { - return _.bind(elemFunction, this)('post', what, params, elem, headers); - } + function putFunction(params, headers) { + return _.bind(elemFunction, this)('put', undefined, params, undefined, headers); + } - function headFunction(params, headers) { - return _.bind(elemFunction, this)('head', undefined, params, undefined, headers); - } + function postFunction(what, elem, params, headers) { + return _.bind(elemFunction, this)('post', what, params, elem, headers); + } - function traceFunction(params, headers) { - return _.bind(elemFunction, this)('trace', undefined, params, undefined, headers); - } + function headFunction(params, headers) { + return _.bind(elemFunction, this)('head', undefined, params, undefined, headers); + } - function optionsFunction(params, headers) { - return _.bind(elemFunction, this)('options', undefined, params, undefined, headers); - } + function traceFunction(params, headers) { + return _.bind(elemFunction, this)('trace', undefined, params, undefined, headers); + } - function patchFunction(elem, params, headers) { - return _.bind(elemFunction, this)('patch', undefined, params, elem, headers); - } + function optionsFunction(params, headers) { + return _.bind(elemFunction, this)('options', undefined, params, undefined, headers); + } - function customFunction(operation, path, params, headers, elem) { - return _.bind(elemFunction, this)(operation, path, params, elem, headers); - } + function patchFunction(elem, params, headers) { + return _.bind(elemFunction, this)('patch', undefined, params, elem, headers); + } - function addRestangularMethodFunction(name, operation, path, defaultParams, defaultHeaders, defaultElem) { - var bindedFunction; - if (operation === 'getList') { - bindedFunction = _.bind(fetchFunction, this, path); - } else { - bindedFunction = _.bind(customFunction, this, operation, path); + function customFunction(operation, path, params, headers, elem) { + return _.bind(elemFunction, this)(operation, path, params, elem, headers); } - var createdFunction = function(params, headers, elem) { - var callParams = _.defaults({ - params: params, - headers: headers, - elem: elem - }, { - params: defaultParams, - headers: defaultHeaders, - elem: defaultElem - }); - return bindedFunction(callParams.params, callParams.headers, callParams.elem); - }; + function addRestangularMethodFunction(name, operation, path, defaultParams, defaultHeaders, defaultElem) { + var bindedFunction; + if (operation === 'getList') { + bindedFunction = _.bind(fetchFunction, this, path); + } else { + bindedFunction = _.bind(customFunction, this, operation, path); + } - if (config.isSafe(operation)) { - this[name] = createdFunction; - } else { - this[name] = function(elem, params, headers) { - return createdFunction(params, headers, elem); + var createdFunction = function(params, headers, elem) { + var callParams = _.defaults({ + params: params, + headers: headers, + elem: elem + }, { + params: defaultParams, + headers: defaultHeaders, + elem: defaultElem + }); + return bindedFunction(callParams.params, callParams.headers, callParams.elem); }; + + if (config.isSafe(operation)) { + this[name] = createdFunction; + } else { + this[name] = function(elem, params, headers) { + return createdFunction(params, headers, elem); + }; + } } - } - function withConfigurationFunction(configurer) { - var newConfig = angular.copy(_.omit(config, 'configuration')); - Configurer.init(newConfig, newConfig); - configurer(newConfig); - return createServiceForConfiguration(newConfig); - } + function withConfigurationFunction(configurer) { + var newConfig = angular.copy(_.omit(config, 'configuration')); + Configurer.init(newConfig, newConfig); + configurer(newConfig); + return createServiceForConfiguration(newConfig); + } - function toService(route, parent) { - var knownCollectionMethods = _.values(config.restangularFields); - var serv = {}; - var collection = (parent || service).all(route); - serv.one = _.bind(one, (parent || service), parent, route); - serv.post = _.bind(collection.post, collection); - serv.getList = _.bind(collection.getList, collection); - serv.withHttpConfig = _.bind(collection.withHttpConfig, collection); - serv.get = _.bind(collection.get, collection); - - for (var prop in collection) { - if (collection.hasOwnProperty(prop) && _.isFunction(collection[prop]) && !_.includes(knownCollectionMethods, prop)) { - serv[prop] = _.bind(collection[prop], collection); + function toService(route, parent) { + var knownCollectionMethods = _.values(config.restangularFields); + var serv = {}; + var collection = (parent || service).all(route); + serv.one = _.bind(one, (parent || service), parent, route); + serv.post = _.bind(collection.post, collection); + serv.getList = _.bind(collection.getList, collection); + serv.withHttpConfig = _.bind(collection.withHttpConfig, collection); + serv.get = _.bind(collection.get, collection); + + for (var prop in collection) { + if (collection.hasOwnProperty(prop) && _.isFunction(collection[prop]) && !_.includes(knownCollectionMethods, prop)) { + serv[prop] = _.bind(collection[prop], collection); + } } - } - return serv; - } + return serv; + } - Configurer.init(service, config); + Configurer.init(service, config); - service.copy = _.bind(copyRestangularizedElement, service); + service.copy = _.bind(copyRestangularizedElement, service); - service.service = _.bind(toService, service); + service.service = _.bind(toService, service); - service.withConfig = _.bind(withConfigurationFunction, service); + service.withConfig = _.bind(withConfigurationFunction, service); - service.one = _.bind(one, service, null); + service.one = _.bind(one, service, null); - service.all = _.bind(all, service, null); + service.all = _.bind(all, service, null); - service.several = _.bind(several, service, null); + service.several = _.bind(several, service, null); - service.oneUrl = _.bind(oneUrl, service, null); + service.oneUrl = _.bind(oneUrl, service, null); - service.allUrl = _.bind(allUrl, service, null); + service.allUrl = _.bind(allUrl, service, null); - service.stripRestangular = _.bind(stripRestangular, service); + service.stripRestangular = _.bind(stripRestangular, service); - service.restangularizeElement = _.bind(restangularizeElem, service); + service.restangularizeElement = _.bind(restangularizeElem, service); - service.restangularizeCollection = _.bind(restangularizeCollectionAndElements, service); + service.restangularizeCollection = _.bind(restangularizeCollectionAndElements, service); - return service; - } + return service; + } - return createServiceForConfiguration(globalConfiguration); - }]; -}); + return createServiceForConfiguration(globalConfiguration); + }]; + }); return restangular.name; })); diff --git a/dist/restangular.min.js b/dist/restangular.min.js index 7e481e41..67d3cfc0 100644 --- a/dist/restangular.min.js +++ b/dist/restangular.min.js @@ -1,6 +1,6 @@ /** * Restful Resources service for AngularJS apps - * @version v1.6.0 - 2016-12-25 * @link https://github.com/mgonto/restangular + * @version v1.6.0 - 2017-01-05 * @link https://github.com/mgonto/restangular * @author Martin Gontovnikas * @license MIT License, http://www.opensource.org/licenses/MIT - */!function(a,b){"function"==typeof define&&define.amd?define(["lodash","angular"],b):"object"==typeof module&&module.exports?module.exports=b(require("lodash"),require("angular")):b(a._,a.angular)}(this,function(a,b){var c=b.module("restangular",[]);return c.provider("Restangular",function(){var c={};c.init=function(c,d){function e(b,c,d,e){var f={};return a.each(a.keys(e),function(g){var h=e[g];h.params=a.extend({},h.params,b.defaultRequestParams[h.method.toLowerCase()]),a.isEmpty(h.params)&&delete h.params,b.isSafe(h.method)?f[g]=function(){return c(a.extend(h,{url:d}))}:f[g]=function(b){return c(a.extend(h,{url:d,data:b}))}}),f}c.configuration=d;var f=["get","head","options","trace","getlist"];d.isSafe=function(b){return a.includes(f,b.toLowerCase())};var g=/^https?:\/\//i;d.isAbsoluteUrl=function(b){return a.isUndefined(d.absoluteUrl)||a.isNull(d.absoluteUrl)?b&&g.test(b):d.absoluteUrl},d.absoluteUrl=!!a.isUndefined(d.absoluteUrl)||d.absoluteUrl,c.setSelfLinkAbsoluteUrl=function(a){d.absoluteUrl=a},d.baseUrl=a.isUndefined(d.baseUrl)?"":d.baseUrl,c.setBaseUrl=function(a){return d.baseUrl=/\/$/.test(a)?a.substring(0,a.length-1):a,this},d.extraFields=d.extraFields||[],c.setExtraFields=function(a){return d.extraFields=a,this},d.defaultHttpFields=d.defaultHttpFields||{},c.setDefaultHttpFields=function(a){return d.defaultHttpFields=a,this},d.plainByDefault=d.plainByDefault||!1,c.setPlainByDefault=function(a){return d.plainByDefault=a===!0,this},d.withHttpValues=function(b,c){return a.defaults(c,b,d.defaultHttpFields)},d.encodeIds=!!a.isUndefined(d.encodeIds)||d.encodeIds,c.setEncodeIds=function(a){d.encodeIds=a},d.defaultRequestParams=d.defaultRequestParams||{get:{},post:{},put:{},remove:{},common:{}},c.setDefaultRequestParams=function(b,c){var e=[],f=c||b;return a.isUndefined(c)?e.push("common"):a.isArray(b)?e=b:e.push(b),a.each(e,function(a){d.defaultRequestParams[a]=f}),this},c.requestParams=d.defaultRequestParams,d.defaultHeaders=d.defaultHeaders||{},c.setDefaultHeaders=function(a){return d.defaultHeaders=a,c.defaultHeaders=d.defaultHeaders,this},c.defaultHeaders=d.defaultHeaders,d.methodOverriders=d.methodOverriders||[],c.setMethodOverriders=function(b){var c=a.extend([],b);return d.isOverridenMethod("delete",c)&&c.push("remove"),d.methodOverriders=c,this},d.jsonp=!a.isUndefined(d.jsonp)&&d.jsonp,c.setJsonp=function(a){d.jsonp=a},d.isOverridenMethod=function(b,c){var e=c||d.methodOverriders;return!a.isUndefined(a.find(e,function(a){return a.toLowerCase()===b.toLowerCase()}))},d.urlCreator=d.urlCreator||"path",c.setUrlCreator=function(b){if(!a.has(d.urlCreatorFactory,b))throw new Error("URL Path selected isn't valid");return d.urlCreator=b,this},d.restangularFields=d.restangularFields||{id:"id",route:"route",parentResource:"parentResource",restangularCollection:"restangularCollection",cannonicalId:"__cannonicalId",etag:"restangularEtag",selfLink:"href",get:"get",getList:"getList",put:"put",post:"post",remove:"remove",head:"head",trace:"trace",options:"options",patch:"patch",getRestangularUrl:"getRestangularUrl",getRequestedUrl:"getRequestedUrl",putElement:"putElement",addRestangularMethod:"addRestangularMethod",getParentList:"getParentList",clone:"clone",ids:"ids",httpConfig:"_$httpConfig",reqParams:"reqParams",one:"one",all:"all",several:"several",oneUrl:"oneUrl",allUrl:"allUrl",customPUT:"customPUT",customPATCH:"customPATCH",customPOST:"customPOST",customDELETE:"customDELETE",customGET:"customGET",customGETLIST:"customGETLIST",customOperation:"customOperation",doPUT:"doPUT",doPATCH:"doPATCH",doPOST:"doPOST",doDELETE:"doDELETE",doGET:"doGET",doGETLIST:"doGETLIST",fromServer:"fromServer",withConfig:"withConfig",withHttpConfig:"withHttpConfig",singleOne:"singleOne",plain:"plain",save:"save",restangularized:"restangularized"},c.setRestangularFields=function(b){return d.restangularFields=a.extend(d.restangularFields,b),this},d.isRestangularized=function(a){return!!a[d.restangularFields.restangularized]},d.setFieldToElem=function(b,c,d){var e=b.split("."),f=c;return a.each(a.initial(e),function(a){f[a]={},f=f[a]}),f[a.last(e)]=d,this},d.getFieldFromElem=function(c,d){var e=c.split("."),f=d;return a.each(e,function(a){f&&(f=f[a])}),b.copy(f)},d.setIdToElem=function(a,b){return d.setFieldToElem(d.restangularFields.id,a,b),this},d.getIdFromElem=function(a){return d.getFieldFromElem(d.restangularFields.id,a)},d.isValidId=function(b){return""!==b&&!a.isUndefined(b)&&!a.isNull(b)},d.setUrlToElem=function(a,b){return d.setFieldToElem(d.restangularFields.selfLink,a,b),this},d.getUrlFromElem=function(a){return d.getFieldFromElem(d.restangularFields.selfLink,a)},d.useCannonicalId=!a.isUndefined(d.useCannonicalId)&&d.useCannonicalId,c.setUseCannonicalId=function(a){return d.useCannonicalId=a,this},d.getCannonicalIdFromElem=function(a){var b=a[d.restangularFields.cannonicalId],c=d.isValidId(b)?b:d.getIdFromElem(a);return c},d.responseInterceptors=d.responseInterceptors||[],d.defaultResponseInterceptor=function(a){return a},d.responseExtractor=function(c,e,f,g,h,i){var j=b.copy(d.responseInterceptors);j.push(d.defaultResponseInterceptor);var k=c;return a.each(j,function(a){k=a(k,e,f,g,h,i)}),k},c.addResponseInterceptor=function(a){return d.responseInterceptors.push(a),this},d.errorInterceptors=d.errorInterceptors||[],c.addErrorInterceptor=function(a){return d.errorInterceptors.push(a),this},c.setResponseInterceptor=c.addResponseInterceptor,c.setResponseExtractor=c.addResponseInterceptor,c.setErrorInterceptor=c.addErrorInterceptor,d.requestInterceptors=d.requestInterceptors||[],d.defaultInterceptor=function(a,b,c,d,e,f,g){return{element:a,headers:e,params:f,httpConfig:g}},d.fullRequestInterceptor=function(c,e,f,g,h,i,j){var k=b.copy(d.requestInterceptors),l=d.defaultInterceptor(c,e,f,g,h,i,j);return a.reduce(k,function(b,c){return a.extend(b,c(b.element,e,f,g,b.headers,b.params,b.httpConfig))},l)},c.addRequestInterceptor=function(a){return d.requestInterceptors.push(function(b,c,d,e,f,g,h){return{headers:f,params:g,element:a(b,c,d,e),httpConfig:h}}),this},c.setRequestInterceptor=c.addRequestInterceptor,c.addFullRequestInterceptor=function(a){return d.requestInterceptors.push(a),this},c.setFullRequestInterceptor=c.addFullRequestInterceptor,d.onBeforeElemRestangularized=d.onBeforeElemRestangularized||function(a){return a},c.setOnBeforeElemRestangularized=function(a){return d.onBeforeElemRestangularized=a,this},c.setRestangularizePromiseInterceptor=function(a){return d.restangularizePromiseInterceptor=a,this},d.onElemRestangularized=d.onElemRestangularized||function(a){return a},c.setOnElemRestangularized=function(a){return d.onElemRestangularized=a,this},d.shouldSaveParent=d.shouldSaveParent||function(){return!0},c.setParentless=function(b){return a.isArray(b)?d.shouldSaveParent=function(c){return!a.includes(b,c)}:a.isBoolean(b)&&(d.shouldSaveParent=function(){return!b}),this},d.suffix=a.isUndefined(d.suffix)?null:d.suffix,c.setRequestSuffix=function(a){return d.suffix=a,this},d.transformers=d.transformers||{},d.matchTransformers=d.matchTransformers||[],c.addElementTransformer=function(b,e,f){var g=null,h=null;2===arguments.length?h=e:(h=f,g=e);var i=function(b,c){return a.isNull(g)||b===g?h(c):c};return a.isRegExp(b)?d.matchTransformers.push({regexp:b,transformer:i}):(d.transformers[b]||(d.transformers[b]=[]),d.transformers[b].push(i)),c},c.extendCollection=function(a,b){return c.addElementTransformer(a,!0,b)},c.extendModel=function(a,b){return c.addElementTransformer(a,!1,b)},d.transformElem=function(b,c,e,f,g){if(!g&&!d.transformLocalElements&&!b[d.restangularFields.fromServer])return b;var h=b,i=d.matchTransformers;i&&a.each(i,function(a){e.match(a.regexp)&&(h=a.transformer(c,h))});var j=d.transformers[e];return j&&a.each(j,function(a){h=a(c,h)}),d.onElemRestangularized(h,c,e,f)},d.transformLocalElements=!a.isUndefined(d.transformLocalElements)&&d.transformLocalElements,c.setTransformOnlyServerElements=function(a){d.transformLocalElements=!a},d.fullResponse=!a.isUndefined(d.fullResponse)&&d.fullResponse,c.setFullResponse=function(a){return d.fullResponse=a,this},d.urlCreatorFactory={};var h=function(){};h.prototype.setConfig=function(a){return this.config=a,this},h.prototype.parentsArray=function(a){for(var b=[];a;)b.push(a),a=a[this.config.restangularFields.parentResource];return b.reverse()},h.prototype.resource=function(b,c,f,g,h,i,j,k){var l=a.defaults(h||{},this.config.defaultRequestParams.common),m=a.defaults(g||{},this.config.defaultHeaders);j&&(d.isSafe(k)?m["If-None-Match"]=j:m["If-Match"]=j);var n=this.base(b);if(i||0===i){var o="";/\/$/.test(n)||(o+="/"),o+=i,n+=o}return this.config.suffix&&n.indexOf(this.config.suffix,n.length-this.config.suffix.length)===-1&&!this.config.getUrlFromElem(b)&&(n+=this.config.suffix),b[this.config.restangularFields.httpConfig]=void 0,e(this.config,c,n,{getList:this.config.withHttpValues(f,{method:"GET",params:l,headers:m}),get:this.config.withHttpValues(f,{method:"GET",params:l,headers:m}),jsonp:this.config.withHttpValues(f,{method:"jsonp",params:l,headers:m}),put:this.config.withHttpValues(f,{method:"PUT",params:l,headers:m}),post:this.config.withHttpValues(f,{method:"POST",params:l,headers:m}),remove:this.config.withHttpValues(f,{method:"DELETE",params:l,headers:m}),head:this.config.withHttpValues(f,{method:"HEAD",params:l,headers:m}),trace:this.config.withHttpValues(f,{method:"TRACE",params:l,headers:m}),options:this.config.withHttpValues(f,{method:"OPTIONS",params:l,headers:m}),patch:this.config.withHttpValues(f,{method:"PATCH",params:l,headers:m})})};var i=function(){};i.prototype=new h,i.prototype.normalizeUrl=function(a){var b=/((?:http[s]?:)?\/\/)?(.*)?/.exec(a);return b[2]=b[2].replace(/[\\\/]+/g,"/"),"undefined"!=typeof b[1]?b[1]+b[2]:b[2]},i.prototype.base=function(b){var c=this;return a.reduce(this.parentsArray(b),function(a,b){var e,f=c.config.getUrlFromElem(b);if(f){if(c.config.isAbsoluteUrl(f))return f;e=f}else if(e=b[c.config.restangularFields.route],b[c.config.restangularFields.restangularCollection]){var g=b[c.config.restangularFields.ids];g&&(e+="/"+g.join(","))}else{var h;h=c.config.useCannonicalId?c.config.getCannonicalIdFromElem(b):c.config.getIdFromElem(b),d.isValidId(h)&&!b.singleOne&&(e+="/"+(c.config.encodeIds?encodeURIComponent(h):h))}return a=a.replace(/\/$/,"")+"/"+e,c.normalizeUrl(a)},this.config.baseUrl)},i.prototype.fetchUrl=function(a,b){var c=this.base(a);return b&&(c+="/"+b),c},i.prototype.fetchRequestedUrl=function(a,c){function e(a){var b=[];for(var c in a)a.hasOwnProperty(c)&&b.push(c);return b.sort()}function f(a,b,c){for(var d=e(a),f=0;fu#22Uz#0J5JklWHxy~0Srlk&zy(E(8Z$Gl@EOs> zJuY#E)j(IXraEzT5WC;YAtn*EtXipW?9tHXtM~>@#pef zuDJN==f?`>mimq+vyG7u4P>LzMp9EaD%nUJ;)OzMOI>4qht14pAp}8KY_<(REjA%X zSl*SzDEEv?##^JJJlMq13~+Z;U7$d7aVk4qo4L+1(bC>vt!)#~;hKh{u10)t#uFdA zrVt@9VuUKUcqYT?&*j)9lA_+thvTevE5$9hWVnk|ZX6eybC3!!gmMF1R zLe!E}Mon+`B|1h;Xm*n^S>ZD=0Mk9Axvd`a%pJ7nYo0R@EKNkIR67VYHj-|q~j+5o2A*h*fN}~#@gI$ZDBA6C&yCCWJQ=F%fn`w9>$X*T#|+2 zJy{S8TmcE{8;q`7aO=oQd?NJPiZIzgip7St&w+THh)AT55Uh>Q}1#@b9l*I26?TzH$cqn#F2 z$6F4ET4}^sWd@cj_1Le};<{4f<8T=rIgTCaGo=7mm7FBAyN-@E_Ms?M2u7(3(SJ_FZ7Z0Yng~Y!?To%rlg%_5#L_cHBcNInfpwZN9MnYNReJwSqlYvwmZYe) zmX%K@Qr_B$#{~)sJ}`NjV}v^Pq0YcCPE$PAK0>X1;6z{qZU% zY}RIG0i$CLp<>lW1vUq%DKn8jP~;v9%EjYJa%>euJ;9z32XikT9^K|n6)IAs=n9rm zgp1J~oa%6Mvk-*HP$j+(_Qjac7$k)Rp)|yVH$uhO7!rWJAzB_iuZHl1H5fsX(Ch(% z$3l%b7aGWsZ=do;V4fKFLnFwIn9JgRqY62K)>WX$guW#My+Xk7aC}}!!u@B&_+H2# zoFP}6z=iG5j&^d2wW;xrHq6P%1Y=m>a88wB$qr75hzs+Q4hwyTf^kIB8*60<3cG{)S$A3rp@;ScMtC&`f#eLEcihP5PX4w=Sh{Z`- z>@%bcx7X)}Vi=%cjT3G(G&db;t$V&o-tDgHpHr!am2SWPJ{l~GR%t)Kv=8> zvrGLj@q!ea<2Cp+-r&u(VgXjeO95#Apyc>&abqzqt{s=+N)VSOhQv4qrN;5tX4DMJ zixU%qx!lz;L6F5yWw`o4s*;6Y|FqETPQle-sG2Tm-Lz1F-V9tz`9JHnIICDDucc}vYBZZYYV&8UKhh>Np zl4k{^rr^BH2QyRsaW6Fzo2cDYB*u}{2*h0S!jpXxe3TkWW62zzmgY$j^t^AXfLf3^t7?HwAEQ{ z%@&){W~sLj<(DJa3K1xXYx>`gY>n=PK$ zp1qj`l1A(Cj@%#fs@>&|n%&_Hj)bvh^msB0D_JF!(ME^6@lpjpjxIoMVFXR$mg~ zr7;0`Z%isAWA#WF8-b}~TX1!32EHB}gW#f2%sT9i;vys3ieex;Olw^fh1ZId*k0`G zFpp3!AH`H#SQHL@F)8i9sxQ`{WSJ7%ik(UH?NbW0?or@Yu_ud4ryAcC&nA0K87D{0 zxVgA8ZYu56Y%wkE=6Z)JfhEbvx*$e*NieTjb1t}JV@Vj*tumBdmEc-QB>q$q0a>Rz zJjeS&Gd_a0BXe!75R)V5;&r%nN)bK)Skhn~@Jtqd9kwQhN9efIr^eRtAviI9E<_W` z?FI3KB0N1IAD>M~6Znaahb;eRFO#XH7$`o@ltqxFetwQsWmsK?SiXC;mS+6F=j9F#PolK& z@L4h@KR8R*4f?=2X2zPj2XN_aCR`BYRpJTBk)* zWIQpAo#q|bFHK9q?e1Wa&C=dsYk_L|SUPUAy8ug*rMcYJ#0%L4(~EIxxYgEi?3doZWukXh46e@#rFdW(@Icr# zTZMnkih_1_v?J76L^^bzIXiM7V{Mo%8`ON)>|%T}I|1Hv3Q;yEADido;I%nf7+Em} zW9V65QGk~#BB|VB=2&Q1)d#a>vo+7~yg z#fWb8Vhd-Fn;R{${5pdWdOPJV60Ej~M^?;(wpGbJIoE1%XD35a9Sg0I*lewGG918{ z)L5IEs4}B#!~wd3RwMQ=(m=LdjyD&L;etDbORyz6dHlRk)->Y z{kRlwwyU@SzHHaQzatW99T7-aB4BYx6nC0^9ocxFVE1Uuw#45h3s;V-P_kG~5#!}h z&&E0YZE*zW;H0)KiNvdPCOWVr2q!z0v^I9U(xoN9Qavs%Wd$o$E9fpzV8=lzFQnDW zl6dK}z1R=2<#N1!+!Gs?1&|IhH~UhaSf;`Y%VK%?eOV^Py=DGfQvXf`be(gtt}~X) z_C%25xZ+7WQcpwOP94rrr z8VaEM=q3xhY^v#KYqvHRIqtC8Lfkp-552|pe(lPS6ixVRNv zdDziq>X|9RyIs?`#B7))jLMa1ygj?JfLr$7$_N?TqXz5K%vFX}PRPLNmPIUiMNNaH zW+Aid5{r&*%4;p=rpA_fVM$|qgJ7m3u+xVHeWTH0q}y}BY_pk{8HVUbT+A)Cl)&5C z9hd8kofe_J!NRvn5<}={8^f3~P^-<_O1J34k!^!XgbU>mRv_^P1Yp)WDN}ej6~SgI zjt2sC_f8)d3+}BFcmm1Y;e*dsdHDe8 zq0-Idnt-2$liboFoDF;rJY20LLB`|8>M2k>QUU8D(dd1olAk!%U-5$18ZV@;)uZBH zo|w3XE-mO>(Yi*#eUP0>g4TpX_(Fkq*Z4uRMvu+E@j~F*5Z<4^Hi_CuoL?Ky+t6P{ z!MN6w_vfrjI=A2t*rZ({|vo&pbnkeyj!r*WU!>4Wmr)q9Yqy6zC4wWv|Y()-qngV$F#V&s|Xv9M&iq3Ug+QB zh1_&*iTovpveh^5qLHwq`8 zn%;9zi_nAR7`rWLnES92NPK7-<{XN^ibLb@!XY*Oa43y@VIuPp)E}-Vw~C{s8*ceY zJKT{+OE&3DkjkkE$$x?|DP8Scd2q2c_ISkT?%BL@W+G`Sy*?Xggzn~@P3~! zM$zNfr}O@aGr-hW&YdM>B~?Xzqj9@0+#aGueo&oMar$phRpH#pdHiI0_f#eIjW~5G zj@RZfyn=Tio7J~^MkVPVhMkAjS@>zPCj z>wjjv1CMTDSE!Nwtb)TUo^8V$&xVl81mIr;^X<>&Zs6aq!tv!k_)WjS)qJLZGBz}LP4EI;KaiixIX}FR5FM4Cs zMKz!4;>9e!gN9rd!R1mc-$k8;WXUBH-36B5#-&7jb4knhhmgw&T=JQhWAVsk_7Q`f zs@p8K#e+XJ_~3E@G|v^G^0_SZK39O>KQ|fSS29s?B?jxRtb^!k9$(Bkhh-|qIhrwG zCvy8AoojIYk{XX)Rb$W9vH0X_IuzH&qw-oZw$O9^S{CkJGa=!+3H8^DapL+UdQ|Xv zemrJfm3cMR3|`|CE8oj8c;a~#`w+#C{Ve<7PKz8jpI3OXA8_3G$H&uGP8%^|9VfQv!2B+P|C{{!`5qILiP delta 7791 zcmb7J33OCNx;A9*J)Pdu>D#$2ZRfd3Eb{5;Ed>=k+(*cY|JPsjuT>Z4 zy)N$eQq@#=dhs4$e+&0*D?fVm?)xvCdEkK;&TLfKd{Sc)5@Qlkas8PfLvvS0qpQ{4 z!5AENyUWZpTN_<=M~{)|Ju=s0*du^3)SDS=$Na7~tAhY}JeWOG(d3yF5EsW3+B=$C z=XW`*EC(KjJk_}1nSh@?!@c56CLSWaniZxv9z%ZJRtMv>HruLft`>Wf6MMZ<@u`;? zLho#^zD&Y)vUeO-dB@|Z_cZwUq~UuX1*&~~xWVsz)adpp#7UoY{OH5*2?fiPPO@lv zAS}K{Pcji&eJA0#Zvt-nCW7%ZYGM+MOa?_|(K~F53#?AtG)G$|4~vqN0(Rn2KAZe9 zDUbL3(jfG=;Lmu9hbQ?x z;Y+;SQNjSe5)ytS()me{@)cOck75b@MnppV6gh73P2dSixjncgP@+?ii^GCAd?5(o zfGZ3#8D2{O0+U?yo4#=*PVH)l2?e-29LKjYV{)n&y zzX-FDA&P`1U4%}N9FK@JNIxHl*F-ujE%d{ywE^T^D2w74L>V~+Fiam=tVXQ!V+`$7 zv#|05(ID|fPPquJVwG1PL1j0alXssu8+XMD+!2S7Jxd|`rSfaBS(|JQXC|&oQpYg+ zB}x8c%8>>%d$D_tNZtKDxI|T7l@m z=#WNgr?Cs1_Kr>_n;|CN+HK=jKzE=wFb~6lsrWH48^Sz4WXTfbeMa{hM>Il>?NQkR zPL4Y=3)1DWXpyJlf2NhA(?my0MaindWir4_2!fkB~%92-=698pE$BUK1~RjHs*OJP!L zP@?a5QC*b zDr_Oop&%V@1cl;X1g6l2BTK8p9Bm|4YfU($Rmhs`HThEu%LuXijb(6)ApNG*N}BAY z#Z&1JY1x6;;BZV0X3$NZt-%Jo8LYz>!3vSn$~r(4+puUiw#cBdolVs|kT#!1){T7nbm>-oUZ{)~tVjr*I3~rG&_q03D8jmMQ($Y8 znX$Fm+V3$)sKeq>5mxS&vQvp8VWA^zQn2H5e`vx3$JmvHC%M_>awPVMV0;%|iR_4E z+zeM^eT0z39*aLQb9A^Fp}{{RN{|t0)jMpiE=LFJJ6+bc@}@EGVXcAs#<|FOFhtG3 zil`FSxUWVv`4PjW)`G{4L6~WX#u|f_lkyvbJ0b{-4d^fy;T2;R{%O=eWJ*D?DH*+{ z$Ov|`IAe3q?rO0WSzXp~lVgKfhufwQI)@=zHMZ#5=t2xeXQG|}Ym+o%19r0ldFBK- z&G|TH&f${PujUFNEALp)nH@8kW`yh{9T8rDPhxbq6VvMN7KW(I85@e@=QOx9RRvpO z@JL=_4FbQ!E<#6KA&$g35EWlaGMztq;tMbwUk2X<5*S&esDymH8lyq{S7IQckRbWO z9xEVBwcsae&>QQI_{1b!jWv3*mNObik0qAix5N=*AN&$68(ZIoq)B)tIRf6v*^uW4 zP-~x)Tu5NlV7aMtbZT5$A}3oXfGJ5%TtSMOlxUXA_iu*@zw;U1RYVe@RP5)FUDo(gV@X_r+)F zS~QUN{$XD%O%JEyZ`>D%6Y0V3aT6U2v;>UoIGc!mpVI3r7FP0|mJYRvY3*QIYz|wj zTl|p*=8R~n<8Or{#IXGUUst`?qI zaY)Vz!L%$Ly0Rj%lf1Ke^PMaPKV&hUY^tozk|QoV9MiH3=@OpF4kGzA0NQ*3rU@3%QBee>N%8tQFgCbv3r!ix}&$&9^<&X#`VfL{32x zPG|Eltmn}wHWcLIodU8`1(}E_P-9|2gka47iFTJ4){O~zB~JvuqBOr|Yn#(XN<6%t zqrjCsAv%hDeYl0=+P5LtQKZFNITA7?!k>%89E0zQ8t`P21lD2(tBONeO?$H&#KW0l z13X$(5@Nl*EQDPUjcv-3iQa7eK}yV$Fx*Xd*QFav^6_p-F#Hr!j`hZpAcU71QC+GE zSlrUuW-~N)IUKeQ7me_JrBT>ZTFTZbKa_@Z6d`5hfoyT?usQ8rjz*h%YAh?W;6zy@ zz9D?>+>kv~dL_GPFl8Xr`JirBSSo0`ZnqN0fR3n!UinIuz=pfYqYI-6PC zVr8Slf=L=W>AFb<9Gx^90o7Aze_?ecj#ua4r|M*Z+pJk-^dhT9O(Ww^lO^Ilqj|*U z8D_o>cdKLn5ZRj|Bjv%2@hJTd295A@qkQmS`gjy(^5~Rql%C0!@hE2|YyN{v(3DKw zy@GS)F)2lgiYtq!7PG~OM`Ae0GTc1ROtEP1OHfvvUvv+;blj?mA2!-H)x?Y!XK3nw zF3NXWR>*z1SJzA}ubwpJo(bIY9GsRBd|wJ$mfeFN+-G#-jri+0*I6`uHm*)@Leh+A ziTl_@PlGj9R}21;F5}!_?Ti>N3bFsF3?#-gb}sF5v>AJO_-aNTj5Cu_KhuDxv zZH@ugkNG0^ya=MTKIos5jHlKMv38jdH|C_e$%RnQt=e=-h&xVy*8d-#n}>^Yjrd`1 zun&zrJbYTGLSkJcX4e_8x=x0t>VjF!;W`bs*iY+9VW^MA%=&z6ug}3p^_fsLSTLob z00ZRts38|xYZ&saO3b$E(PxdueyfH?+2eHq_|h6kUF)tj7kTr-eMt0X3=9^|i@{U# z(%3X4UTEuaj+9(%y83Be4$T93jbZqBw;$#;hSGMGyWQQWr~Py8{+Epw;^}T{pfRJ< z>aeyuN%VuY+2EZ?&x=c%wAk8|g6mCYK8bs5UC9nWXN!~*(`f5rgJ3OvnafcuvZZ0a zEeh}2^yptAgm-fRasE+B$r~x>n_}>%=7N!Gd{c`W-!)78Ia?%A3HkG7VCItxjQI(u zpP%OIHZe3@WAOTX3Er8n#_jnNk=_!4o12x0TNm%i*5ywuh#Fwj9^QDrMT!}%VGy;J zl6Db!bQtCA24mxNg_u+aapQh2CH}3KBFPnmw9q&(NByzZrG>gnj+3qc4+b^eqAgixl9CW`~4=?_|NDSWWag0tP8%b>BI;_TFZ*O(loaC6fIT&}B z`XQkw7S^5=Z0#W>d`}HRmdvFRJh~)`Byc`XFNvka+&4=8rd|WB1had^RPZUiF_^VA z410S+ak*EAuY1FZ=L?}*nuIA!Nzt-23_F$vf4vc(~G17%LDP|at&@RpN+H?F{tjBqi=~gy7?IrH}Ni)+#_ME*F84qVGTl~DT2zjfflgKW@o>k>| zI8h0~>RdeC<&DX!bFgW38ZNJ%fsdC8=-Sg(hNEdsJax3we#CE1uSug){JO@ZX(n29 zuf@!`?Cw`uza>3vRR6&+ks_cgSioW%8tt8}wx(hPtYxT2NnVlS)FUE4a{D$qFwo%d z;d?J6S}+#Ri0QKyM7#m5TNK#-Xc$s9rs3a@s<38b4fIlS)>{{h%E!ZSe4Pbzy7=%| zuciHh^(LC&(g$Ror0mB#ayiN#3uEt&6!>htlyo9-`AKrvdQ3;&XVSsX_#R%k^jI|Q z+z^sbr+eJYc3^)zg|?R-PoQnc6G^mP{6rdUJD*H~--as8*bs%Ck-p^U=ZnWTWYf*O zu{jFY9um?eXg4a!5+)Jxxf!WAs!ahblL7aPdbl{iAI|zRvVS)_w;lt5SD|1t3DDyt zQDe{MFt;>TByE|2^;=?ThCjDOf}2}%-Di+Mqz_0b-h%_N9=&)+C&R!%3WdBoP=MPr zgOE(Tk@d*_tvc-5>O|nSV$9x_jcwZ^5wlB#>)T}1!~T0);DkOiJf3Dqmyf;|$d$x% zLJU^$aLC0c4jP2Nyg;s?h?b8$)d2bSX7p{3!O(Uc{_7PT{=Qv7k7p9`$02QhoX>w zCU8n2Jtch4m~3! z@BqC3j0Jheym0Zk3N%dgrU9q>*~HQMr)HnmDE!aQmVkM#l&yIldQOBFcMGxkxfq-y zq*+JBV0Me~=<_O6>{j9Q^CnE%BCOgcB@_A5BM*ZQ zkC9mRYKkAZxMOGxh<%~JtyZqG`XkkTPHxYY`}OGAtHZZD1!&r@z&f660@3J}{c*Uk zKa+;nHzZyVzew(q33~r<7*@W>R$^RCeEY?!QO(y5MPk)Z0WJ(#FyTNLq7JOU{sXHJ z_tHf4z7#`fc*G)V5qPi`od-j(@!%Bv^S zTMrk}igoH)IVQY3nOKD{=Dr+GwsCA*ndiK5`Q-rIdRa-gR(yn0&)@clk#V$S)B^ek z{r`}5L@jPw?m}xY5I44KNEN`tomaAn7C2c1r{q*4myZ(598sI+u}q?>5Y!(txYecq zf2fMyBYp9z38L4ckoKAdj@L?2b|L~_yhe`L$0Lw&d>VJp^6c?4+&-?q=bY`0*b^$8 zB!tB$8?g7J33pD!QG4Y2GiV%Pl~{bTj*h>0DuV{*?A(#OtU=hYmb!h(a4M;5{K1^o z;F;lM3j282;)Xp)`wyKS!+G~Kha-BOy&>f2x?hi`c>7)_pR3$3uQ#}emG?&aC~W5& zsTA+SH!?>#PdX&l>R_ zX-%8O*hg%JUG{rt%jj~MY%j!}%b}^)buI#*Og7_Af(Rd+lY8}<$uZ!@WI6b61>xx_ z{z!OBi)!+Gyi|_WZ`Du*>7@#CufgrN z6bmoJVaJ6K3}4_<_%9a{5qGg%Og}i<9Ni=LDeErgeJK|kE>+>vOX*O* zZA8)AD{=AdY^uZGR`5Y|CUFwl(E|n>hchq7q3v=Z4qr~e-!7LS@k#=0>o=2#PPCWKycwl3wM-nq$m Date: Thu, 5 Jan 2017 21:31:57 +0200 Subject: [PATCH 37/55] test(coverage): add coverage and coveralls.io integration --- .gitignore | 1 + Gruntfile.js | 19 +++++++++++++++++++ README.md | 1 + karma.conf.js | 17 +++++++++++++++-- package.json | 8 +++++--- 5 files changed, 41 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 79787e35..e8a68eb0 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ results lib node_modules components +coverage bower_components npm-debug.log .idea diff --git a/Gruntfile.js b/Gruntfile.js index d06566eb..b33e7f1c 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -85,6 +85,23 @@ module.exports = function(grunt) { autoWatch: true } }, + coveralls: { + // Options relevant to all targets + options: { + // When true, grunt-coveralls will only print a warning rather than + // an error, to prevent CI builds from failing unnecessarily (e.g. if + // coveralls.io is down). Optional, defaults to false. + force: false + }, + + restangular: { + // LCOV coverage file (can be string, glob or array) + src: 'coverage/**/lcov.info', + options: { + // Any options for just this target + } + }, + }, changelog: { options: { dest: 'CHANGELOG.md' @@ -111,6 +128,8 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-zip'); + grunt.loadNpmTasks('grunt-coveralls'); + // Default task. grunt.registerTask('default', ['build']); diff --git a/README.md b/README.md index 359bf272..73bce6a5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ #Restangular [![Build Status](https://travis-ci.org/mgonto/restangular.svg?branch=master)](https://travis-ci.org/mgonto/restangular) +[![Coverage Status](https://coveralls.io/repos/github/mgonto/restangular/badge.svg?branch=master)](https://coveralls.io/github/mgonto/restangular?branch=master) [![PayPayl donate button](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=martin%40gon%2eto&lc=US&item_name=Martin%20Gontovnikas¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted "Donate once-off to this project using Paypal") [![Donate on Gittip](http://img.shields.io/gittip/mgonto.svg)](https://www.gittip.com/mgonto/) diff --git a/karma.conf.js b/karma.conf.js index 79bde350..096fa462 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -29,7 +29,7 @@ module.exports = function (config) { // test results reporter to use // possible values: 'dots', 'progress', 'junit' - reporters: ['mocha'], + reporters: ['mocha', 'coverage'], // web server port @@ -70,7 +70,20 @@ module.exports = function (config) { // Continuous Integration mode // if true, it capture browsers, run tests and exit - singleRun: false + singleRun: false, + + preprocessors: { + // source files, that you wanna generate coverage for + // do not include tests or libraries + // (these files will be instrumented by Istanbul) + 'src/**/*.js': ['coverage'] + }, + + // optionally, configure the reporter + coverageReporter: { + type: 'lcov', + dir : 'coverage/' + } }); }; diff --git a/package.json b/package.json index 691bc3f6..4d245be3 100644 --- a/package.json +++ b/package.json @@ -37,17 +37,19 @@ "devDependencies": { "angular-mocks": "^1.4.8", "grunt": "^0.4.5", + "grunt-bower": "*", + "grunt-bower-task": "*", "grunt-cli": ">= 0.1.7", "grunt-contrib-concat": "*", "grunt-contrib-jshint": "*", "grunt-contrib-uglify": "*", - "grunt-bower": "*", - "grunt-bower-task": "*", - "grunt-karma": "latest", "grunt-conventional-changelog": "0.0.12", + "grunt-coveralls": "^1.0.1", + "grunt-karma": "latest", "grunt-zip": "*", "karma": "^0.13.19", "karma-chrome-launcher": "~v2.0.0", + "karma-coverage": "^1.1.1", "karma-firefox-launcher": "~v1.0.0", "karma-jasmine": "~0.1.5", "karma-mocha-reporter": "0.2.8", From 58caacd26bfdc5df79d574e6afbec0e079e793a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Thu, 5 Jan 2017 22:27:01 +0200 Subject: [PATCH 38/55] chore(changelog): upgrade package and config --- CHANGELOG.md | 21 ++++++++++++++++++++- Gruntfile.js | 25 +++++++++++++++++++++++-- package.json | 2 +- 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef816d33..bc12d64e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,23 @@ -# Unreleased + +# Unreleased (2017-01-05) + +* test(coverage): add coverage and coveralls.io integration ([fdd5de6](https://github.com/mgonto/restangular/commit/fdd5de6)) +* chore(docs) Add new example production site ([2596035](https://github.com/mgonto/restangular/commit/2596035)) +* chore(docs) Add note about pull requests and github preview tab ([6883075](https://github.com/mgonto/restangular/commit/6883075)) +* chore(docs) Apply automatic formatting to code and spec ([bc16122](https://github.com/mgonto/restangular/commit/bc16122)) +* chore(docs) Reformat changelog, add unreleased section ([8bfa685](https://github.com/mgonto/restangular/commit/8bfa685)) +* chore(docs) Update issue guidelines to include StackOverflow as source for solutions to problems ([34b0e9a](https://github.com/mgonto/restangular/commit/34b0e9a)) +* chore(docs) Update link to demo Plunker, rephrase ([7c30615](https://github.com/mgonto/restangular/commit/7c30615)) +* chore(test) fix jshint errors in spec file ([1a988cb](https://github.com/mgonto/restangular/commit/1a988cb)) +* feat(docs) Add FAQ about cancelling request ([8552c51](https://github.com/mgonto/restangular/commit/8552c51)), closes [#926](https://github.com/mgonto/restangular/issues/926) [#1145](https://github.com/mgonto/restangular/issues/1145) [#1377](https://github.com/mgonto/restangular/issues/1377) [#1391](https://github.com/mgonto/restangular/issues/1391) +* fix(copy) Collections are now copied/cloned properly ([c92b138](https://github.com/mgonto/restangular/commit/c92b138)) +* fix(copy) Copying collections now correctly sets route, fromServer and parent on the copy ([7fd668b](https://github.com/mgonto/restangular/commit/7fd668b)) +* fix(elementTransformer) matchTransformer now doesn't throw if route is undefined ([fb242ae](https://github.com/mgonto/restangular/commit/fb242ae)) +* Update dist files ([7c245a2](https://github.com/mgonto/restangular/commit/7c245a2)) +* chore(dependencies): Update lodash version to ~4.17.0 as in unit tests ([e0b68a0](https://github.com/mgonto/restangular/commit/e0b68a0)) + + + # 1.6.0 (2016-12-25) diff --git a/Gruntfile.js b/Gruntfile.js index b33e7f1c..02bce19f 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -102,9 +102,28 @@ module.exports = function(grunt) { } }, }, - changelog: { + conventionalChangelog: { options: { - dest: 'CHANGELOG.md' + changelogOpts: { + // conventional-changelog options go here + outputUnreleased: true, + // preset: 'angular' + }, + context: { + // context goes here + }, + gitRawCommitsOpts: { + // git-raw-commits options go here + }, + parserOpts: { + // conventional-commits-parser options go here + }, + writerOpts: { + // conventional-changelog-writer options go here + } + }, + release: { + src: 'CHANGELOG.md' } } }); @@ -143,6 +162,8 @@ module.exports = function(grunt) { grunt.registerTask('travis', ['karma:travis', 'karma:travisUnderscore']); + grunt.registerTask('changelog', ['conventionalChangelog']); + // Provides the "bump" task. grunt.registerTask('bump', 'Increment version number', function() { var versionType = grunt.option('type'); diff --git a/package.json b/package.json index 4d245be3..069479e6 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "grunt-contrib-concat": "*", "grunt-contrib-jshint": "*", "grunt-contrib-uglify": "*", - "grunt-conventional-changelog": "0.0.12", + "grunt-conventional-changelog": "latest", "grunt-coveralls": "^1.0.1", "grunt-karma": "latest", "grunt-zip": "*", From ca9856a0c2a3f376ab2417e9f319d30e414b085e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Thu, 5 Jan 2017 22:55:32 +0200 Subject: [PATCH 39/55] chore(travis): change travis script and include coveralls --- .travis.yml | 2 ++ Gruntfile.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ad563755..011f5e65 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,3 +7,5 @@ before_script: - sh -e /etc/init.d/xvfb start - 'npm install -g bower grunt-cli' - 'bower install --config.interactive=false' + +script: grunt travis diff --git a/Gruntfile.js b/Gruntfile.js index 02bce19f..13420589 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -160,7 +160,7 @@ module.exports = function(grunt) { grunt.registerTask('test-debug', ['karma:debug']); - grunt.registerTask('travis', ['karma:travis', 'karma:travisUnderscore']); + grunt.registerTask('travis', ['karma:travis', 'karma:travisUnderscore', 'coveralls']); grunt.registerTask('changelog', ['conventionalChangelog']); From b583197fd069551e3ea2b0d104c1c2d81173fb0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Fri, 6 Jan 2017 00:11:16 +0200 Subject: [PATCH 40/55] chore(deps): upgrade dev dependencies, fix tests (#1450) --- .travis.yml | 2 +- README.md | 1 + package.json | 13 +++++++------ test/restangularSpec.js | 24 ++++++++++++------------ 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/.travis.yml b/.travis.yml index 011f5e65..20b0f596 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: - - "0.10" + - "7.0" before_script: - export DISPLAY=:99.0 diff --git a/README.md b/README.md index 73bce6a5..225ee3cc 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [![Build Status](https://travis-ci.org/mgonto/restangular.svg?branch=master)](https://travis-ci.org/mgonto/restangular) [![Coverage Status](https://coveralls.io/repos/github/mgonto/restangular/badge.svg?branch=master)](https://coveralls.io/github/mgonto/restangular?branch=master) +[![David](https://img.shields.io/david/dev/mgonto/restangular.svg)]() [![PayPayl donate button](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=martin%40gon%2eto&lc=US&item_name=Martin%20Gontovnikas¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted "Donate once-off to this project using Paypal") [![Donate on Gittip](http://img.shields.io/gittip/mgonto.svg)](https://www.gittip.com/mgonto/) diff --git a/package.json b/package.json index 069479e6..d42e5428 100644 --- a/package.json +++ b/package.json @@ -36,10 +36,10 @@ }, "devDependencies": { "angular-mocks": "^1.4.8", - "grunt": "^0.4.5", + "grunt": "^1.0.0", "grunt-bower": "*", "grunt-bower-task": "*", - "grunt-cli": ">= 0.1.7", + "grunt-cli": "^1.2.0", "grunt-contrib-concat": "*", "grunt-contrib-jshint": "*", "grunt-contrib-uglify": "*", @@ -47,12 +47,13 @@ "grunt-coveralls": "^1.0.1", "grunt-karma": "latest", "grunt-zip": "*", - "karma": "^0.13.19", - "karma-chrome-launcher": "~v2.0.0", + "jasmine-core": "^2.5.2", + "karma": "^1.3.0", + "karma-chrome-launcher": "~2.0.0", "karma-coverage": "^1.1.1", "karma-firefox-launcher": "~v1.0.0", - "karma-jasmine": "~0.1.5", - "karma-mocha-reporter": "0.2.8", + "karma-jasmine": "^1.1.0", + "karma-mocha-reporter": "^2.2.0", "karma-phantomjs-launcher": "~v1.0.2" }, "scripts": { diff --git a/test/restangularSpec.js b/test/restangularSpec.js index a6a08221..417b3e33 100644 --- a/test/restangularSpec.js +++ b/test/restangularSpec.js @@ -309,9 +309,9 @@ describe('Restangular', function() { CallbackManager.firstErrorInterceptor = function() {}; CallbackManager.secondErrorInterceptor = function() {}; - spyOn(CallbackManager, 'successCallback').andCallThrough(); - spyOn(CallbackManager, 'firstErrorInterceptor').andCallThrough(); - spyOn(CallbackManager, 'secondErrorInterceptor').andCallThrough(); + spyOn(CallbackManager, 'successCallback').and.callThrough(); + spyOn(CallbackManager, 'firstErrorInterceptor').and.callThrough(); + spyOn(CallbackManager, 'secondErrorInterceptor').and.callThrough(); Restangular.addErrorInterceptor(CallbackManager.firstErrorInterceptor); Restangular.addErrorInterceptor(CallbackManager.secondErrorInterceptor); @@ -341,8 +341,8 @@ describe('Restangular', function() { return false; // prevent promise to be rejected }; - spyOn(CallbackManager, 'successCallback').andCallThrough(); - spyOn(CallbackManager, 'errorCallback').andCallThrough(); + spyOn(CallbackManager, 'successCallback').and.callThrough(); + spyOn(CallbackManager, 'errorCallback').and.callThrough(); Restangular.addErrorInterceptor(CallbackManager.firstErrorInterceptor); Restangular.addErrorInterceptor(CallbackManager.secondErrorInterceptor); @@ -371,9 +371,9 @@ describe('Restangular', function() { }; CallbackManager.secondErrorInterceptor = function() {}; - spyOn(CallbackManager, 'successCallback').andCallThrough(); - spyOn(CallbackManager, 'firstErrorInterceptor').andCallThrough(); - spyOn(CallbackManager, 'secondErrorInterceptor').andCallThrough(); + spyOn(CallbackManager, 'successCallback').and.callThrough(); + spyOn(CallbackManager, 'firstErrorInterceptor').and.callThrough(); + spyOn(CallbackManager, 'secondErrorInterceptor').and.callThrough(); Restangular.addErrorInterceptor(CallbackManager.firstErrorInterceptor); Restangular.addErrorInterceptor(CallbackManager.secondErrorInterceptor); @@ -943,7 +943,7 @@ describe('Restangular', function() { expect(copiedAccount).not.toBe(accountsModel[0]); // create a spy for one of the methods to capture the value of 'this' - spyOn(copiedAccount, 'getRestangularUrl').andCallFake(function() { + spyOn(copiedAccount, 'getRestangularUrl').and.callFake(function() { that = this; }); @@ -957,7 +957,7 @@ describe('Restangular', function() { // with fromServer=true restangularAccount1.get().then(responseHandler); $httpBackend.flush(); - var account = responseHandler.calls[0].args[0], + var account = responseHandler.calls.argsFor(0)[0], copiedAccount = Restangular.copy(account); expect(account.fromServer).toEqual(true); expect(copiedAccount.fromServer).toEqual(true); @@ -981,7 +981,7 @@ describe('Restangular', function() { // with collections, fromServer = true; restangularAccounts.getList().then(responseHandler); $httpBackend.flush(); - accounts = responseHandler.calls[0].args[0], + accounts = responseHandler.calls.argsFor(0)[0], copiedAccounts = Restangular.copy(accounts); expect(accounts.fromServer).toEqual(true); expect(copiedAccounts.fromServer).toEqual(true); @@ -1116,7 +1116,7 @@ describe('Restangular', function() { Restangular.all('accounts').getList().then(responseHandler); $httpBackend.flush(); - var accounts = responseHandler.calls[0].args[0]; + var accounts = responseHandler.calls.argsFor(0)[0]; var accountsCopy = accounts.clone(); expect(accounts.customThing).toEqual('customValue'); From 2bfb745cb7fede8a7cea20ee94a29dc14066b4b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Fri, 6 Jan 2017 00:21:31 +0200 Subject: [PATCH 41/55] fix(docs): add link to david-dm.org from badge --- CHANGELOG.md | 7 ++++--- README.md | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc12d64e..f64d7457 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Unreleased (2017-01-05) +* chore(changelog): upgrade package and config ([58caacd](https://github.com/mgonto/restangular/commit/58caacd)) +* chore(dependencies): Update lodash version to ~4.17.0 as in unit tests ([e0b68a0](https://github.com/mgonto/restangular/commit/e0b68a0)) +* chore(deps): upgrade dev dependencies, fix tests (#1450) ([b583197](https://github.com/mgonto/restangular/commit/b583197)), closes [#1450](https://github.com/mgonto/restangular/issues/1450) +* chore(travis): change travis script and include coveralls ([ca9856a](https://github.com/mgonto/restangular/commit/ca9856a)) * test(coverage): add coverage and coveralls.io integration ([fdd5de6](https://github.com/mgonto/restangular/commit/fdd5de6)) * chore(docs) Add new example production site ([2596035](https://github.com/mgonto/restangular/commit/2596035)) * chore(docs) Add note about pull requests and github preview tab ([6883075](https://github.com/mgonto/restangular/commit/6883075)) @@ -14,9 +18,6 @@ * fix(copy) Copying collections now correctly sets route, fromServer and parent on the copy ([7fd668b](https://github.com/mgonto/restangular/commit/7fd668b)) * fix(elementTransformer) matchTransformer now doesn't throw if route is undefined ([fb242ae](https://github.com/mgonto/restangular/commit/fb242ae)) * Update dist files ([7c245a2](https://github.com/mgonto/restangular/commit/7c245a2)) -* chore(dependencies): Update lodash version to ~4.17.0 as in unit tests ([e0b68a0](https://github.com/mgonto/restangular/commit/e0b68a0)) - - diff --git a/README.md b/README.md index 225ee3cc..cae96082 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Build Status](https://travis-ci.org/mgonto/restangular.svg?branch=master)](https://travis-ci.org/mgonto/restangular) [![Coverage Status](https://coveralls.io/repos/github/mgonto/restangular/badge.svg?branch=master)](https://coveralls.io/github/mgonto/restangular?branch=master) -[![David](https://img.shields.io/david/dev/mgonto/restangular.svg)]() +[![David](https://img.shields.io/david/dev/mgonto/restangular.svg)](https://david-dm.org/mgonto/restangular/?type=dev) [![PayPayl donate button](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=martin%40gon%2eto&lc=US&item_name=Martin%20Gontovnikas¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted "Donate once-off to this project using Paypal") [![Donate on Gittip](http://img.shields.io/gittip/mgonto.svg)](https://www.gittip.com/mgonto/) From c49ca45bc9d8731086a9195a1d99d02a6ffc1e4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Fri, 6 Jan 2017 23:13:16 +0200 Subject: [PATCH 42/55] chore(docs): update contribution guidelines --- CONTRIBUTING.md | 69 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 22 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9722aac4..59451f29 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,42 +1,67 @@ -#Issues +# Issues -## Opening an Issue +Read the [issue guidlines](.github/ISSUE_TEMPLATE.md) before opening an issue! -To open an issue, please keep in mind a few important things. First, take a look at the README docs and make sure that your question isn't already answered in the documentation. In particular, review [the configuration methods](https://github.com/mgonto/restangular#configuring-restangular) and [methods description](https://github.com/mgonto/restangular#methods-description). Then make sure you search the issues list to see if there's already an issue open that solves your problem. Then, once you've determined that your issue isn't a duplicate, here a couple guidelines to opening an issue that will be addressed easily and quickly: +# How to submit PRs -- Please make sure your issue is written in a clear and succint manner, so that all the details of your issue are easy to understand and fully explained. Also, please make sure enclose your code in [fenced blocks](https://help.github.com/articles/creating-and-highlighting-code-blocks/) for readability. -- Make sure your issue includes a live Plunker (fork [this Plunker](http://plnkr.co/edit/26Heuv5F6hUgxpNWNTee?p=info)), and all relevant code samples (as well as information about server responses, if relevant) +## Install env -# PRs - -##Install env - -In order to contribute just git clone the repository and then run: +In order to contribute just create your own [fork](https://help.github.com/articles/fork-a-repo/) +of the repository and then run: ``` -git clone git@github.com:mgonto/restangular.git +git clone git@github.com:/restangular.git cd restangular npm install grunt-cli --global npm install ``` -Be sure to have PhantomJS installed as Karma tests use it. Otherwise, in Mac just run +## Create a branch -``` -brew install phantomjs -``` +Create a branch for your code changes -All changes must be done in src/restangular.js +`git checkout -b my_awesome_fix` -##Branching +## Write tests -Please submit a Pull Request or create issues for anything you want :). If your code is super small, nobody will blame -you for doing it from master to master. Otherwise, please try to create a branch `features/[name of your awesome feature]`. +When making changes to the code, please always **write a test case** for +your topic before making the change. Watch the test fail, then +implement the change and watch the test succeed. -##Testing and styling +Tests are run with `grunt test`. -Before submitting any PR, you should run `grunt test` to validate your didn't break anything. If you just added a new -feature, please also add tests for it. And when you're done with your code, run `grunt jshint` to check +## Keep the style + +When you're done with your code, run `grunt jshint` to check if you code follow the same simple coding design as the rest of the project. +Consider integrating `jshint` in your editor to get real time feedback on your +style as you're coding. + +## Commit message format + +Please write your commit messages in the [angular conventional changelog](https://github.com/conventional-changelog/conventional-changelog-angular/blob/master/convention.md) format. This will help +us to keep a decent [CHANGELOG](CHANGELOG.md) with minimum effort. Check previous commit +messages for examples. + +## Update docs + +If your code change includes new features, please include an update to the [README.md](README.md) +explaining your feature. + +**Don't**, however, generate distribution files (the files in [dist](dist)). This will be done +on a regular basis by maintainers as PRs are merged. + +## Squash commits + +Please consider squasing the commits in your topic branch into a single commit including +all changes needed. This will make the PR cleaner and the change history more easy to follow +after the PR has been merged. Also, the CHANGELOG will make more sense. + +Look [here](https://ariejan.net/2011/07/05/git-squash-your-latests-commits-into-one/) and +[here](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Squashing-Commits) for how to. + +## Sumbit the PR + +Now you're ready to [open a PR](https://help.github.com/articles/creating-a-pull-request-from-a-fork/). Thanks! From 7155bc78e838737e88a7588efbbe578a78bddec6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Fri, 6 Jan 2017 23:22:45 +0200 Subject: [PATCH 43/55] chore(release): release version 1.6.1 --- CHANGELOG.md | 42 ++++++++++++++++++++++++++-------------- dist/restangular.js | 2 +- dist/restangular.min.js | 2 +- dist/restangular.zip | Bin 74515 -> 74515 bytes package.json | 4 ++-- 5 files changed, 31 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f64d7457..b768c53e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,16 @@ - -# Unreleased (2017-01-05) + +## 1.6.1 (2017-01-06) -* chore(changelog): upgrade package and config ([58caacd](https://github.com/mgonto/restangular/commit/58caacd)) -* chore(dependencies): Update lodash version to ~4.17.0 as in unit tests ([e0b68a0](https://github.com/mgonto/restangular/commit/e0b68a0)) -* chore(deps): upgrade dev dependencies, fix tests (#1450) ([b583197](https://github.com/mgonto/restangular/commit/b583197)), closes [#1450](https://github.com/mgonto/restangular/issues/1450) -* chore(travis): change travis script and include coveralls ([ca9856a](https://github.com/mgonto/restangular/commit/ca9856a)) -* test(coverage): add coverage and coveralls.io integration ([fdd5de6](https://github.com/mgonto/restangular/commit/fdd5de6)) +### Bug fixes + +* fix(copy) Collections are now copied/cloned properly ([c92b138](https://github.com/mgonto/restangular/commit/c92b138)) +* fix(copy) Copying collections now correctly sets route, fromServer and parent on the copy ([7fd668b](https://github.com/mgonto/restangular/commit/7fd668b)) +* fix(elementTransformer) matchTransformer now doesn't throw if route is undefined ([fb242ae](https://github.com/mgonto/restangular/commit/fb242ae)) + +### Docs + +* chore(docs): update contribution guidelines ([c49ca45](https://github.com/mgonto/restangular/commit/c49ca45)) +* fix(docs): add link to david-dm.org from badge ([2bfb745](https://github.com/mgonto/restangular/commit/2bfb745)) * chore(docs) Add new example production site ([2596035](https://github.com/mgonto/restangular/commit/2596035)) * chore(docs) Add note about pull requests and github preview tab ([6883075](https://github.com/mgonto/restangular/commit/6883075)) * chore(docs) Apply automatic formatting to code and spec ([bc16122](https://github.com/mgonto/restangular/commit/bc16122)) @@ -14,14 +19,18 @@ * chore(docs) Update link to demo Plunker, rephrase ([7c30615](https://github.com/mgonto/restangular/commit/7c30615)) * chore(test) fix jshint errors in spec file ([1a988cb](https://github.com/mgonto/restangular/commit/1a988cb)) * feat(docs) Add FAQ about cancelling request ([8552c51](https://github.com/mgonto/restangular/commit/8552c51)), closes [#926](https://github.com/mgonto/restangular/issues/926) [#1145](https://github.com/mgonto/restangular/issues/1145) [#1377](https://github.com/mgonto/restangular/issues/1377) [#1391](https://github.com/mgonto/restangular/issues/1391) -* fix(copy) Collections are now copied/cloned properly ([c92b138](https://github.com/mgonto/restangular/commit/c92b138)) -* fix(copy) Copying collections now correctly sets route, fromServer and parent on the copy ([7fd668b](https://github.com/mgonto/restangular/commit/7fd668b)) -* fix(elementTransformer) matchTransformer now doesn't throw if route is undefined ([fb242ae](https://github.com/mgonto/restangular/commit/fb242ae)) -* Update dist files ([7c245a2](https://github.com/mgonto/restangular/commit/7c245a2)) +### Other +* chore(changelog): upgrade package and config ([58caacd](https://github.com/mgonto/restangular/commit/58caacd)) +* chore(dependencies): Update lodash version to ~4.17.0 as in unit tests ([e0b68a0](https://github.com/mgonto/restangular/commit/e0b68a0)) +* chore(deps): upgrade dev dependencies, fix tests (#1450) ([b583197](https://github.com/mgonto/restangular/commit/b583197)), closes [#1450](https://github.com/mgonto/restangular/issues/1450) +* chore(travis): change travis script and include coveralls ([ca9856a](https://github.com/mgonto/restangular/commit/ca9856a)) +* test(coverage): add coverage and coveralls.io integration ([fdd5de6](https://github.com/mgonto/restangular/commit/fdd5de6)) +* Update dist files ([7c245a2](https://github.com/mgonto/restangular/commit/7c245a2)) -# 1.6.0 (2016-12-25) + +## 1.6.0 (2016-12-25) * Url now supports unescaped suffix (0350bcd) * Added Restangular Plunkr example (c4ef002) @@ -43,11 +52,13 @@ * Added context/explanation of when to use JSONP. (fec9b27) * Add regexp matching for route to element transformers (#1430) (de8f561) -# 1.5.2 (2016-02-15) + +## 1.5.2 (2016-02-15) * Change \_.contains to \_.includes for compatability with lodash >= 4.0 -# 1.5.1 (2015-04-03) + +## 1.5.1 (2015-04-03) * Release 1.5.0 * Updated zip @@ -59,7 +70,8 @@ * Update README.md * Update README.md -# 1.5.0 (2015-04-03) + +## 1.5.0 (2015-04-03) * Tons of bug fixes * Upgraded Lodash to 1.3.0 diff --git a/dist/restangular.js b/dist/restangular.js index f014fd39..b59b25f9 100644 --- a/dist/restangular.js +++ b/dist/restangular.js @@ -1,6 +1,6 @@ /** * Restful Resources service for AngularJS apps - * @version v1.6.0 - 2017-01-05 * @link https://github.com/mgonto/restangular + * @version v1.6.1 - 2017-01-06 * @link https://github.com/mgonto/restangular * @author Martin Gontovnikas * @license MIT License, http://www.opensource.org/licenses/MIT */(function(root, factory) { diff --git a/dist/restangular.min.js b/dist/restangular.min.js index 67d3cfc0..729f8aad 100644 --- a/dist/restangular.min.js +++ b/dist/restangular.min.js @@ -1,6 +1,6 @@ /** * Restful Resources service for AngularJS apps - * @version v1.6.0 - 2017-01-05 * @link https://github.com/mgonto/restangular + * @version v1.6.1 - 2017-01-06 * @link https://github.com/mgonto/restangular * @author Martin Gontovnikas * @license MIT License, http://www.opensource.org/licenses/MIT */!function(a,b){"function"==typeof define&&define.amd?define(["lodash","angular"],b):"object"==typeof module&&module.exports?module.exports=b(require("lodash"),require("angular")):b(a._,a.angular)}(this,function(a,b){var c=b.module("restangular",[]);return c.provider("Restangular",function(){var c={};c.init=function(c,d){function e(b,c,d,e){var f={};return a.each(a.keys(e),function(g){var h=e[g];h.params=a.extend({},h.params,b.defaultRequestParams[h.method.toLowerCase()]),a.isEmpty(h.params)&&delete h.params,b.isSafe(h.method)?f[g]=function(){return c(a.extend(h,{url:d}))}:f[g]=function(b){return c(a.extend(h,{url:d,data:b}))}}),f}c.configuration=d;var f=["get","head","options","trace","getlist"];d.isSafe=function(b){return a.includes(f,b.toLowerCase())};var g=/^https?:\/\//i;d.isAbsoluteUrl=function(b){return a.isUndefined(d.absoluteUrl)||a.isNull(d.absoluteUrl)?b&&g.test(b):d.absoluteUrl},d.absoluteUrl=!!a.isUndefined(d.absoluteUrl)||d.absoluteUrl,c.setSelfLinkAbsoluteUrl=function(a){d.absoluteUrl=a},d.baseUrl=a.isUndefined(d.baseUrl)?"":d.baseUrl,c.setBaseUrl=function(a){return d.baseUrl=/\/$/.test(a)?a.substring(0,a.length-1):a,this},d.extraFields=d.extraFields||[],c.setExtraFields=function(a){return d.extraFields=a,this},d.defaultHttpFields=d.defaultHttpFields||{},c.setDefaultHttpFields=function(a){return d.defaultHttpFields=a,this},d.plainByDefault=d.plainByDefault||!1,c.setPlainByDefault=function(a){return d.plainByDefault=a===!0,this},d.withHttpValues=function(b,c){return a.defaults(c,b,d.defaultHttpFields)},d.encodeIds=!!a.isUndefined(d.encodeIds)||d.encodeIds,c.setEncodeIds=function(a){d.encodeIds=a},d.defaultRequestParams=d.defaultRequestParams||{get:{},post:{},put:{},remove:{},common:{}},c.setDefaultRequestParams=function(b,c){var e=[],f=c||b;return a.isUndefined(c)?e.push("common"):a.isArray(b)?e=b:e.push(b),a.each(e,function(a){d.defaultRequestParams[a]=f}),this},c.requestParams=d.defaultRequestParams,d.defaultHeaders=d.defaultHeaders||{},c.setDefaultHeaders=function(a){return d.defaultHeaders=a,c.defaultHeaders=d.defaultHeaders,this},c.defaultHeaders=d.defaultHeaders,d.methodOverriders=d.methodOverriders||[],c.setMethodOverriders=function(b){var c=a.extend([],b);return d.isOverridenMethod("delete",c)&&c.push("remove"),d.methodOverriders=c,this},d.jsonp=!a.isUndefined(d.jsonp)&&d.jsonp,c.setJsonp=function(a){d.jsonp=a},d.isOverridenMethod=function(b,c){var e=c||d.methodOverriders;return!a.isUndefined(a.find(e,function(a){return a.toLowerCase()===b.toLowerCase()}))},d.urlCreator=d.urlCreator||"path",c.setUrlCreator=function(b){if(!a.has(d.urlCreatorFactory,b))throw new Error("URL Path selected isn't valid");return d.urlCreator=b,this},d.restangularFields=d.restangularFields||{id:"id",route:"route",parentResource:"parentResource",restangularCollection:"restangularCollection",cannonicalId:"__cannonicalId",etag:"restangularEtag",selfLink:"href",get:"get",getList:"getList",put:"put",post:"post",remove:"remove",head:"head",trace:"trace",options:"options",patch:"patch",getRestangularUrl:"getRestangularUrl",getRequestedUrl:"getRequestedUrl",putElement:"putElement",addRestangularMethod:"addRestangularMethod",getParentList:"getParentList",clone:"clone",ids:"ids",httpConfig:"_$httpConfig",reqParams:"reqParams",one:"one",all:"all",several:"several",oneUrl:"oneUrl",allUrl:"allUrl",customPUT:"customPUT",customPATCH:"customPATCH",customPOST:"customPOST",customDELETE:"customDELETE",customGET:"customGET",customGETLIST:"customGETLIST",customOperation:"customOperation",doPUT:"doPUT",doPATCH:"doPATCH",doPOST:"doPOST",doDELETE:"doDELETE",doGET:"doGET",doGETLIST:"doGETLIST",fromServer:"fromServer",withConfig:"withConfig",withHttpConfig:"withHttpConfig",singleOne:"singleOne",plain:"plain",save:"save",restangularized:"restangularized"},c.setRestangularFields=function(b){return d.restangularFields=a.extend(d.restangularFields,b),this},d.isRestangularized=function(a){return!!a[d.restangularFields.restangularized]},d.setFieldToElem=function(b,c,d){var e=b.split("."),f=c;return a.each(a.initial(e),function(a){f[a]={},f=f[a]}),f[a.last(e)]=d,this},d.getFieldFromElem=function(c,d){var e=c.split("."),f=d;return a.each(e,function(a){f&&(f=f[a])}),b.copy(f)},d.setIdToElem=function(a,b){return d.setFieldToElem(d.restangularFields.id,a,b),this},d.getIdFromElem=function(a){return d.getFieldFromElem(d.restangularFields.id,a)},d.isValidId=function(b){return""!==b&&!a.isUndefined(b)&&!a.isNull(b)},d.setUrlToElem=function(a,b){return d.setFieldToElem(d.restangularFields.selfLink,a,b),this},d.getUrlFromElem=function(a){return d.getFieldFromElem(d.restangularFields.selfLink,a)},d.useCannonicalId=!a.isUndefined(d.useCannonicalId)&&d.useCannonicalId,c.setUseCannonicalId=function(a){return d.useCannonicalId=a,this},d.getCannonicalIdFromElem=function(a){var b=a[d.restangularFields.cannonicalId],c=d.isValidId(b)?b:d.getIdFromElem(a);return c},d.responseInterceptors=d.responseInterceptors||[],d.defaultResponseInterceptor=function(a){return a},d.responseExtractor=function(c,e,f,g,h,i){var j=b.copy(d.responseInterceptors);j.push(d.defaultResponseInterceptor);var k=c;return a.each(j,function(a){k=a(k,e,f,g,h,i)}),k},c.addResponseInterceptor=function(a){return d.responseInterceptors.push(a),this},d.errorInterceptors=d.errorInterceptors||[],c.addErrorInterceptor=function(a){return d.errorInterceptors.push(a),this},c.setResponseInterceptor=c.addResponseInterceptor,c.setResponseExtractor=c.addResponseInterceptor,c.setErrorInterceptor=c.addErrorInterceptor,d.requestInterceptors=d.requestInterceptors||[],d.defaultInterceptor=function(a,b,c,d,e,f,g){return{element:a,headers:e,params:f,httpConfig:g}},d.fullRequestInterceptor=function(c,e,f,g,h,i,j){var k=b.copy(d.requestInterceptors),l=d.defaultInterceptor(c,e,f,g,h,i,j);return a.reduce(k,function(b,c){return a.extend(b,c(b.element,e,f,g,b.headers,b.params,b.httpConfig))},l)},c.addRequestInterceptor=function(a){return d.requestInterceptors.push(function(b,c,d,e,f,g,h){return{headers:f,params:g,element:a(b,c,d,e),httpConfig:h}}),this},c.setRequestInterceptor=c.addRequestInterceptor,c.addFullRequestInterceptor=function(a){return d.requestInterceptors.push(a),this},c.setFullRequestInterceptor=c.addFullRequestInterceptor,d.onBeforeElemRestangularized=d.onBeforeElemRestangularized||function(a){return a},c.setOnBeforeElemRestangularized=function(a){return d.onBeforeElemRestangularized=a,this},c.setRestangularizePromiseInterceptor=function(a){return d.restangularizePromiseInterceptor=a,this},d.onElemRestangularized=d.onElemRestangularized||function(a){return a},c.setOnElemRestangularized=function(a){return d.onElemRestangularized=a,this},d.shouldSaveParent=d.shouldSaveParent||function(){return!0},c.setParentless=function(b){return a.isArray(b)?d.shouldSaveParent=function(c){return!a.includes(b,c)}:a.isBoolean(b)&&(d.shouldSaveParent=function(){return!b}),this},d.suffix=a.isUndefined(d.suffix)?null:d.suffix,c.setRequestSuffix=function(a){return d.suffix=a,this},d.transformers=d.transformers||{},d.matchTransformers=d.matchTransformers||[],c.addElementTransformer=function(b,e,f){var g=null,h=null;2===arguments.length?h=e:(h=f,g=e);var i=function(b,c){return a.isNull(g)||b===g?h(c):c};return a.isRegExp(b)?d.matchTransformers.push({regexp:b,transformer:i}):(d.transformers[b]||(d.transformers[b]=[]),d.transformers[b].push(i)),c},c.extendCollection=function(a,b){return c.addElementTransformer(a,!0,b)},c.extendModel=function(a,b){return c.addElementTransformer(a,!1,b)},d.transformElem=function(b,c,e,f,g){if(!g&&!d.transformLocalElements&&!b[d.restangularFields.fromServer])return b;var h=b,i=d.matchTransformers;i&&a.each(i,function(a){a.regexp.test(e)&&(h=a.transformer(c,h))});var j=d.transformers[e];return j&&a.each(j,function(a){h=a(c,h)}),d.onElemRestangularized(h,c,e,f)},d.transformLocalElements=!a.isUndefined(d.transformLocalElements)&&d.transformLocalElements,c.setTransformOnlyServerElements=function(a){d.transformLocalElements=!a},d.fullResponse=!a.isUndefined(d.fullResponse)&&d.fullResponse,c.setFullResponse=function(a){return d.fullResponse=a,this},d.urlCreatorFactory={};var h=function(){};h.prototype.setConfig=function(a){return this.config=a,this},h.prototype.parentsArray=function(a){for(var b=[];a;)b.push(a),a=a[this.config.restangularFields.parentResource];return b.reverse()},h.prototype.resource=function(b,c,f,g,h,i,j,k){var l=a.defaults(h||{},this.config.defaultRequestParams.common),m=a.defaults(g||{},this.config.defaultHeaders);j&&(d.isSafe(k)?m["If-None-Match"]=j:m["If-Match"]=j);var n=this.base(b);if(i||0===i){var o="";/\/$/.test(n)||(o+="/"),o+=i,n+=o}return this.config.suffix&&n.indexOf(this.config.suffix,n.length-this.config.suffix.length)===-1&&!this.config.getUrlFromElem(b)&&(n+=this.config.suffix),b[this.config.restangularFields.httpConfig]=void 0,e(this.config,c,n,{getList:this.config.withHttpValues(f,{method:"GET",params:l,headers:m}),get:this.config.withHttpValues(f,{method:"GET",params:l,headers:m}),jsonp:this.config.withHttpValues(f,{method:"jsonp",params:l,headers:m}),put:this.config.withHttpValues(f,{method:"PUT",params:l,headers:m}),post:this.config.withHttpValues(f,{method:"POST",params:l,headers:m}),remove:this.config.withHttpValues(f,{method:"DELETE",params:l,headers:m}),head:this.config.withHttpValues(f,{method:"HEAD",params:l,headers:m}),trace:this.config.withHttpValues(f,{method:"TRACE",params:l,headers:m}),options:this.config.withHttpValues(f,{method:"OPTIONS",params:l,headers:m}),patch:this.config.withHttpValues(f,{method:"PATCH",params:l,headers:m})})};var i=function(){};i.prototype=new h,i.prototype.normalizeUrl=function(a){var b=/((?:http[s]?:)?\/\/)?(.*)?/.exec(a);return b[2]=b[2].replace(/[\\\/]+/g,"/"),"undefined"!=typeof b[1]?b[1]+b[2]:b[2]},i.prototype.base=function(b){var c=this;return a.reduce(this.parentsArray(b),function(a,b){var e,f=c.config.getUrlFromElem(b);if(f){if(c.config.isAbsoluteUrl(f))return f;e=f}else if(e=b[c.config.restangularFields.route],b[c.config.restangularFields.restangularCollection]){var g=b[c.config.restangularFields.ids];g&&(e+="/"+g.join(","))}else{var h;h=c.config.useCannonicalId?c.config.getCannonicalIdFromElem(b):c.config.getIdFromElem(b),d.isValidId(h)&&!b.singleOne&&(e+="/"+(c.config.encodeIds?encodeURIComponent(h):h))}return a=a.replace(/\/$/,"")+"/"+e,c.normalizeUrl(a)},this.config.baseUrl)},i.prototype.fetchUrl=function(a,b){var c=this.base(a);return b&&(c+="/"+b),c},i.prototype.fetchRequestedUrl=function(a,c){function e(a){var b=[];for(var c in a)a.hasOwnProperty(c)&&b.push(c);return b.sort()}function f(a,b,c){for(var d=e(a),f=0;fh;6u!-k1MDSQSBx(Y@HhUU5khPno(lYP!F*sQ|1 q>MRFH&GAnULMC53mw~2ev(I_+V6Xy^A=A5+7%kz-r=L+`bO!)T@F;Tt diff --git a/package.json b/package.json index d42e5428..8f2bdd21 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "restangular", "description": "Restful Resources service for AngularJS apps", - "version": "1.6.0", + "version": "1.6.1", "filename": "restangular.min.js", "main": "./dist/restangular.js", "homepage": "https://github.com/mgonto/restangular", @@ -60,4 +60,4 @@ "test": "grunt test --verbose" }, "license": "MIT" -} +} \ No newline at end of file From 8ef2c2cc5de8f6b05ca1aad934189c5830d6b2ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Fri, 6 Jan 2017 23:29:23 +0200 Subject: [PATCH 44/55] chore(docs): update maintenance docs, add release workflow --- MAINTENANCE.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 MAINTENANCE.md diff --git a/MAINTENANCE.md b/MAINTENANCE.md new file mode 100644 index 00000000..6d219897 --- /dev/null +++ b/MAINTENANCE.md @@ -0,0 +1,45 @@ +# Restangular maintenance policy + +Restangular follows the [Semantic Versioning](http://semver.org/) for its releases: +`(Major).(Minor).(Patch)`. + +- **Patch number**: When backwards compatible bug fixes are introduced that fix + incorrect behavior. +- **Minor version**: When new, backwards compatible functionality is introduced + to the public API or a minor feature is introduced, or when a set of smaller + features is rolled out. +- **Major version**: Whenever there is something significant or any backwards + incompatible changes are introduced to the public API. + +The current stable release will receive security patches and bug fixes +(eg. `1.6.0` -> `1.6.1`). Feature releases will mark the next supported stable +release where the minor version is increased numerically by increments of one +(eg. `1.6.3 -> 1.7.0`). + +We encourage everyone to run the latest stable release. + +# Notes for maintainers + +Following are a set of guidelines and checklists for maintaining the +[main repository](https://github.com/mgonto/restangular). + +## Keeping master usable + +In order for developers to use the bleeding edge master version in their projects, +we'll need to keep the dist files up to date. After merging PRs that include mentionable +changes, please update the dist files `grunt build` and the CHANGELOG `grunt changelog`. +The CHANGELOG will have a section called `Unreleased` for changes not yet included in +any release. This is the place for + +## Releasing + +Follow this checklist for publishing a new release. + +- [ ] Bump version `grunt bump --type=patch/minor/major` +- [ ] Create changelog `grunt changelog` (open it in editor and make sure its sensible) +- [ ] Make dist files `grunt build` +- [ ] Commit changes `git add dist package.json CHANGELOG.md` and `git commit -m "chore(release): release 1.6.1"` +- [ ] Tag release `git tag -a -m "Version 1.6.1" 1.6.1` +- [ ] Push everything `git push` and `git push --tags` +- [ ] Publish to NPM `npm publish` +- [ ] Create a [new release](https://github.com/mgonto/restangular/releases) on GitHub, entering the version changelog as body From 28347b102effccffabad60ebc99da50b907d4871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Sat, 7 Jan 2017 17:50:08 +0200 Subject: [PATCH 45/55] chore(test): refactor spec file --- test/restangularSpec.js | 858 ++++++++++++++++++++-------------------- 1 file changed, 431 insertions(+), 427 deletions(-) diff --git a/test/restangularSpec.js b/test/restangularSpec.js index 417b3e33..de6a9662 100644 --- a/test/restangularSpec.js +++ b/test/restangularSpec.js @@ -1,168 +1,211 @@ -/* global describe, beforeEach, inject, afterEach, it, expect, spyOn, jasmine */ +/* global describe, beforeEach, afterEach, it, expect, spyOn, jasmine */ /* jshint unused: false */ -describe('Restangular', function() { +describe('Restangular', function () { // API - var Restangular, $httpBackend, - accountsModel, restangularAccounts, restangularAccount0, restangularAccount1, - infoModel, accountsDoSomethingModel, customers, publications, newCustomer, - accountsHalModel, - messages, newAccount, nextAccountId; + var Restangular, $httpBackend, testData, + restangularAccounts, restangularAccount0, restangularAccount1; // Load required modules - beforeEach(angular.mock.module('restangular')); - - // Init HTTP mock backend and Restangular resources - beforeEach(inject(function($injector) { - // Model - accountsModel = [{ - id: 0, - user: 'Martin ', - amount: 42, - transactions: [] - }, { - id: 1, - user: 'Paul', - amount: 3.1416, - transactions: [{ - from: 'Martin', - amount: 3, - id: 0 + beforeEach(function () { + // Load restangular module + angular.mock.module('restangular'); + + // Get references to modules from the injector + angular.mock.inject(function ($injector) { + $httpBackend = $injector.get('$httpBackend'); + Restangular = $injector.get('Restangular'); + }); + + // Restangularize a few demo accounts + restangularAccounts = Restangular.all('accounts'); + restangularAccount0 = Restangular.one('accounts', 0); + restangularAccount1 = Restangular.one('accounts', 1); + + // Create testdata for our tests + testData = { + // Model + accountsModel: [{ + id: 0, + user: 'Martin ', + amount: 42, + transactions: [] }, { - from: 'Anonymous', - amount: 0.1416, - id: 1 - }] - }]; - nextAccountId = 22; - - // HAL model (http://stateless.co/hal_specification.html) - accountsHalModel = [{ - id: 0, - user: 'Martin', - amount: 42, - transaction: [], - _links: { - self: '/accountsHAL/martin' - } - }, { - id: 1, - user: 'Paul', - amount: 3.1416, - transaction: [{ - from: 'Martin', - amount: 3, + id: 1, + user: 'Paul', + amount: 3.1416, + transactions: [{ + from: 'Martin', + amount: 3, + id: 0 + }, { + from: 'Anonymous', + amount: 0.1416, + id: 1 + }] + }], + + nextAccountId: 22, + + // HAL model (http://stateless.co/hal_specification.html) + accountsHalModel: [{ id: 0, + user: 'Martin', + amount: 42, + transaction: [], _links: { - self: '/accountsHAL/paul/transactions/0' + self: '/accountsHAL/martin' } }, { - from: 'Anonymous', - amount: 0.1416, id: 1, + user: 'Paul', + amount: 3.1416, + transaction: [{ + from: 'Martin', + amount: 3, + id: 0, + _links: { + self: '/accountsHAL/paul/transactions/0' + } + }, { + from: 'Anonymous', + amount: 0.1416, + id: 1, + _links: { + self: '/accountsHAL/paul/transactions/1' + } + }], _links: { - self: '/accountsHAL/paul/transactions/1' + self: '/accountsHAL/paul' } }], - _links: { - self: '/accountsHAL/paul' - } - }]; - infoModel = { - id: 0, - text: 'Some additional account information' - }; + infoModel: { + id: 0, + text: 'Some additional account information' + }, - newAccount = { - user: 'First User', - amount: 45, - transactions: [] - }; + newAccount: { + user: 'First User', + amount: 45, + transactions: [] + }, + + messages: [{ + id: 23, + name: 'Gonto' + }, { + id: 45, + name: 'John' + }], - messages = [{ - id: 23, - name: 'Gonto' - }, { - id: 45, - name: 'John' - }]; + accountsDoSomethingModel: { + result: 1 + }, - accountsDoSomethingModel = { - result: 1 + // Another API for testing + customers: [{ + id: 0, + name: 'Alice', + status: 'active', + credit: 4000.0 + }, { + id: 1, + name: 'Bob', + status: 'active', + credit: 4000.0 + }, { + id: 2, + name: 'Carl', + status: 'active', + credit: 4000.0 + }], + publications: [{ + id: 1, + title: 'Sample', + content: 'Rich data', + tags: [ + 'science', + 'chemistry' + ] + }], + newCustomer: { + id: 3, + name: 'New', + status: 'active', + credit: 4000.0 + } }; - $httpBackend = $injector.get('$httpBackend'); + // Set up backend responses $httpBackend.when('HEAD', '/accounts').respond(); $httpBackend.when('TRACE', '/accounts').respond(); $httpBackend.when('OPTIONS', '/accounts').respond(); - $httpBackend.whenGET('/accounts').respond(accountsModel); - $httpBackend.whenGET('/accounts/do-something').respond(accountsDoSomethingModel); - $httpBackend.whenJSONP('/accounts').respond(accountsModel); - $httpBackend.whenGET('/accounts/0,1').respond(accountsModel); - $httpBackend.whenGET('/accounts/messages').respond(messages); - $httpBackend.whenGET('/accounts/1/message').respond(messages[0]); - $httpBackend.whenGET('/accounts/1/messages').respond(messages); - $httpBackend.whenGET('/accounts/0').respond(accountsModel[0]); - $httpBackend.whenGET('/accounts/1').respond(accountsModel[1]); - $httpBackend.whenJSONP('/accounts/1').respond(accountsModel[1]); - $httpBackend.whenGET('/accounts/1/transactions').respond(accountsModel[1].transactions); - $httpBackend.whenGET('/accounts/1/transactions/1').respond(accountsModel[1].transactions[1]); - $httpBackend.whenGET('/accounts/search/byOwner').respond(accountsModel); - - $httpBackend.whenGET('/info').respond(infoModel); - $httpBackend.whenGET('/accounts/1/info').respond(infoModel); - $httpBackend.whenPUT('/info').respond(function(method, url, data) { - return [200, data, '']; - }); - - $httpBackend.whenGET('/accountsHAL').respond(accountsHalModel); - $httpBackend.whenPUT('/accountsHAL/martin').respond(function(method, url, data) { - accountsHalModel[0] = angular.fromJson(data); - return [200, data, '']; - }); - - // Full URL - $httpBackend.whenGET('http://accounts.com/all').respond(accountsModel); - - $httpBackend.whenPOST('/accounts').respond(function(method, url, data, headers) { + // CRUD /accounts + $httpBackend.whenGET('/accounts').respond(testData.accountsModel); + $httpBackend.whenJSONP('/accounts').respond(testData.accountsModel); + $httpBackend.whenPOST('/accounts').respond(function (method, url, data, headers) { var newData = angular.fromJson(data); newData.fromServer = true; - newData.id = nextAccountId; + newData.id = testData.nextAccountId; return [201, JSON.stringify(newData), '']; }); - - $httpBackend.whenPOST('/accounts/1/transactions').respond(function(method, url, data, headers) { + $httpBackend.whenGET('/accounts/do-something').respond(testData.accountsDoSomethingModel); + $httpBackend.whenGET('/accounts/search/byOwner').respond(testData.accountsModel); + + // CRUD /accounts/{id} + $httpBackend.whenGET('/accounts/0,1').respond(testData.accountsModel); + $httpBackend.whenGET('/accounts/messages').respond(testData.messages); + $httpBackend.whenGET('/accounts/1/message').respond(testData.messages[0]); + $httpBackend.whenGET('/accounts/1/messages').respond(testData.messages); + $httpBackend.whenGET('/accounts/0').respond(testData.accountsModel[0]); + $httpBackend.whenGET('/accounts/1').respond(testData.accountsModel[1]); + $httpBackend.whenJSONP('/accounts/1').respond(testData.accountsModel[1]); + $httpBackend.whenGET('/accounts/1/transactions').respond(testData.accountsModel[1].transactions); + $httpBackend.whenGET('/accounts/1/transactions/1').respond(testData.accountsModel[1].transactions[1]); + $httpBackend.whenPOST('/accounts/1/transactions').respond(function (method, url, data, headers) { return [201, '', '']; }); - - $httpBackend.whenDELETE('/accounts/1/transactions/1').respond(function(method, url, data, headers) { + $httpBackend.whenDELETE('/accounts/1/transactions/1').respond(function (method, url, data, headers) { return [200, '', '']; }); - - $httpBackend.whenDELETE('/accounts/1').respond(function(method, url, data, headers) { + $httpBackend.whenDELETE('/accounts/1').respond(function (method, url, data, headers) { return [200, '', '']; }); - - $httpBackend.whenPOST('/accounts/1').respond(function(method, url, data, headers) { + $httpBackend.whenPOST('/accounts/1').respond(function (method, url, data, headers) { return [200, '', '']; }); - - $httpBackend.whenPUT('/accounts/1').respond(function(method, url, data, headers) { - accountsModel[1] = angular.fromJson(data); + $httpBackend.whenPUT('/accounts/1').respond(function (method, url, data, headers) { + testData.accountsModel[1] = angular.fromJson(data); return [201, data, '']; }); - $httpBackend.whenGET('/error').respond(function() { + + $httpBackend.whenGET('/info').respond(testData.infoModel); + $httpBackend.whenGET('/accounts/1/info').respond(testData.infoModel); + $httpBackend.whenPUT('/info').respond(function (method, url, data) { + return [200, data, '']; + }); + + $httpBackend.whenGET('/accountsHAL').respond(testData.accountsHalModel); + $httpBackend.whenPUT('/accountsHAL/martin').respond(function (method, url, data) { + testData.accountsHalModel[0] = angular.fromJson(data); + return [200, data, '']; + }); + + // Full URL + $httpBackend.whenGET('http://accounts.com/all').respond(testData.accountsModel); + + $httpBackend.whenGET('/error').respond(function () { return [500, {}, '']; }); - $httpBackend.whenGET('/misc/zero').respond(function() { + $httpBackend.whenGET('/misc/zero').respond(function () { return [200, 0, '']; }); - $httpBackend.whenPOST('/customs').respond(function(method, url, data, headers) { + $httpBackend.whenPOST('/customs').respond(function (method, url, data, headers) { if (JSON.parse(data).one) { return [201, '', '']; } else { @@ -173,85 +216,46 @@ describe('Restangular', function() { // return the status code given // e.g.: /error/404 returns 404 Not Found var urlRegex = /\/error\/(\d{3})/; - $httpBackend.whenGET(urlRegex).respond(function(method, url, data, headers) { + $httpBackend.whenGET(urlRegex).respond(function (method, url, data, headers) { return [url.match(urlRegex)[1], {}, '']; }); - Restangular = $injector.get('Restangular'); - restangularAccounts = Restangular.all('accounts'); - restangularAccount0 = Restangular.one('accounts', 0); - restangularAccount1 = Restangular.one('accounts', 1); - - - // Another API for testing - customers = [{ - id: 0, - name: 'Alice', - status: 'active', - credit: 4000.0 - }, { - id: 1, - name: 'Bob', - status: 'active', - credit: 4000.0 - }, { - id: 2, - name: 'Carl', - status: 'active', - credit: 4000.0 - }]; - publications = [{ - id: 1, - title: 'Sample', - content: 'Rich data', - tags: [ - 'science', - 'chemistry' - ] - }]; - newCustomer = { - id: 3, - name: 'New', - status: 'active', - credit: 4000.0 - }; - - $httpBackend.whenGET('/customers/').respond(customers); - $httpBackend.whenGET('http://localhost:8080/customers/').respond(customers); - $httpBackend.whenGET('api.new.domain/customers/').respond(customers); - $httpBackend.whenGET('/customers/?active=true').respond(customers); - $httpBackend.whenGET('/customers/publications/?tags=chemistry').respond(publications); - $httpBackend.whenPUT('/customers/0').respond(function(method, url, data) { - customers[0] = angular.fromJson(data); + $httpBackend.whenGET('/customers/').respond(testData.customers); + $httpBackend.whenGET('http://localhost:8080/customers/').respond(testData.customers); + $httpBackend.whenGET('api.new.domain/customers/').respond(testData.customers); + $httpBackend.whenGET('/customers/?active=true').respond(testData.customers); + $httpBackend.whenGET('/customers/publications/?tags=chemistry').respond(testData.publications); + $httpBackend.whenPUT('/customers/0').respond(function (method, url, data) { + testData.customers[0] = angular.fromJson(data); return [200, data, '']; }); - $httpBackend.whenPOST('/customers/').respond(function(method, url, data, headers) { + $httpBackend.whenPOST('/customers/').respond(function (method, url, data, headers) { var newData = angular.fromJson(data); newData.fromServer = true; return [201, JSON.stringify(newData), '']; }); - })); + }); // END OF BEFOREEACH - afterEach(function() { + afterEach(function () { $httpBackend.verifyNoOutstandingExpectation(); $httpBackend.verifyNoOutstandingRequest(); }); - describe('Interceptors', function() { - it('Should add multiple request and response interceptors', function() { - Restangular.addRequestInterceptor(function(elem) { + describe('Interceptors', function () { + it('Should add multiple request and response interceptors', function () { + Restangular.addRequestInterceptor(function (elem) { var elemCopy = angular.copy(elem); elemCopy.firstRequestInterceptor = true; return elemCopy; }); - Restangular.addRequestInterceptor(function(elem) { + Restangular.addRequestInterceptor(function (elem) { expect(elem.firstRequestInterceptor).toBeDefined(); var elemCopy = angular.copy(elem); elemCopy.secondRequestInterceptor = true; return elemCopy; }); - Restangular.addFullRequestInterceptor(function(elem) { + Restangular.addFullRequestInterceptor(function (elem) { expect(elem.firstRequestInterceptor).toBeDefined(); expect(elem.secondRequestInterceptor).toBeDefined(); var elemCopy = angular.copy(elem); @@ -261,20 +265,20 @@ describe('Restangular', function() { }; }); - Restangular.addResponseInterceptor(function(elem) { + Restangular.addResponseInterceptor(function (elem) { var elemCopy = angular.copy(elem); elemCopy.firstResponseInterceptor = true; return elemCopy; }); - Restangular.addResponseInterceptor(function(elem) { + Restangular.addResponseInterceptor(function (elem) { expect(elem.firstResponseInterceptor).toBeDefined(); var elemCopy = angular.copy(elem); elemCopy.secondResponseInterceptor = true; return elemCopy; }); - $httpBackend.whenPOST('/list').respond(function(method, url, data, headers) { + $httpBackend.whenPOST('/list').respond(function (method, url, data, headers) { var elem = angular.fromJson(data); expect(elem.firstRequestInterceptor).toBeDefined(); expect(elem.secondRequestInterceptor).toBeDefined(); @@ -286,7 +290,7 @@ describe('Restangular', function() { Restangular.all('list').post({ name: 'Gonto' - }).then(function(elem) { + }).then(function (elem) { expect(elem.firstResponseInterceptor).toBeDefined(); expect(elem.secondResponseInterceptor).toBeDefined(); }); @@ -294,20 +298,20 @@ describe('Restangular', function() { $httpBackend.flush(); }); - it('Should add multiple error interceptors', function() { + it('Should add multiple error interceptors', function () { $httpBackend.expectGET('/error'); - var CallbackManager = function() {}; - CallbackManager.successCallback = function() { + var CallbackManager = function () {}; + CallbackManager.successCallback = function () { expect(CallbackManager.successCallback).not.toHaveBeenCalled(); }; - CallbackManager.errorCallback = function() { + CallbackManager.errorCallback = function () { expect(CallbackManager.firstErrorInterceptor).toHaveBeenCalled(); expect(CallbackManager.secondErrorInterceptor).toHaveBeenCalled(); }; - CallbackManager.firstErrorInterceptor = function() {}; - CallbackManager.secondErrorInterceptor = function() {}; + CallbackManager.firstErrorInterceptor = function () {}; + CallbackManager.secondErrorInterceptor = function () {}; spyOn(CallbackManager, 'successCallback').and.callThrough(); spyOn(CallbackManager, 'firstErrorInterceptor').and.callThrough(); @@ -323,21 +327,21 @@ describe('Restangular', function() { $httpBackend.flush(); }); - it('Should add multiple error interceptors but don\'t reject the promise if one of them returns false', function() { + it('Should add multiple error interceptors but don\'t reject the promise if one of them returns false', function () { $httpBackend.expectGET('/error'); - var CallbackManager = function() {}; - CallbackManager.successCallback = function() { + var CallbackManager = function () {}; + CallbackManager.successCallback = function () { expect(CallbackManager.successCallback).not.toHaveBeenCalled(); }; - CallbackManager.errorCallback = function() { + CallbackManager.errorCallback = function () { expect(CallbackManager.errorCallback).not.toHaveBeenCalled(); }; - CallbackManager.firstErrorInterceptor = function() { + CallbackManager.firstErrorInterceptor = function () { return true; }; - CallbackManager.secondErrorInterceptor = function() { + CallbackManager.secondErrorInterceptor = function () { return false; // prevent promise to be rejected }; @@ -354,22 +358,22 @@ describe('Restangular', function() { $httpBackend.flush(); }); - it('Should add multiple error interceptors for a single get too', function() { + it('Should add multiple error interceptors for a single get too', function () { $httpBackend.expectGET('/error/404'); - var CallbackManager = function() {}; - CallbackManager.successCallback = function() { + var CallbackManager = function () {}; + CallbackManager.successCallback = function () { expect(CallbackManager.successCallback).not.toHaveBeenCalled(); }; - CallbackManager.errorCallback = function() { + CallbackManager.errorCallback = function () { expect(CallbackManager.firstErrorInterceptor).toHaveBeenCalled(); expect(CallbackManager.secondErrorInterceptor).toHaveBeenCalled(); }; - CallbackManager.firstErrorInterceptor = function(response) { + CallbackManager.firstErrorInterceptor = function (response) { expect(Number(response.status)).toEqual(404); }; - CallbackManager.secondErrorInterceptor = function() {}; + CallbackManager.secondErrorInterceptor = function () {}; spyOn(CallbackManager, 'successCallback').and.callThrough(); spyOn(CallbackManager, 'firstErrorInterceptor').and.callThrough(); @@ -386,17 +390,17 @@ describe('Restangular', function() { }); }); - describe('Transformers', function() { - it('Should decorate element both on server and local by default', function() { + describe('Transformers', function () { + it('Should decorate element both on server and local by default', function () { - Restangular.extendModel('accounts', function(account) { - account.extended = function() { + Restangular.extendModel('accounts', function (account) { + account.extended = function () { return true; }; return account; }); - Restangular.one('accounts', 1).get().then(function(account) { + Restangular.one('accounts', 1).get().then(function (account) { expect(account.extended).toBeDefined(); }); @@ -409,9 +413,9 @@ describe('Restangular', function() { }); - describe('With Suffix', function() { - it('shouldn\'t add suffix to getRestangularUrl', function() { - var suffixRestangular = Restangular.withConfig(function(RestangularConfigurer) { + describe('With Suffix', function () { + it('shouldn\'t add suffix to getRestangularUrl', function () { + var suffixRestangular = Restangular.withConfig(function (RestangularConfigurer) { RestangularConfigurer.setRequestSuffix('.json'); }); var collection = suffixRestangular.all('accounts'); @@ -419,8 +423,8 @@ describe('Restangular', function() { expect(collection.one('1').getRestangularUrl()).toBe('/accounts/1'); }); - it('should add suffix to getRequestedUrl', function() { - var suffixRestangular = Restangular.withConfig(function(RestangularConfigurer) { + it('should add suffix to getRequestedUrl', function () { + var suffixRestangular = Restangular.withConfig(function (RestangularConfigurer) { RestangularConfigurer.setRequestSuffix('.json'); }); var collection = suffixRestangular.all('accounts'); @@ -428,8 +432,8 @@ describe('Restangular', function() { expect(collection.one('1').getRequestedUrl()).toBe('/accounts/1.json'); }); - it('should add suffix to request', function() { - var suffixRestangular = Restangular.withConfig(function(RestangularConfigurer) { + it('should add suffix to request', function () { + var suffixRestangular = Restangular.withConfig(function (RestangularConfigurer) { RestangularConfigurer.setRequestSuffix('.json'); }); var collection = suffixRestangular.all('accounts'); @@ -440,8 +444,8 @@ describe('Restangular', function() { $httpBackend.flush(); }); - it('shouldn\'t add suffix to allUrl', function() { - var suffixRestangular = Restangular.withConfig(function(RestangularConfigurer) { + it('shouldn\'t add suffix to allUrl', function () { + var suffixRestangular = Restangular.withConfig(function (RestangularConfigurer) { RestangularConfigurer.setRequestSuffix('.json'); }); $httpBackend.expectGET('http://accounts.com/all'); @@ -450,8 +454,8 @@ describe('Restangular', function() { }); }); - describe('JSONp', function() { - it('should work for get', function() { + describe('JSONp', function () { + it('should work for get', function () { Restangular.setJsonp(true); Restangular.one('accounts', 1).get(); @@ -459,7 +463,7 @@ describe('Restangular', function() { $httpBackend.flush(); }); - it('should work for getList', function() { + it('should work for getList', function () { Restangular.setJsonp(true); Restangular.all('accounts').getList(); @@ -467,7 +471,7 @@ describe('Restangular', function() { $httpBackend.flush(); }); - it('shouldn\'t override post', function() { + it('shouldn\'t override post', function () { Restangular.setJsonp(true); restangularAccounts.post({ id: 2, @@ -481,9 +485,9 @@ describe('Restangular', function() { }); - describe('Local data', function() { - it('Should restangularize a collection OK', function() { - var collection = angular.copy(accountsModel); + describe('Local data', function () { + it('Should restangularize a collection OK', function () { + var collection = angular.copy(testData.accountsModel); Restangular.restangularizeCollection(null, collection, 'accounts'); @@ -495,8 +499,8 @@ describe('Restangular', function() { }); - it('Should restangularize a function with arguments OK', function() { - var collection = function(a, b) {}; + it('Should restangularize a function with arguments OK', function () { + var collection = function (a, b) {}; Restangular.restangularizeCollection(null, collection, 'accounts'); @@ -505,7 +509,7 @@ describe('Restangular', function() { expect(collection.getRestangularUrl()).toBe('/accounts'); }); - it('should have fromServer set when restangularizeElement is called with that param', function() { + it('should have fromServer set when restangularizeElement is called with that param', function () { var element = Restangular.restangularizeElement(null, {}, 'accounts', true); expect(element.fromServer).toEqual(true); @@ -516,7 +520,7 @@ describe('Restangular', function() { expect(element.fromServer).toEqual(false); }); - it('should have fromServer set when restangularizeCollection is called with that param', function() { + it('should have fromServer set when restangularizeCollection is called with that param', function () { var collection = Restangular.restangularizeCollection(null, [{}], 'accounts', true); expect(collection[0].fromServer).toEqual(true); @@ -528,11 +532,11 @@ describe('Restangular', function() { }); }); - describe('restangularizePromiseIntercept', function() { - it('should be invoked by restangularizePromise', function() { + describe('restangularizePromiseIntercept', function () { + it('should be invoked by restangularizePromise', function () { var calledWithPromise; - Restangular.setRestangularizePromiseInterceptor(function(promise) { + Restangular.setRestangularizePromiseInterceptor(function (promise) { calledWithPromise = promise; promise.$object.$custom = true; @@ -547,8 +551,8 @@ describe('Restangular', function() { }); }); - describe('$object', function() { - it('Should work for single get', function() { + describe('$object', function () { + it('Should work for single get', function () { var promise = Restangular.one('accounts', 1).get(); var obj = promise.$object; expect(obj).toBeDefined(); @@ -559,9 +563,9 @@ describe('Restangular', function() { expect(obj.amount).toEqual(3.1416); }); - it('Shouldn\'t be restangularized by default', function() { - Restangular.extendModel('accounts', function(account) { - account.extended = function() { + it('Shouldn\'t be restangularized by default', function () { + Restangular.extendModel('accounts', function (account) { + account.extended = function () { return true; }; return account; @@ -575,7 +579,7 @@ describe('Restangular', function() { $httpBackend.flush(); }); - it('Should work for single get', function() { + it('Should work for single get', function () { var promise = Restangular.all('accounts').getList(); var list = promise.$object; expect(list).toBeDefined(); @@ -588,120 +592,120 @@ describe('Restangular', function() { }); }); - describe('ALL', function() { - it('getList() should return an array of items', function() { - restangularAccounts.getList().then(function(accounts) { - expect(Restangular.stripRestangular(accounts)).toEqual(Restangular.stripRestangular(accountsModel)); + describe('ALL', function () { + it('getList() should return an array of items', function () { + restangularAccounts.getList().then(function (accounts) { + expect(Restangular.stripRestangular(accounts)).toEqual(Restangular.stripRestangular(testData.accountsModel)); }); $httpBackend.flush(); }); - it('several getList() should return an array of items', function() { + it('several getList() should return an array of items', function () { $httpBackend.expectGET('/accounts/0,1'); - Restangular.several('accounts', 0, 1).getList().then(function(accounts) { - expect(Restangular.stripRestangular(accounts)).toEqual(Restangular.stripRestangular(accountsModel)); + Restangular.several('accounts', 0, 1).getList().then(function (accounts) { + expect(Restangular.stripRestangular(accounts)).toEqual(Restangular.stripRestangular(testData.accountsModel)); }); $httpBackend.flush(); }); - it('several remove() should work', function() { + it('several remove() should work', function () { $httpBackend.expectDELETE('/accounts/0,1').respond([200, '', '']); Restangular.several('accounts', 0, 1).remove(); $httpBackend.flush(); }); - it('get(id) should return the item with given id', function() { - restangularAccounts.get(0).then(function(account) { - expect(Restangular.stripRestangular(account)).toEqual(Restangular.stripRestangular(accountsModel[0])); + it('get(id) should return the item with given id', function () { + restangularAccounts.get(0).then(function (account) { + expect(Restangular.stripRestangular(account)).toEqual(Restangular.stripRestangular(testData.accountsModel[0])); }); $httpBackend.flush(); }); - it('uses all to get the list without parameters', function() { + it('uses all to get the list without parameters', function () { Restangular.one('accounts', 1).all('messages').getList(); $httpBackend.expectGET('/accounts/1/messages'); $httpBackend.flush(); }); - it('Custom GET methods should work', function() { - restangularAccounts.customGETLIST('messages').then(function(msgs) { - expect(Restangular.stripRestangular(msgs)).toEqual(Restangular.stripRestangular(messages)); + it('Custom GET methods should work', function () { + restangularAccounts.customGETLIST('messages').then(function (msgs) { + expect(Restangular.stripRestangular(msgs)).toEqual(Restangular.stripRestangular(testData.messages)); }); $httpBackend.flush(); }); - it('post() should add a new item', function() { + it('post() should add a new item', function () { restangularAccounts.post({ id: 2, user: 'Someone' - }).then(function() { - expect(accountsModel.length).toEqual(2); + }).then(function () { + expect(testData.accountsModel.length).toEqual(2); }); $httpBackend.expectPOST('/accounts').respond(201, ''); $httpBackend.flush(); }); - it('customPOST() should add a new item', function() { + it('customPOST() should add a new item', function () { restangularAccounts.customPOST({ id: 2, user: 'Someone' - }).then(function() { - expect(accountsModel.length).toEqual(2); + }).then(function () { + expect(testData.accountsModel.length).toEqual(2); }); $httpBackend.expectPOST('/accounts').respond(201, ''); $httpBackend.flush(); }); - it('post() should work with arrays', function() { + it('post() should work with arrays', function () { Restangular.all('places').post([{ name: 'Gonto' }, { name: 'John' - }]).then(function(value) { + }]).then(function (value) { expect(value.length).toEqual(2); }); - $httpBackend.expectPOST('/places').respond(function(method, url, data, headers) { + $httpBackend.expectPOST('/places').respond(function (method, url, data, headers) { return [201, angular.fromJson(data), '']; }); $httpBackend.flush(); }); - it('post() should add a new item with data and return the data from the server', function() { - restangularAccounts.post(newAccount).then(function(added) { + it('post() should add a new item with data and return the data from the server', function () { + restangularAccounts.post(testData.newAccount).then(function (added) { expect(added.fromServer).toEqual(true); - expect(added.id).toEqual(nextAccountId); - expect(added.user).toEqual(newAccount.user); + expect(added.id).toEqual(testData.nextAccountId); + expect(added.user).toEqual(testData.newAccount.user); }); $httpBackend.expectPOST('/accounts'); $httpBackend.flush(); }); - it('Doing a post and then other operation (delete) should call right URLs', function() { - restangularAccounts.post(newAccount).then(function(added) { + it('Doing a post and then other operation (delete) should call right URLs', function () { + restangularAccounts.post(testData.newAccount).then(function (added) { added.remove(); - $httpBackend.expectDELETE('/accounts/' + nextAccountId).respond(201, ''); + $httpBackend.expectDELETE('/accounts/' + testData.nextAccountId).respond(201, ''); }); $httpBackend.flush(); }); - it('Doing a post to a server that returns no element will return undefined', function() { - restangularAccounts.getList().then(function(accounts) { + it('Doing a post to a server that returns no element will return undefined', function () { + restangularAccounts.getList().then(function (accounts) { var newTransaction = { id: 1, name: 'Gonto' }; - accounts[1].post('transactions', newTransaction).then(function(transaction) { + accounts[1].post('transactions', newTransaction).then(function (transaction) { expect(transaction).toBeUndefined(); }); }); @@ -709,23 +713,23 @@ describe('Restangular', function() { $httpBackend.flush(); }); - it('head() should safely return', function() { - restangularAccounts.head().then(function() { + it('head() should safely return', function () { + restangularAccounts.head().then(function () { expect(true).toBe(true); }); $httpBackend.flush(); }); - it('trace() should safely return', function() { - restangularAccounts.trace().then(function() { + it('trace() should safely return', function () { + restangularAccounts.trace().then(function () { expect(true).toBe(true); }); $httpBackend.flush(); }); - it('customPUT should work', function() { - $httpBackend.expectPUT('/accounts/hey').respond(accountsModel); + it('customPUT should work', function () { + $httpBackend.expectPUT('/accounts/hey').respond(testData.accountsModel); restangularAccounts.customPUT({ key: 'value' }, 'hey'); @@ -733,50 +737,50 @@ describe('Restangular', function() { $httpBackend.flush(); }); - it('customPATCH should work', function() { + it('customPATCH should work', function () { var data = { foo: 'bar' }; - $httpBackend.expectPATCH('/accounts/hey', data).respond(accountsModel); + $httpBackend.expectPATCH('/accounts/hey', data).respond(testData.accountsModel); restangularAccounts.customPATCH(data, 'hey'); $httpBackend.flush(); }); - it('options() should safely return', function() { - restangularAccounts.options().then(function() { + it('options() should safely return', function () { + restangularAccounts.options().then(function () { expect(true).toBe(true); }); $httpBackend.flush(); }); - it('getList() should correctly handle params after customDELETE', function() { - $httpBackend.expectGET('/accounts?foo=1').respond(accountsModel); + it('getList() should correctly handle params after customDELETE', function () { + $httpBackend.expectGET('/accounts?foo=1').respond(testData.accountsModel); restangularAccounts.getList({ foo: 1 - }).then(function() { + }).then(function () { $httpBackend.expectDELETE('/accounts?id=1').respond(201, ''); return restangularAccounts.customDELETE('', { id: 1 }); - }).then(function() { - $httpBackend.expectGET('/accounts?foo=1').respond(accountsModel); + }).then(function () { + $httpBackend.expectGET('/accounts?foo=1').respond(testData.accountsModel); return restangularAccounts.getList({ foo: 1 }); - }).then(function(accounts) { - expect(Restangular.stripRestangular(accounts)).toEqual(Restangular.stripRestangular(accountsModel)); + }).then(function (accounts) { + expect(Restangular.stripRestangular(accounts)).toEqual(Restangular.stripRestangular(testData.accountsModel)); }); $httpBackend.flush(); }); }); - describe('Scoped Service', function() { + describe('Scoped Service', function () { - it('should correctly work', function() { + it('should correctly work', function () { var Accounts = Restangular.service('accounts'); - Accounts.post(newAccount); + Accounts.post(testData.newAccount); Accounts.one(0).get(); Accounts.getList(); @@ -786,9 +790,9 @@ describe('Restangular', function() { $httpBackend.flush(); }); - it('should correctly work with children', function() { + it('should correctly work with children', function () { var Transactions = Restangular.service('transactions', restangularAccount1); - Transactions.post(newAccount); + Transactions.post(testData.newAccount); Transactions.one(1).get(); Transactions.getList(); Transactions.get(1); @@ -800,9 +804,9 @@ describe('Restangular', function() { $httpBackend.flush(); }); - it('should add custom collection method added with withConfig', function() { - var Accounts = Restangular.withConfig(function(RestangularConfigurer) { - RestangularConfigurer.addElementTransformer('accounts', true, function(worker) { + it('should add custom collection method added with withConfig', function () { + var Accounts = Restangular.withConfig(function (RestangularConfigurer) { + RestangularConfigurer.addElementTransformer('accounts', true, function (worker) { worker.addRestangularMethod('doSomething', 'get', 'do-something'); return worker; }); @@ -811,7 +815,7 @@ describe('Restangular', function() { expect(Accounts.doSomething).toBeDefined(); expect(_.isFunction(Accounts.doSomething)).toBeTruthy(); - Accounts.post(newAccount); + Accounts.post(testData.newAccount); Accounts.one(0).get(); Accounts.getList(); Accounts.doSomething(); @@ -825,30 +829,30 @@ describe('Restangular', function() { $httpBackend.flush(); }); - it('should provide a one-off $http configuration method', function() { + it('should provide a one-off $http configuration method', function () { var Accounts = Restangular.service('accounts'); Accounts.withHttpConfig({ transformRequest: angular.identity }); - Accounts.post(newAccount); + Accounts.post(testData.newAccount); $httpBackend.expectPOST('/accounts'); $httpBackend.flush(); }); }); - describe('ONE', function() { - it('get() should return a JSON item', function() { - restangularAccount1.get().then(function(account) { + describe('ONE', function () { + it('get() should return a JSON item', function () { + restangularAccount1.get().then(function (account) { expect(Restangular.stripRestangular(account)) - .toEqual(Restangular.stripRestangular(accountsModel[1])); + .toEqual(Restangular.stripRestangular(testData.accountsModel[1])); }); $httpBackend.flush(); }); - it('Should save as put correctly', function() { - restangularAccount1.get().then(function(account) { + it('Should save as put correctly', function () { + restangularAccount1.get().then(function (account) { $httpBackend.expectPUT('/accounts/1'); account.put(); }); @@ -856,7 +860,7 @@ describe('Restangular', function() { $httpBackend.flush(); }); - it('Should save as post correctly', function() { + it('Should save as post correctly', function () { var account1 = angular.copy(restangularAccount1); $httpBackend.expectPOST('/accounts/1'); account1.name = 'Hey'; @@ -865,52 +869,52 @@ describe('Restangular', function() { $httpBackend.flush(); }); - it('Should keep route property when element is created', function() { + it('Should keep route property when element is created', function () { var account1 = Restangular.restangularizeElement(null, {}, 'accounts'); $httpBackend.expectPOST('/accounts'); $httpBackend.expectPUT('/accounts/1'); account1.name = 'Hey'; - account1.save().then(function(accountFromServer) { + account1.save().then(function (accountFromServer) { accountFromServer.id = 1; return accountFromServer.save(); - }).then(function(accountFromServer2) { + }).then(function (accountFromServer2) { expect(accountFromServer2.route).toBe(account1.route); }); $httpBackend.flush(); }); - it('Should make RequestLess connections with one', function() { - restangularAccount1.one('transactions', 1).get().then(function(transaction) { + it('Should make RequestLess connections with one', function () { + restangularAccount1.one('transactions', 1).get().then(function (transaction) { expect(Restangular.stripRestangular(transaction)) - .toEqual(Restangular.stripRestangular(accountsModel[1].transactions[1])); + .toEqual(Restangular.stripRestangular(testData.accountsModel[1].transactions[1])); }); $httpBackend.flush(); }); - it('Should make RequestLess connections with all', function() { - restangularAccount1.all('transactions').getList().then(function(transactions) { + it('Should make RequestLess connections with all', function () { + restangularAccount1.all('transactions').getList().then(function (transactions) { expect(Restangular.stripRestangular(transactions)) - .toEqual(Restangular.stripRestangular(accountsModel[1].transactions)); + .toEqual(Restangular.stripRestangular(testData.accountsModel[1].transactions)); }); $httpBackend.flush(); }); - it('Custom GET methods should work', function() { - restangularAccount1.customGET('message').then(function(msg) { - expect(Restangular.stripRestangular(msg)).toEqual(Restangular.stripRestangular(messages[0])); + it('Custom GET methods should work', function () { + restangularAccount1.customGET('message').then(function (msg) { + expect(Restangular.stripRestangular(msg)).toEqual(Restangular.stripRestangular(testData.messages[0])); }); $httpBackend.flush(); }); - it('put() should update the value', function() { - restangularAccount1.get().then(function(account) { + it('put() should update the value', function () { + restangularAccount1.get().then(function (account) { account.amount = 1.618; - account.put().then(function(newAc) { - expect(accountsModel[1].amount).toEqual(1.618); + account.put().then(function (newAc) { + expect(testData.accountsModel[1].amount).toEqual(1.618); newAc.remove(); $httpBackend.expectDELETE('/accounts/1'); }); @@ -922,11 +926,11 @@ describe('Restangular', function() { $httpBackend.flush(); }); - it('should return an array when accessing a subvalue', function() { - restangularAccount1.get().then(function(account) { - account.getList('transactions').then(function(transactions) { + it('should return an array when accessing a subvalue', function () { + restangularAccount1.get().then(function (account) { + account.getList('transactions').then(function (transactions) { expect(Restangular.stripRestangular(transactions)) - .toEqual(Restangular.stripRestangular(accountsModel[1].transactions)); + .toEqual(Restangular.stripRestangular(testData.accountsModel[1].transactions)); }); }); @@ -934,16 +938,16 @@ describe('Restangular', function() { }); }); - describe('COPY', function() { - it('should copy an object and "this" should reference the copied object', function() { - var copiedAccount = Restangular.copy(accountsModel[0]); + describe('COPY', function () { + it('should copy an object and "this" should reference the copied object', function () { + var copiedAccount = Restangular.copy(testData.accountsModel[0]); var that; copiedAccount.user = 'Copied string'; - expect(copiedAccount).not.toBe(accountsModel[0]); + expect(copiedAccount).not.toBe(testData.accountsModel[0]); // create a spy for one of the methods to capture the value of 'this' - spyOn(copiedAccount, 'getRestangularUrl').and.callFake(function() { + spyOn(copiedAccount, 'getRestangularUrl').and.callFake(function () { that = this; }); @@ -951,7 +955,7 @@ describe('Restangular', function() { expect(that).toBe(copiedAccount); }); - it('should copy an object and "fromServer" param should be the same with the copied object', function() { + it('should copy an object and "fromServer" param should be the same with the copied object', function () { var responseHandler = jasmine.createSpy(); // with fromServer=true @@ -1014,16 +1018,16 @@ describe('Restangular', function() { }); }); - describe('getRestangularUrl', function() { - it('should return the generated URL when you chain Restangular methods together', function() { + describe('getRestangularUrl', function () { + it('should return the generated URL when you chain Restangular methods together', function () { var restangularSpaces = Restangular.one('accounts', 123).one('buildings', 456).all('spaces'); expect(restangularSpaces.getRestangularUrl()).toEqual('/accounts/123/buildings/456/spaces'); }); }); - describe('getRestangularUrl with useCannonicalId set to true', function() { - it('should return the generated URL when you chain Restangular methods together', function() { - var R = Restangular.withConfig(function(config) { + describe('getRestangularUrl with useCannonicalId set to true', function () { + it('should return the generated URL when you chain Restangular methods together', function () { + var R = Restangular.withConfig(function (config) { config.setUseCannonicalId(true); }); var restangularSpaces = R.one('accounts', 123).one('buildings', 456).all('spaces'); @@ -1032,53 +1036,53 @@ describe('Restangular', function() { }); - describe('addElementTransformer', function() { - it('should allow for a custom method to be placed at the collection level', function() { + describe('addElementTransformer', function () { + it('should allow for a custom method to be placed at the collection level', function () { var accountsPromise; - Restangular.addElementTransformer('accounts', true, function(collection) { - collection.totalAmount = function() {}; + Restangular.addElementTransformer('accounts', true, function (collection) { + collection.totalAmount = function () {}; return collection; }); accountsPromise = Restangular.all('accounts').getList(); - accountsPromise.then(function(accounts) { + accountsPromise.then(function (accounts) { expect(typeof accounts.totalAmount).toEqual('function'); }); $httpBackend.flush(); }); - it('should allow for a custom method to be placed at the model level when one model is requested', function() { + it('should allow for a custom method to be placed at the model level when one model is requested', function () { var accountPromise; - Restangular.addElementTransformer('accounts', false, function(model) { - model.prettifyAmount = function() {}; + Restangular.addElementTransformer('accounts', false, function (model) { + model.prettifyAmount = function () {}; return model; }); accountPromise = Restangular.one('accounts', 1).get(); - accountPromise.then(function(account) { + accountPromise.then(function (account) { expect(typeof account.prettifyAmount).toEqual('function'); }); $httpBackend.flush(); }); - it('should allow for a custom method to be placed at the model level when several models are requested', function() { + it('should allow for a custom method to be placed at the model level when several models are requested', function () { var accountsPromise; - Restangular.addElementTransformer('accounts', false, function(model) { - model.prettifyAmount = function() {}; + Restangular.addElementTransformer('accounts', false, function (model) { + model.prettifyAmount = function () {}; return model; }); accountsPromise = Restangular.all('accounts', 1).getList(); - accountsPromise.then(function(accounts) { - accounts.forEach(function(account) { + accountsPromise.then(function (accounts) { + accounts.forEach(function (account) { expect(typeof account.prettifyAmount).toEqual('function'); }); }); @@ -1086,18 +1090,18 @@ describe('Restangular', function() { $httpBackend.flush(); }); - it('should allow for a custom method to be placed at the collection level using a regexp matching the route', function() { + it('should allow for a custom method to be placed at the collection level using a regexp matching the route', function () { var accountsPromise; - Restangular.addElementTransformer(/^accounts/, false, function(model) { - model.prettifyAmount = function() {}; + Restangular.addElementTransformer(/^accounts/, false, function (model) { + model.prettifyAmount = function () {}; return model; }); accountsPromise = Restangular.all('accounts/search/byOwner', 1).getList(); - accountsPromise.then(function(accounts) { - accounts.forEach(function(account) { + accountsPromise.then(function (accounts) { + accounts.forEach(function (account) { expect(typeof account.prettifyAmount).toEqual('function'); }); }); @@ -1108,7 +1112,7 @@ describe('Restangular', function() { it('should work with cloned collections', function () { var responseHandler = jasmine.createSpy(); - Restangular.addElementTransformer(/^accounts/, true, function(collection) { + Restangular.addElementTransformer(/^accounts/, true, function (collection) { collection.customThing = 'customValue'; return collection; }); @@ -1123,35 +1127,35 @@ describe('Restangular', function() { expect(accountsCopy.customThing).toEqual('customValue'); }); - it('should allow for a custom method to be placed at the model level using regexp route when one model is requested', function() { + it('should allow for a custom method to be placed at the model level using regexp route when one model is requested', function () { var accountPromise; - Restangular.addElementTransformer(/^accounts/, false, function(model) { - model.prettifyAmount = function() {}; + Restangular.addElementTransformer(/^accounts/, false, function (model) { + model.prettifyAmount = function () {}; return model; }); accountPromise = Restangular.one('accounts', 1).get(); - accountPromise.then(function(account) { + accountPromise.then(function (account) { expect(typeof account.prettifyAmount).toEqual('function'); }); $httpBackend.flush(); }); - it('should allow for a custom method to be placed at the model level using regexp when several models are requested', function() { + it('should allow for a custom method to be placed at the model level using regexp when several models are requested', function () { var accountsPromise; - Restangular.addElementTransformer(/^accounts/, false, function(model) { - model.prettifyAmount = function() {}; + Restangular.addElementTransformer(/^accounts/, false, function (model) { + model.prettifyAmount = function () {}; return model; }); accountsPromise = Restangular.all('accounts', 1).getList(); - accountsPromise.then(function(accounts) { - accounts.forEach(function(account) { + accountsPromise.then(function (accounts) { + accounts.forEach(function (account) { expect(typeof account.prettifyAmount).toEqual('function'); }); }); @@ -1161,12 +1165,12 @@ describe('Restangular', function() { }); - describe('extendCollection', function() { - it('should be an alias for a specific invocation of addElementTransformer', function() { + describe('extendCollection', function () { + it('should be an alias for a specific invocation of addElementTransformer', function () { var spy = spyOn(Restangular, 'addElementTransformer'); - var fn = function(collection) { - collection.totalAmount = function() {}; + var fn = function (collection) { + collection.totalAmount = function () {}; return collection; }; @@ -1176,12 +1180,12 @@ describe('Restangular', function() { }); }); - describe('extendModel', function() { - it('should be an alias for a specific invocation of addElementTransformer', function() { + describe('extendModel', function () { + it('should be an alias for a specific invocation of addElementTransformer', function () { var spy = spyOn(Restangular, 'addElementTransformer'); - var fn = function(model) { - model.prettifyAmount = function() {}; + var fn = function (model) { + model.prettifyAmount = function () {}; return model; }; @@ -1191,8 +1195,8 @@ describe('Restangular', function() { }); }); - describe('headers', function() { - it('should return defaultHeaders', function() { + describe('headers', function () { + it('should return defaultHeaders', function () { var defaultHeaders = { testheader: 'header value' }; @@ -1200,9 +1204,9 @@ describe('Restangular', function() { expect(Restangular.defaultHeaders).toEqual(defaultHeaders); }); - it('should pass uppercase methods in X-HTTP-Method-Override', function() { + it('should pass uppercase methods in X-HTTP-Method-Override', function () { Restangular.setMethodOverriders(['put']); - $httpBackend.expectPOST('/overriders/1').respond(function(method, url, data, headers) { + $httpBackend.expectPOST('/overriders/1').respond(function (method, url, data, headers) { expect(headers['X-HTTP-Method-Override']).toBe('PUT'); return {}; }); @@ -1211,8 +1215,8 @@ describe('Restangular', function() { }); }); - describe('defaultRequestParams', function() { - it('should return defaultRequestParams', function() { + describe('defaultRequestParams', function () { + it('should return defaultRequestParams', function () { var defaultRequestParams = { param: 'value' }; @@ -1222,7 +1226,7 @@ describe('Restangular', function() { expect(Restangular.requestParams.common).toEqual(defaultRequestParams); }); - it('should be able to set default params for get, post, put.. methods separately', function() { + it('should be able to set default params for get, post, put.. methods separately', function () { var postParams = { post: 'value' }, @@ -1239,7 +1243,7 @@ describe('Restangular', function() { expect(Restangular.requestParams.common).not.toEqual(putParams); }); - it('should be able to set default params for multiple methods with array', function() { + it('should be able to set default params for multiple methods with array', function () { var defaultParams = { param: 'value' }; @@ -1253,9 +1257,9 @@ describe('Restangular', function() { }); }); - describe('withConfig', function() { - it('should create new service with scoped configuration', function() { - var childRestangular = Restangular.withConfig(function(RestangularConfigurer) { + describe('withConfig', function () { + it('should create new service with scoped configuration', function () { + var childRestangular = Restangular.withConfig(function (RestangularConfigurer) { RestangularConfigurer.setBaseUrl('/api/v1'); }); @@ -1264,12 +1268,12 @@ describe('Restangular', function() { }); - it('should allow nested configurations', function() { - var childRestangular = Restangular.withConfig(function(RestangularConfigurer) { + it('should allow nested configurations', function () { + var childRestangular = Restangular.withConfig(function (RestangularConfigurer) { RestangularConfigurer.setBaseUrl('/api/v1'); }); - var grandchildRestangular = childRestangular.withConfig(function(RestangularConfigurer) { + var grandchildRestangular = childRestangular.withConfig(function (RestangularConfigurer) { RestangularConfigurer.setRequestSuffix('.json'); }); @@ -1284,9 +1288,9 @@ describe('Restangular', function() { }); }); - describe('Self linking', function() { - it('Should request the link in HAL format', function() { - var linkRestangular = Restangular.withConfig(function(RestangularConfigurer) { + describe('Self linking', function () { + it('Should request the link in HAL format', function () { + var linkRestangular = Restangular.withConfig(function (RestangularConfigurer) { RestangularConfigurer.setRestangularFields({ selfLink: '_links.self' }); @@ -1304,38 +1308,38 @@ describe('Restangular', function() { }); }); - describe('Singe one (endpoint not expecting an id)', function() { - it('does not use the id for single resource GET', function() { + describe('Singe one (endpoint not expecting an id)', function () { + it('does not use the id for single resource GET', function () { Restangular.one('info', 0, true).get(); $httpBackend.expectGET('/info'); $httpBackend.flush(); }); - it('getRestangularUrl() returns still the url without id after GET', function() { + it('getRestangularUrl() returns still the url without id after GET', function () { var record = Restangular.one('info', 0, true); - record.get().then(function(data) { + record.get().then(function (data) { expect(data.getRestangularUrl()).toEqual('/info'); }); $httpBackend.expectGET('/info'); $httpBackend.flush(); }); - it('does not use the id for single nested resource GET', function() { + it('does not use the id for single nested resource GET', function () { Restangular.one('accounts', 1).one('info', 0, true).get(); $httpBackend.expectGET('/accounts/1/info'); $httpBackend.flush(); }); - it('does not use the id for single resource PUT', function() { + it('does not use the id for single resource PUT', function () { Restangular.one('info', 0, true).put(); $httpBackend.expectPUT('/info'); $httpBackend.flush(); }); }); - describe('setSelfLinkAbsoluteUrl', function() { - it('works', function() { - var childRestangular = Restangular.withConfig(function(RestangularConfigurer) { + describe('setSelfLinkAbsoluteUrl', function () { + it('works', function () { + var childRestangular = Restangular.withConfig(function (RestangularConfigurer) { RestangularConfigurer.setSelfLinkAbsoluteUrl(false); }); @@ -1344,41 +1348,41 @@ describe('Restangular', function() { }); }); - describe('Misc', function() { - it('should not strip [one] or [all] key from plain object', function() { + describe('Misc', function () { + it('should not strip [one] or [all] key from plain object', function () { Restangular.all('customs').customPOST({ one: 'I am here', two: 'I am also here' - }).then(function() { + }).then(function () { expect(1).toBe(1); - }, function() { + }, function () { expect('Promise').toBe('correctly fulfilled'); }); $httpBackend.flush(); }); - it('should not stip non-restangularized elements', function() { + it('should not stip non-restangularized elements', function () { expect(Restangular.stripRestangular(['test', 'test2'])).toEqual(['test', 'test2']); }); - it('should accept 0 as response', function() { - Restangular.one('misc', 'zero').get().then(function(res) { + it('should accept 0 as response', function () { + Restangular.one('misc', 'zero').get().then(function (res) { expect(res).toEqual(0); }); $httpBackend.flush(); }); - it('Should accept 0 as a proper id in custom requests', function() { + it('Should accept 0 as a proper id in custom requests', function () { $httpBackend.expectDELETE('/accounts/0').respond(202); Restangular.all('accounts').customDELETE(0); $httpBackend.flush(); }); }); - describe('testing normalize url', function() { + describe('testing normalize url', function () { - it('should get a list of objects', function() { - Restangular.all('customers/').getList().then(function(res) { + it('should get a list of objects', function () { + Restangular.all('customers/').getList().then(function (res) { res.getList({ active: true }); @@ -1390,8 +1394,8 @@ describe('Restangular', function() { $httpBackend.flush(); }); - it('should get a list of objects even if the path has extra slashes', function() { - Restangular.all('customers///').getList().then(function(res) { + it('should get a list of objects even if the path has extra slashes', function () { + Restangular.all('customers///').getList().then(function (res) { res.getList({ active: true }); @@ -1401,37 +1405,37 @@ describe('Restangular', function() { $httpBackend.flush(); }); - it('should post with slash at the end', function() { - Restangular.all('customers/').getList().then(function(res) { - res.post(newCustomer); + it('should post with slash at the end', function () { + Restangular.all('customers/').getList().then(function (res) { + res.post(testData.newCustomer); $httpBackend.expectPOST('/customers/'); }); $httpBackend.expectGET('/customers/'); $httpBackend.flush(); }); - it('should put with slash at the end', function() { - Restangular.all('customers/').getList().then(function(customers) { + it('should put with slash at the end', function () { + Restangular.all('customers/').getList().then(function (customers) { customers[0].put(); $httpBackend.expectPUT('/customers/0'); }); $httpBackend.flush(); }); - it('should return a normalized URL even it has extra slashes', function() { + it('should return a normalized URL even it has extra slashes', function () { var restangularSpaces = Restangular.one('accounts//', 123).one('buildings//', 456).all('spaces///'); expect(restangularSpaces.getRestangularUrl()).toEqual('/accounts/123/buildings/456/spaces/'); }); - it('should create a new service and still working normalized URL', function() { - var newRes = Restangular.withConfig(function(RestangularConfigurer) { + it('should create a new service and still working normalized URL', function () { + var newRes = Restangular.withConfig(function (RestangularConfigurer) { RestangularConfigurer.setBaseUrl('http://localhost:8080'); }); expect(newRes.configuration.baseUrl).toEqual('http://localhost:8080'); newRes.all('customers////').getList(); $httpBackend.expectGET('http://localhost:8080/customers/'); - var newApi = Restangular.withConfig(function(RestangularConfigurer) { + var newApi = Restangular.withConfig(function (RestangularConfigurer) { RestangularConfigurer.setBaseUrl('api.new.domain'); }); @@ -1442,8 +1446,8 @@ describe('Restangular', function() { $httpBackend.flush(); }); - it('Should work with absolute URL with //authority', function() { - var newRes = Restangular.withConfig(function(RestangularConfigurer) { + it('Should work with absolute URL with //authority', function () { + var newRes = Restangular.withConfig(function (RestangularConfigurer) { RestangularConfigurer.setBaseUrl('//localhost:8080'); }); expect(newRes.configuration.baseUrl).toEqual('//localhost:8080'); @@ -1454,29 +1458,29 @@ describe('Restangular', function() { }); }); - describe('setPlainByDefault', function() { + describe('setPlainByDefault', function () { - it('should not add restangularized methods to response object', function() { - var newRes = Restangular.withConfig(function(RestangularConfigurer) { + it('should not add restangularized methods to response object', function () { + var newRes = Restangular.withConfig(function (RestangularConfigurer) { RestangularConfigurer.setPlainByDefault(true); }); expect(newRes.configuration.plainByDefault).toEqual(true); - newRes.one('accounts', 0).get().then(function(account) { - expect(account).toEqual(accountsModel[0]); + newRes.one('accounts', 0).get().then(function (account) { + expect(account).toEqual(testData.accountsModel[0]); }); $httpBackend.flush(); }); - it('shoud not add restangularized methods to response collection', function() { - var newRes = Restangular.withConfig(function(RestangularConfigurer) { + it('shoud not add restangularized methods to response collection', function () { + var newRes = Restangular.withConfig(function (RestangularConfigurer) { RestangularConfigurer.setPlainByDefault(true); }); - newRes.all('accounts').getList().then(function(accounts) { - expect(accounts).toEqual(accountsModel); + newRes.all('accounts').getList().then(function (accounts) { + expect(accounts).toEqual(testData.accountsModel); }); $httpBackend.flush(); }); From 9e4b3b40fdd613395672652135b44e7d3db34167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Sat, 7 Jan 2017 19:54:21 +0200 Subject: [PATCH 46/55] test(service): add test case for setRestangularFields Closes #374 --- test/restangularSpec.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/restangularSpec.js b/test/restangularSpec.js index de6a9662..526e10b6 100644 --- a/test/restangularSpec.js +++ b/test/restangularSpec.js @@ -1377,6 +1377,29 @@ describe('Restangular', function () { Restangular.all('accounts').customDELETE(0); $httpBackend.flush(); }); + + it('should accept reserved properties as keys in POST data when POSTing raw data', function () { + // https://github.com/mgonto/restangular/issues/374 + $httpBackend.expectPOST('/accounts/1/merge', {ids: 'test', options: 'foo', clone: 'bar'}).respond(200); + var parent = Restangular.restangularizeElement(null, {id: 1}, 'accounts', true); + parent.post('merge', {ids: 'test', options: 'foo', clone: 'bar'}); + $httpBackend.flush(); + }); + + it('should accept reserved properties as a key in POST data when using a restangularized element by configuring restangularFields', function () { + // https://github.com/mgonto/restangular/issues/374 + $httpBackend.expectPOST('/accounts', {ids: 'test', options: 'foo', clone: 'bar'}).respond(200); + Restangular.setRestangularFields({ + ids: '_ids', + clone: '_clone', + options: '_options' + }); + var parent = Restangular.restangularizeElement(null, {ids: 'test', options: 'foo', clone: 'bar'}, 'accounts', false); + parent.save(); + $httpBackend.flush(); + }); + + }); describe('testing normalize url', function () { From d55c4392b184e659c11f50e225d64c3a04da6572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Tue, 10 Jan 2017 00:06:53 +0200 Subject: [PATCH 47/55] fix(config): make it possible to override all restangularize fields --- src/restangular.js | 2 +- test/restangularSpec.js | 103 ++++++++++++++++++++++------------------ 2 files changed, 59 insertions(+), 46 deletions(-) diff --git a/src/restangular.js b/src/restangular.js index 644a2b8b..ff7d5e24 100644 --- a/src/restangular.js +++ b/src/restangular.js @@ -989,7 +989,7 @@ _.each(requestMethods, function(requestFunc, name) { var callOperation = name === 'delete' ? 'remove' : name; _.each(['do', 'custom'], function(alias) { - elem[alias + name.toUpperCase()] = _.bind(requestFunc, elem, callOperation); + elem[config.restangularFields[alias + name.toUpperCase()]] = _.bind(requestFunc, elem, callOperation); }); }); elem[config.restangularFields.customGETLIST] = _.bind(fetchFunction, elem); diff --git a/test/restangularSpec.js b/test/restangularSpec.js index 526e10b6..55fd0917 100644 --- a/test/restangularSpec.js +++ b/test/restangularSpec.js @@ -205,14 +205,6 @@ describe('Restangular', function () { return [200, 0, '']; }); - $httpBackend.whenPOST('/customs').respond(function (method, url, data, headers) { - if (JSON.parse(data).one) { - return [201, '', '']; - } else { - return [400, '', '']; - } - }); - // return the status code given // e.g.: /error/404 returns 404 Not Found var urlRegex = /\/error\/(\d{3})/; @@ -242,6 +234,64 @@ describe('Restangular', function () { $httpBackend.verifyNoOutstandingRequest(); }); + describe('stripRestangular', function () { + // We test stripRestangular by saving objects and checking + // the data received by the backend in the POST request. + // StripRestangular is used to remove Restangular's methods + // from the restangularized POST data (element), but not from raw data. + + it('should not strip Restangular properties from raw POST data', function () { + // https://github.com/mgonto/restangular/issues/374 + var restangularFields = Restangular.configuration.restangularFields; + // create an object whose keys are the values of the restangularFields + var postData = _.keyBy(restangularFields, function (value) { return value; }); + // we don't want our post data to be treated as a restangularized object + postData.restangularized = false; + // when posting, restangular shouldn't remove any of our properties + var expectedData = angular.copy(postData); + + $httpBackend.expectPOST('/accounts/1/merge', expectedData).respond(200); + var parent = Restangular.restangularizeElement(null, {id: 1}, 'accounts', true); + parent.post('merge', postData); + $httpBackend.flush(); + }); + + it('should not strip "original" Restangular properties in restangularized POST data when overriding restangularFields', function () { + // https://github.com/mgonto/restangular/issues/374 + // Here, we want to post a data object with fields + // that normally are used by Restangular, such that save, ids, options etc. + // We do that by taking each field in restangularFields and overriding it + // with something else, making restangular use different properties + // for its internal properties and functions, freeing the original ones + // for use in our data object + + // these are the original field names used by restangular + var restangularFields = Restangular.configuration.restangularFields; + // create an object whose keys are the values of the restangularFields + // i.e. {save: "save", clone: "clone", doPOST: "doPOST", ...} + var postData = _.keyBy(restangularFields, function (value) { return value; }); + // we expect the http service to get all of these "original" properties in the data object + var expectedData = angular.copy(postData); + + // Override the field names used internally by Restangular, + // the new config will be something like + // {id: '_id', save: '_save', clone: '_clone', ...} + var newFieldConfig = {}; + _.each(restangularFields, function (value, key) { + newFieldConfig[key] = '_' + key; + }); + Restangular.setRestangularFields(newFieldConfig); + + // Restangularize the data as an element to save + var parent = Restangular.restangularizeElement(null, postData, 'accounts', false); + + // make the POST and check the posted data + $httpBackend.expectPOST('/accounts', expectedData).respond(200); + parent._save(); // we've overriden the save method as _save + $httpBackend.flush(); + }); + }); + describe('Interceptors', function () { it('Should add multiple request and response interceptors', function () { Restangular.addRequestInterceptor(function (elem) { @@ -1349,21 +1399,6 @@ describe('Restangular', function () { }); describe('Misc', function () { - it('should not strip [one] or [all] key from plain object', function () { - Restangular.all('customs').customPOST({ - one: 'I am here', - two: 'I am also here' - }).then(function () { - expect(1).toBe(1); - }, function () { - expect('Promise').toBe('correctly fulfilled'); - }); - $httpBackend.flush(); - }); - - it('should not stip non-restangularized elements', function () { - expect(Restangular.stripRestangular(['test', 'test2'])).toEqual(['test', 'test2']); - }); it('should accept 0 as response', function () { Restangular.one('misc', 'zero').get().then(function (res) { @@ -1378,28 +1413,6 @@ describe('Restangular', function () { $httpBackend.flush(); }); - it('should accept reserved properties as keys in POST data when POSTing raw data', function () { - // https://github.com/mgonto/restangular/issues/374 - $httpBackend.expectPOST('/accounts/1/merge', {ids: 'test', options: 'foo', clone: 'bar'}).respond(200); - var parent = Restangular.restangularizeElement(null, {id: 1}, 'accounts', true); - parent.post('merge', {ids: 'test', options: 'foo', clone: 'bar'}); - $httpBackend.flush(); - }); - - it('should accept reserved properties as a key in POST data when using a restangularized element by configuring restangularFields', function () { - // https://github.com/mgonto/restangular/issues/374 - $httpBackend.expectPOST('/accounts', {ids: 'test', options: 'foo', clone: 'bar'}).respond(200); - Restangular.setRestangularFields({ - ids: '_ids', - clone: '_clone', - options: '_options' - }); - var parent = Restangular.restangularizeElement(null, {ids: 'test', options: 'foo', clone: 'bar'}, 'accounts', false); - parent.save(); - $httpBackend.flush(); - }); - - }); describe('testing normalize url', function () { From 94dc0ebc2bf6e95dc11404d9e7b4ae682e8a4b90 Mon Sep 17 00:00:00 2001 From: David Bonachera Date: Tue, 10 Jan 2017 11:30:08 +0800 Subject: [PATCH 48/55] Update README.md Added an app using restangular --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index cae96082..9fd46c61 100644 --- a/README.md +++ b/README.md @@ -167,6 +167,7 @@ Each time, there're more Production WebApps using `Restangular`. If your webapp * **Life360** is using Restangular to build the WebApp version of their platform * **Thomson Reuters** is using Restangular for the new Webapp they've built * **Quran.com** is using Restangular for their alpha/beta app and soon to be main site +* **[Worldcampus.co](http://www.worldcampus.co)** is using Restangular for their beta international students social network. * **[ENTSO-E Transparency Platform](https://transparency.entsoe.eu)** **[Back to top](#table-of-contents)** From 622710f1fed0030ebdf6a81f97aabe663f6fa651 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Tue, 10 Jan 2017 12:40:54 +0200 Subject: [PATCH 49/55] test(underscore): replace keyBy with each to work with underscore tests --- test/restangularSpec.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/restangularSpec.js b/test/restangularSpec.js index 55fd0917..13452748 100644 --- a/test/restangularSpec.js +++ b/test/restangularSpec.js @@ -244,7 +244,10 @@ describe('Restangular', function () { // https://github.com/mgonto/restangular/issues/374 var restangularFields = Restangular.configuration.restangularFields; // create an object whose keys are the values of the restangularFields - var postData = _.keyBy(restangularFields, function (value) { return value; }); + var postData = {}; + _.each(restangularFields, function (value, key) { + postData.value = value; + }); // we don't want our post data to be treated as a restangularized object postData.restangularized = false; // when posting, restangular shouldn't remove any of our properties @@ -269,7 +272,10 @@ describe('Restangular', function () { var restangularFields = Restangular.configuration.restangularFields; // create an object whose keys are the values of the restangularFields // i.e. {save: "save", clone: "clone", doPOST: "doPOST", ...} - var postData = _.keyBy(restangularFields, function (value) { return value; }); + var postData = {}; + _.each(restangularFields, function (value, key) { + postData.value = value; + }); // we expect the http service to get all of these "original" properties in the data object var expectedData = angular.copy(postData); From bc769dc9325362590f1b95d08f2cb01330a4bc1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Sat, 14 Jan 2017 13:04:52 +0200 Subject: [PATCH 50/55] test(ETags): add tests for ETag headers and setPlainByDefault As reported in #1453 --- test/restangularSpec.js | 140 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 131 insertions(+), 9 deletions(-) diff --git a/test/restangularSpec.js b/test/restangularSpec.js index 13452748..61db1fcb 100644 --- a/test/restangularSpec.js +++ b/test/restangularSpec.js @@ -1501,30 +1501,152 @@ describe('Restangular', function () { }); describe('setPlainByDefault', function () { + var plainByDefaultRestangular; - it('should not add restangularized methods to response object', function () { - var newRes = Restangular.withConfig(function (RestangularConfigurer) { + beforeEach(function () { + plainByDefaultRestangular = Restangular.withConfig(function (RestangularConfigurer) { RestangularConfigurer.setPlainByDefault(true); }); + }); - expect(newRes.configuration.plainByDefault).toEqual(true); + it('should set the property on the configuration', function () { + expect(plainByDefaultRestangular.configuration.plainByDefault).toEqual(true); + }); - newRes.one('accounts', 0).get().then(function (account) { + it('should not add restangularized methods to response object', function () { + plainByDefaultRestangular.one('accounts', 0).get().then(function (account) { expect(account).toEqual(testData.accountsModel[0]); }); - $httpBackend.flush(); }); it('shoud not add restangularized methods to response collection', function () { - var newRes = Restangular.withConfig(function (RestangularConfigurer) { - RestangularConfigurer.setPlainByDefault(true); + plainByDefaultRestangular.all('accounts').getList().then(function (accounts) { + expect(accounts).toEqual(testData.accountsModel); }); + $httpBackend.flush(); + }); - newRes.all('accounts').getList().then(function (accounts) { - expect(accounts).toEqual(testData.accountsModel); + describe('with ETag', function () { + beforeEach(function () { + $httpBackend.whenGET('/accounts').respond( + testData.accountsModel, + {'ETag': 'c11ea3f8-3bfd-4be8-a6a6-501dd831b8a4'} + ); + $httpBackend.whenGET('/accounts/1').respond( + testData.accountsModel[1], + {'ETag': 'bf79b780-f132-4f44-a9eb-7e6eb4f902b2'} + ); + }); + + it('should not add restangularized ETag to response object', function () { + plainByDefaultRestangular.one('accounts', 0).get().then(function (account) { + expect(account).toEqual(testData.accountsModel[0]); + }); + $httpBackend.flush(); + }); + + it('shoud not add restangularized ETag to response collection', function () { + plainByDefaultRestangular.all('accounts').getList().then(function (accounts) { + expect(accounts).toEqual(testData.accountsModel); + }); + $httpBackend.flush(); + }); + }); + }); + + describe('ETags', function () { + beforeEach(function () { + $httpBackend.whenGET('/etagAccounts').respond( + testData.accountsModel, + {'ETag': 'c11ea3f8-3bfd-4be8-a6a6-501dd831b8a4'} + ); + $httpBackend.whenGET('/etagAccounts/1').respond( + testData.accountsModel[1], + {'ETag': 'bf79b780-f132-4f44-a9eb-7e6eb4f902b2'} + ); + }); + + it('should include the ETag in the restangularized element', function () { + Restangular.one('etagAccounts', 1).get().then(function (account) { + expect(account.restangularEtag).toEqual('bf79b780-f132-4f44-a9eb-7e6eb4f902b2'); + }); + $httpBackend.flush(); + }); + it('should include the ETag in the restangularized collection', function () { + Restangular.all('etagAccounts').getList().then(function (accounts) { + expect(accounts.restangularEtag).toEqual('c11ea3f8-3bfd-4be8-a6a6-501dd831b8a4'); }); $httpBackend.flush(); }); + it('should add the If-Match header on PUT requests', function () { + var responseHandler = jasmine.createSpy(); + Restangular.one('etagAccounts', 1).get().then(responseHandler); + $httpBackend.flush(); + + var account = responseHandler.calls.argsFor(0)[0]; + $httpBackend.expect( + 'PUT', + '/etagAccounts/1', + testData.accountsModel[1], + function (headers) { + return headers['If-Match'] === 'bf79b780-f132-4f44-a9eb-7e6eb4f902b2'; + } + ).respond(200); + account.save(); + $httpBackend.flush(); + }); + it('should add the If-Match header on DELETE requests', function () { + var responseHandler = jasmine.createSpy(); + Restangular.one('etagAccounts', 1).get().then(responseHandler); + $httpBackend.flush(); + + var account = responseHandler.calls.argsFor(0)[0]; + $httpBackend.expect( + 'DELETE', + '/etagAccounts/1', + testData.accountsModel[1], + function (headers) { + return headers['If-Match'] === 'bf79b780-f132-4f44-a9eb-7e6eb4f902b2'; + } + ).respond(200); + account.remove(); + $httpBackend.flush(); + }); + it('should add the If-None-Match header on GET requests for elements', function () { + var responseHandler = jasmine.createSpy(); + Restangular.one('etagAccounts', 1).get().then(responseHandler); + $httpBackend.flush(); + + var account = responseHandler.calls.argsFor(0)[0]; + $httpBackend.expect( + 'GET', + '/etagAccounts/1', + undefined, + function (headers) { + return headers['If-None-Match'] === 'bf79b780-f132-4f44-a9eb-7e6eb4f902b2'; + } + ).respond(200); + account.get(); + $httpBackend.flush(); + }); + it('should add the If-None-Match header on GET requests for collections', function () { + var responseHandler = jasmine.createSpy(); + Restangular.all('etagAccounts').getList().then(responseHandler); + $httpBackend.flush(); + + var accounts = responseHandler.calls.argsFor(0)[0]; + $httpBackend.expect( + 'GET', + '/etagAccounts', + undefined, + function (headers) { + return headers['If-None-Match'] === 'c11ea3f8-3bfd-4be8-a6a6-501dd831b8a4'; + } + ).respond(200); + accounts.getList(); + $httpBackend.flush(); + }); + }); }); From aac96122a2480ed1538b18aefb56e844f6fc0b9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Sat, 14 Jan 2017 19:28:53 +0200 Subject: [PATCH 51/55] test(getRestangularUrl): add tests for getRestangularUrl As per #1421 --- test/restangularSpec.js | 80 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 72 insertions(+), 8 deletions(-) diff --git a/test/restangularSpec.js b/test/restangularSpec.js index 13452748..c648b0c8 100644 --- a/test/restangularSpec.js +++ b/test/restangularSpec.js @@ -1075,23 +1075,87 @@ describe('Restangular', function () { }); describe('getRestangularUrl', function () { + it('should get the URL for the current object', function () { + var element = Restangular.one('accounts', 123); + expect(element.getRestangularUrl()).toEqual('/accounts/123'); + }); + it('should not include query parameters', function () { + var responseHandler = jasmine.createSpy(), element; + $httpBackend.expectGET('/accounts/123?query=params').respond({id: 123, name: 'account123'}); + Restangular.one('accounts', 123).get({query: 'params'}).then(responseHandler); + $httpBackend.flush(); + + element = responseHandler.calls.argsFor(0)[0]; + expect(element.getRestangularUrl()).toEqual('/accounts/123'); + }); + it('should be the same for the built resource as for the fetched resource', function () { + var responseHandler = jasmine.createSpy(), element, resource; + $httpBackend.expectGET('/accounts/123').respond({id: 123, name: 'Account 123'}); + resource = Restangular.one('accounts', 123); + resource.get().then(responseHandler); + $httpBackend.flush(); + + element = responseHandler.calls.argsFor(0)[0]; + expect(resource.getRestangularUrl()).toEqual('/accounts/123'); + expect(element.getRestangularUrl()).toEqual('/accounts/123'); + }); + it('should use the id from the response, not the request', function () { + var responseHandler = jasmine.createSpy(), element, resource; + $httpBackend.expectGET('/accounts/123').respond({id: 444, name: 'Account 444'}); + resource = Restangular.one('accounts', 123); + resource.get().then(responseHandler); + $httpBackend.flush(); + + element = responseHandler.calls.argsFor(0)[0]; + expect(resource.getRestangularUrl()).toEqual('/accounts/123'); + expect(element.getRestangularUrl()).toEqual('/accounts/444'); + }); + it('should have an empty id in the URL if the response id is empty', function () { + // https://github.com/mgonto/restangular/issues/1421 + var responseHandler = jasmine.createSpy(), element, resource; + $httpBackend.expectGET('/accounts/123').respond({name: 'Account 444'}); + resource = Restangular.one('accounts', 123); + resource.get().then(responseHandler); + $httpBackend.flush(); + + element = responseHandler.calls.argsFor(0)[0]; + expect(resource.getRestangularUrl()).toEqual('/accounts/123'); + expect(element.getRestangularUrl()).toEqual('/accounts'); + }); + it('should return the generated URL for PUTed elements', function () { + var responseHandler = jasmine.createSpy(), element; + $httpBackend.expectPUT('/accounts/123').respond({id: 123, name: 'Account 123'}); + Restangular.one('accounts', 123).put().then(responseHandler); + $httpBackend.flush(); + + element = responseHandler.calls.argsFor(0)[0]; + expect(element.getRestangularUrl()).toEqual('/accounts/123'); + }); + it('should return the generated URL for POSTed elements', function () { + var responseHandler = jasmine.createSpy(), element; + $httpBackend.expectPOST('/accounts').respond({id: 123, name: 'Account 123'}); + Restangular.restangularizeElement(null, {name: 'Account 123'}, 'accounts', false, false).save().then(responseHandler); + $httpBackend.flush(); + + element = responseHandler.calls.argsFor(0)[0]; + expect(element.getRestangularUrl()).toEqual('/accounts/123'); + }); it('should return the generated URL when you chain Restangular methods together', function () { var restangularSpaces = Restangular.one('accounts', 123).one('buildings', 456).all('spaces'); expect(restangularSpaces.getRestangularUrl()).toEqual('/accounts/123/buildings/456/spaces'); }); - }); - describe('getRestangularUrl with useCannonicalId set to true', function () { - it('should return the generated URL when you chain Restangular methods together', function () { - var R = Restangular.withConfig(function (config) { - config.setUseCannonicalId(true); + describe('with useCannonicalId set to true', function () { + it('should return the generated URL when you chain Restangular methods together', function () { + var R = Restangular.withConfig(function (config) { + config.setUseCannonicalId(true); + }); + var restangularSpaces = R.one('accounts', 123).one('buildings', 456).all('spaces'); + expect(restangularSpaces.getRestangularUrl()).toEqual('/accounts/123/buildings/456/spaces'); }); - var restangularSpaces = R.one('accounts', 123).one('buildings', 456).all('spaces'); - expect(restangularSpaces.getRestangularUrl()).toEqual('/accounts/123/buildings/456/spaces'); }); }); - describe('addElementTransformer', function () { it('should allow for a custom method to be placed at the collection level', function () { var accountsPromise; From c4da3b452be8877ac0590ea36259a93e7fb3df01 Mon Sep 17 00:00:00 2001 From: benmag1 Date: Sun, 15 Jan 2017 15:12:49 +0000 Subject: [PATCH 52/55] Adds a Snyk badge to show you are vulnerability-free --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9fd46c61..08b91860 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![Build Status](https://travis-ci.org/mgonto/restangular.svg?branch=master)](https://travis-ci.org/mgonto/restangular) [![Coverage Status](https://coveralls.io/repos/github/mgonto/restangular/badge.svg?branch=master)](https://coveralls.io/github/mgonto/restangular?branch=master) [![David](https://img.shields.io/david/dev/mgonto/restangular.svg)](https://david-dm.org/mgonto/restangular/?type=dev) +[![Known Vulnerabilities](https://snyk.io/test/github/mgonto/restangular/badge.svg)](https://snyk.io/test/github/mgonto/restangular) [![PayPayl donate button](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=martin%40gon%2eto&lc=US&item_name=Martin%20Gontovnikas¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted "Donate once-off to this project using Paypal") [![Donate on Gittip](http://img.shields.io/gittip/mgonto.svg)](https://www.gittip.com/mgonto/) From 65f500672094ae2f0c406458111849da5ee0c488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Bostr=C3=B6m?= Date: Wed, 18 Jan 2017 21:40:13 +0200 Subject: [PATCH 53/55] docs(readme): add link to ng2-restangular Closes #1318 --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 08b91860..376a0f97 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,12 @@ -Restangular is an AngularJS service that simplifies common GET, POST, DELETE, and UPDATE requests with a minimum of client code. -It's a perfect fit for any WebApp that consumes data from a RESTful API. +Restangular is an AngularJS service that simplifies common GET, POST, DELETE, and UPDATE requests with a minimum of client code. It's a perfect fit for any WebApp that consumes data from a RESTful API. -Try the [live demo on plunkr](http://plnkr.co/edit/8qrGeE?p=preview). It uses the same example as the official [Angular Javascript Project](http://angularjs.org/#wire-up-a-backend), but with Restangular! +**Note This version of Restangular [only supports Angular 1](#supported-angular-versions). For an Angular 2 version of Restangular, check out [ng2-restangular](https://github.com/2muchcoffeecom/ng2-restangular).** It's a separate project with different maintainers, so issues regarding ng2-restangular should be reported [over there](https://github.com/2muchcoffeecom/ng2-restangular/issues) :wink: + +Learn Restangular! Try the [live demo on plunkr](http://plnkr.co/edit/8qrGeE?p=preview). It uses the same example as the official [Angular Javascript Project](http://angularjs.org/#wire-up-a-backend), but with Restangular! Or watch [a video introduction of a talk I gave at Devoxx France](http://www.parleys.com/play/535a189ee4b0c5ba17d43455/chapter1/about) about Restangular. -Watch [a video introduction of a talk I gave at Devoxx France](http://www.parleys.com/play/535a189ee4b0c5ba17d43455/chapter1/about) about Restangular. #Table of contents From c0d09afcd01f6c507c591932f08c9ad2af8aa520 Mon Sep 17 00:00:00 2001 From: 2muchcoffee Date: Mon, 17 Apr 2017 16:26:29 +0300 Subject: [PATCH 54/55] update readme rename ng2-restangular to ngx-restangular We had to rename ng2-restangular to ngx-restangular due to Angular SemVer --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 376a0f97..69a5cc29 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Restangular is an AngularJS service that simplifies common GET, POST, DELETE, and UPDATE requests with a minimum of client code. It's a perfect fit for any WebApp that consumes data from a RESTful API. -**Note This version of Restangular [only supports Angular 1](#supported-angular-versions). For an Angular 2 version of Restangular, check out [ng2-restangular](https://github.com/2muchcoffeecom/ng2-restangular).** It's a separate project with different maintainers, so issues regarding ng2-restangular should be reported [over there](https://github.com/2muchcoffeecom/ng2-restangular/issues) :wink: +**Note This version of Restangular [only supports Angular 1](#supported-angular-versions). For an Angular 2+ version of Restangular, check out [ngx-restangular](https://github.com/2muchcoffeecom/ngx-restangular).** It's a separate project with different maintainers, so issues regarding ngx-restangular should be reported [over there](https://github.com/2muchcoffeecom/ngx-restangular/issues) :wink: Learn Restangular! Try the [live demo on plunkr](http://plnkr.co/edit/8qrGeE?p=preview). It uses the same example as the official [Angular Javascript Project](http://angularjs.org/#wire-up-a-backend), but with Restangular! Or watch [a video introduction of a talk I gave at Devoxx France](http://www.parleys.com/play/535a189ee4b0c5ba17d43455/chapter1/about) about Restangular. From a81d594f12fc88ba4b16e35ff328f6c7fddfb741 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Alberto?= Date: Thu, 27 Apr 2017 14:57:00 -0300 Subject: [PATCH 55/55] Docs corrections (#1472) * Anchor correction * Anchor correction --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 69a5cc29..aea15823 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -#Restangular +# Restangular [![Build Status](https://travis-ci.org/mgonto/restangular.svg?branch=master)](https://travis-ci.org/mgonto/restangular) [![Coverage Status](https://coveralls.io/repos/github/mgonto/restangular/badge.svg?branch=master)](https://coveralls.io/github/mgonto/restangular?branch=master) @@ -17,7 +17,7 @@ Restangular is an AngularJS service that simplifies common GET, POST, DELETE, an Learn Restangular! Try the [live demo on plunkr](http://plnkr.co/edit/8qrGeE?p=preview). It uses the same example as the official [Angular Javascript Project](http://angularjs.org/#wire-up-a-backend), but with Restangular! Or watch [a video introduction of a talk I gave at Devoxx France](http://www.parleys.com/play/535a189ee4b0c5ba17d43455/chapter1/about) about Restangular. -#Table of contents +# Table of contents - [Restangular](#restangular) - [Differences with $resource](#differences-with-resource) @@ -138,7 +138,7 @@ $scope.user.one('messages', 123).one('from', 123).getList('unread'); **[Back to top](#table-of-contents)** -#How do I add this to my project? +## How do I add this to my project? You can download this by: @@ -155,7 +155,7 @@ You can download this by: **[Back to top](#table-of-contents)** -#Dependencies +## Dependencies Restangular depends on Angular and Lodash (or Underscore). @@ -173,7 +173,7 @@ Each time, there're more Production WebApps using `Restangular`. If your webapp **[Back to top](#table-of-contents)** -#Starter Guide +# Starter Guide ## Quick Configuration (For Lazy Readers) This is all you need to start using all the basic Restangular features.