Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Commit

Permalink
feat($resource): collapse empty suffix parameters correctly
Browse files Browse the repository at this point in the history
Previously only repeated `/` delimiters were collapsed into a
single `/`.  Now, the sequence `/.` at the end of the template, i.e.
only followed by a sequence of word characters, is collapsed into a single
`.`. This makes it easier to support suffixes on resource URLs.
For example, given a resource template of `/some/path/:id.:format`, if
the `:id` is `""` but format `"json"` then the URL is now
`/some/path.json`, rather than `/some/path/.json`.

BREAKING CHANGE: A `/` followed by a `.`, in the last segment of the
URL template is now collapsed into a single `.` delimiter. For example:
`users/.json` will become `users.json`. If your server relied upon this
sequence then it will no longer work. In this case you can now escape the
`/.` sequence with `/\.`
  • Loading branch information
ruprict authored and petebacondarwin committed May 14, 2013
1 parent c32a859 commit 5306136
Show file tree
Hide file tree
Showing 2 changed files with 169 additions and 13 deletions.
15 changes: 14 additions & 1 deletion src/ngResource/resource.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@
* `http://example.com:8080/api`), you'll need to escape the colon character before the port
* number, like this: `$resource('http://example.com\\:8080/api')`.
*
* If you are using a url with a suffix, just add the suffix, like this:
* `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')
* or even `$resource('http://example.com/resource/:resource_id.:format')`
* If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be
* collapsed down to a single `.`. If you need this sequence to appear and not collapse then you
* can escape it with `/\.`.
*
* @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
* `actions` methods. If any of the parameter value is a function, it will be executed every time
* when a param value needs to be obtained for a request (unless the param was overridden).
Expand Down Expand Up @@ -356,7 +363,13 @@ angular.module('ngResource', ['ng']).
});

// strip trailing slashes and set the url
config.url = url.replace(/\/+$/, '');
url = url.replace(/\/+$/, '');
// then replace collapse `/.` if found in the last URL path segment before the query
// E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x`

This comment has been minimized.

Copy link
@metamatt

metamatt Jul 24, 2013

Contributor

This example is wrong, right? The left side should say id/.format, not id./format.

url = url.replace(/\/\.(?=\w+($|\?))/, '.');
// replace escaped `/\.` with `/.`
config.url = url.replace(/\/\\\./, '/.');


// set params - delegate param encoding to $http
forEach(params, function(value, key){
Expand Down
167 changes: 155 additions & 12 deletions test/ngResource/resourceSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,13 @@ describe("resource", function() {
it('should not ignore leading slashes of undefinend parameters that have non-slash trailing sequence', function() {
var R = $resource('/Path/:a.foo/:b.bar/:c.baz');

$httpBackend.when('GET', '/Path/.foo/.bar/.baz').respond('{}');
$httpBackend.when('GET', '/Path/0.foo/.bar/.baz').respond('{}');
$httpBackend.when('GET', '/Path/false.foo/.bar/.baz').respond('{}');
$httpBackend.when('GET', '/Path/.foo/.bar/.baz').respond('{}');
$httpBackend.when('GET', '/Path/.foo/.bar/.baz').respond('{}');
$httpBackend.when('GET', '/Path/1.foo/.bar/.baz').respond('{}');
$httpBackend.when('GET', '/Path/2.foo/3.bar/.baz').respond('{}');
$httpBackend.when('GET', '/Path/.foo/.bar.baz').respond('{}');
$httpBackend.when('GET', '/Path/0.foo/.bar.baz').respond('{}');
$httpBackend.when('GET', '/Path/false.foo/.bar.baz').respond('{}');
$httpBackend.when('GET', '/Path/.foo/.bar.baz').respond('{}');
$httpBackend.when('GET', '/Path/.foo/.bar.baz').respond('{}');
$httpBackend.when('GET', '/Path/1.foo/.bar.baz').respond('{}');
$httpBackend.when('GET', '/Path/2.foo/3.bar.baz').respond('{}');
$httpBackend.when('GET', '/Path/4.foo/.bar/5.baz').respond('{}');
$httpBackend.when('GET', '/Path/6.foo/7.bar/8.baz').respond('{}');

Expand Down Expand Up @@ -692,17 +692,160 @@ describe("resource", function() {
expect(person.id).toEqual(456);
});

describe('suffix parameter', function() {

describe('query', function() {
it('should add a suffix', function() {
$httpBackend.expect('GET', '/users.json').respond([{id: 1, name: 'user1'}]);
var UserService = $resource('/users/:id.json', {id: '@id'});
var user = UserService.query();
$httpBackend.flush();
expect(user).toEqualData([{id: 1, name: 'user1'}]);
});

it('should not require it if not provided', function(){
$httpBackend.expect('GET', '/users.json').respond([{id: 1, name: 'user1'}]);
var UserService = $resource('/users.json');
var user = UserService.query();
$httpBackend.flush();
expect(user).toEqualData([{id: 1, name: 'user1'}]);
});

it('should work when query parameters are supplied', function() {
$httpBackend.expect('GET', '/users.json?red=blue').respond([{id: 1, name: 'user1'}]);
var UserService = $resource('/users/:user_id.json', {user_id: '@id'});
var user = UserService.query({red: 'blue'});
$httpBackend.flush();
expect(user).toEqualData([{id: 1, name: 'user1'}]);
});

it('should work when query parameters are supplied and the format is a resource parameter', function() {
$httpBackend.expect('GET', '/users.json?red=blue').respond([{id: 1, name: 'user1'}]);
var UserService = $resource('/users/:user_id.:format', {user_id: '@id', format: 'json'});
var user = UserService.query({red: 'blue'});
$httpBackend.flush();
expect(user).toEqualData([{id: 1, name: 'user1'}]);
});

it('should work with the action is overriden', function(){
$httpBackend.expect('GET', '/users.json').respond([{id: 1, name: 'user1'}]);
var UserService = $resource('/users/:user_id', {user_id: '@id'}, {
query: {
method: 'GET',
url: '/users/:user_id.json',
isArray: true
}
});
var user = UserService.query();
$httpBackend.flush();
expect(user).toEqualData([ {id: 1, name: 'user1'} ]);
});
});

describe('get', function(){
it('should add them to the id', function() {
$httpBackend.expect('GET', '/users/1.json').respond({id: 1, name: 'user1'});
var UserService = $resource('/users/:user_id.json', {user_id: '@id'});
var user = UserService.get({user_id: 1});
$httpBackend.flush();
expect(user).toEqualData({id: 1, name: 'user1'});
});

it('should work when an id and query parameters are supplied', function() {
$httpBackend.expect('GET', '/users/1.json?red=blue').respond({id: 1, name: 'user1'});
var UserService = $resource('/users/:user_id.json', {user_id: '@id'});
var user = UserService.get({user_id: 1, red: 'blue'});
$httpBackend.flush();
expect(user).toEqualData({id: 1, name: 'user1'});
});

it('should work when the format is a parameter', function() {
$httpBackend.expect('GET', '/users/1.json?red=blue').respond({id: 1, name: 'user1'});
var UserService = $resource('/users/:user_id.:format', {user_id: '@id', format: 'json'});
var user = UserService.get({user_id: 1, red: 'blue'});
$httpBackend.flush();
expect(user).toEqualData({id: 1, name: 'user1'});
});

it('should work with the action is overriden', function(){
$httpBackend.expect('GET', '/users/1.json').respond({id: 1, name: 'user1'});
var UserService = $resource('/users/:user_id', {user_id: '@id'}, {
get: {
method: 'GET',
url: '/users/:user_id.json'
}
});
var user = UserService.get({user_id: 1});
$httpBackend.flush();
expect(user).toEqualData({id: 1, name: 'user1'});
});
});

describe("save", function() {
it('should append the suffix', function() {
$httpBackend.expect('POST', '/users.json', '{"name":"user1"}').respond({id: 123, name: 'user1'});
var UserService = $resource('/users/:user_id.json', {user_id: '@id'});
var user = UserService.save({name: 'user1'}, callback);
expect(user).toEqualData({name: 'user1'});
expect(callback).not.toHaveBeenCalled();
$httpBackend.flush();
expect(user).toEqualData({id: 123, name: 'user1'});
expect(callback).toHaveBeenCalledOnce();
expect(callback.mostRecentCall.args[0]).toEqual(user);
expect(callback.mostRecentCall.args[1]()).toEqual({});
});

it('should append when an id is supplied', function() {
$httpBackend.expect('POST', '/users/123.json', '{"id":123,"name":"newName"}').respond({id: 123, name: 'newName'});
var UserService = $resource('/users/:user_id.json', {user_id: '@id'});
var user = UserService.save({id: 123, name: 'newName'}, callback);
expect(callback).not.toHaveBeenCalled();
$httpBackend.flush();
expect(user).toEqualData({id: 123, name: 'newName'});
expect(callback).toHaveBeenCalledOnce();
expect(callback.mostRecentCall.args[0]).toEqual(user);
expect(callback.mostRecentCall.args[1]()).toEqual({});
});

it('should append when an id is supplied and the format is a parameter', function() {
$httpBackend.expect('POST', '/users/123.json', '{"id":123,"name":"newName"}').respond({id: 123, name: 'newName'});
var UserService = $resource('/users/:user_id.:format', {user_id: '@id', format: 'json'});
var user = UserService.save({id: 123, name: 'newName'}, callback);
expect(callback).not.toHaveBeenCalled();
$httpBackend.flush();
expect(user).toEqualData({id: 123, name: 'newName'});
expect(callback).toHaveBeenCalledOnce();
expect(callback.mostRecentCall.args[0]).toEqual(user);
expect(callback.mostRecentCall.args[1]()).toEqual({});
});
});

describe('escaping /. with /\\.', function() {
it('should work with query()', function() {
$httpBackend.expect('GET', '/users/.json').respond();
$resource('/users/\\.json').query();
});
it('should work with get()', function() {
$httpBackend.expect('GET', '/users/.json').respond();
$resource('/users/\\.json').get();
});
it('should work with save()', function() {
$httpBackend.expect('POST', '/users/.json').respond();
$resource('/users/\\.json').save({});
});
});
});

describe('action-level url override', function() {

it('should support overriding url template with static url', function() {
$httpBackend.expect('GET', '/override-url?type=Customer&typeId=123').respond({id: 'abc'});
var TypeItem = $resource('/:type/:typeId', {type: 'Order'}, {
get: {
method: 'GET',
params: {type: 'Customer'},
url: '/override-url'
}
get: {
method: 'GET',
params: {type: 'Customer'},
url: '/override-url'
}
});
var item = TypeItem.get({typeId: 123});
$httpBackend.flush();
Expand Down

1 comment on commit 5306136

@e-oz
Copy link

@e-oz e-oz commented on 5306136 May 23, 2013

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's wrong behavior. In URL / means much more, than .. So if you want (for some interesting reason) to replace this part of URL, you should keep / anyway. And I don't understand why you think that angular should change URL at all.

Please sign in to comment.