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

Add hasBody to ngResource action configuration #12181

Closed
wants to merge 10 commits into from

Conversation

kaufholdr
Copy link
Contributor

The use of hasBody would allow developers of REST interfaces to decide if a request body is necessary or not on a per method basis.

The way ngResource currently handles request bodies in custom requests make the definition of custom requests pretty useless. It’s not possible to transfere a request body through a other requests than POST, PUT, PATCH. Or am I wrong?

It may be true that some browser implementations are also brocken when it comes to methods like DELETE but this broken behavior should not be enforced by angular.

Even as i read the #3207 after creating the patch its somehow picking up pmariduenas comment on creating a enableDeleteRequestBody. Its just the generic version for all custom requests.

@caitp
Copy link
Contributor

caitp commented Jun 21, 2015

I don't think custom http methods are necessarily recommended for REST services. This does need some consideration

@kaufholdr
Copy link
Contributor Author

I thought that the decision of enabling custom methods in ngResource has already been made as it is possible right now.

The current behavior of silently dropping the content body for custom requests is just weired (it took me several hours of debugging to locate the problem in angularjs). For me it feels like a bug and not a feature.

Do you think that the inclusion of this patch could be harmfull to the functionality of ngResource?

@caitp
Copy link
Contributor

caitp commented Jun 21, 2015

Custom methods and custom http methods are different things, are there any other methods you want a body for? I know people have asked for bodies on DELETE requests

@kaufholdr
Copy link
Contributor Author

I would not include a body in the DELETE request as I currently have no use for it but i would like to include a body in a LOGIN request which carries the user information in a JSON (not only username an pasword). Some other custom http methods where i would like to include a body are: CLOCKIN, CLOCKOUT, STORNO, ASSIGN, NOTIFY, DISABLE but they are all specific to the project I'm working on. That's why I would like to have a generic attribute on the action specification.

@yathos
Copy link

yathos commented Jul 8, 2015

I created a modified bower-angular-resource version that includes the requested fix in version 1.4.1 https://github.com/yathos/bower-angular-resource If someone would like to test it.

@kaufholdr
Copy link
Contributor Author

Please tell me if there is anything that I could do to get this pull request included.

@Narretz
Copy link
Contributor

Narretz commented Aug 8, 2015

Honestly, I don't think custom HTTP methods are a big enough use case to warrant inclusion into the angular core. Especially since $resource is pretty much built for "normal" REST interfaces. If you are doing something special, the recommendation has always been to built your own wrapper around $http.

@ElayneB
Copy link

ElayneB commented Oct 26, 2016

+1 for this feature. We have built a client on top of $resource and we consider DELETE with bodies to be part of "normal" rest interfaces.

The use case is bulk relationship delete.
E.G.
DELETE model/:id/relatedModel body: [relatedModelId, relatedModelId, ..]

@kaufholdr
Copy link
Contributor Author

I'm still interested in getting this pull request integrated.

@gkalpak gkalpak modified the milestones: 1.6.x (post 1.6.0), Ice Box Dec 5, 2016
@gkalpak
Copy link
Member

gkalpak commented Dec 5, 2016

Let's consider this after 1.6.0 is released and decide what direction to go (either merge or close).
It is a minimal addition and doesn't seem to do any harm; plus it could take care of the recurring debate about whether delete requests should have a body or not.

@@ -640,7 +642,7 @@ angular.module('ngResource', ['ng']).
};

forEach(actions, function(action, name) {
var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method);
var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method) || action.hasBody === true;
Copy link
Member

Choose a reason for hiding this comment

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

I would find something like the following more intuitive:

var hasBody = action.hasBody === true || (action.hasBody !== false && /^(POST|PUT|PATCH)$/i.test(action.method));

Am I missing any reason why we shouldn't allow people to overwrite hasBody for POST, PUT, PATCH? (Not that they should.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm fine with both versions of the statement. Should I modify the patch?

Copy link
Member

@gkalpak gkalpak Dec 6, 2016

Choose a reason for hiding this comment

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

Unless you can think of a reason not to 😃

Copy link
Contributor Author

@kaufholdr kaufholdr Dec 6, 2016

Choose a reason for hiding this comment

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

I had to like twice to see the error but the correct statement would be:
var hasBody = action.hasBody === true || (action.hasBody !== true && /^(POST|PUT|PATCH)$/i.test(action.method));
right?

Copy link
Member

Choose a reason for hiding this comment

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

I actually meant it the way I wrote it above. Your version is the same as:

var hasBody = action.hasBody === true || /^(POST|PUT|PATCH)$/i.test(action.method);

...which is not what we want.

@googlebot
Copy link

We found a Contributor License Agreement for you (the sender of this pull request), but were unable to find agreements for the commit author(s). If you authored these, maybe you used a different email address in the git commits than was used to sign the CLA (login here to double check)? If these were authored by someone else, then they will need to sign a CLA as well, and confirm that they're okay with these being contributed to Google.

1 similar comment
@googlebot
Copy link

We found a Contributor License Agreement for you (the sender of this pull request), but were unable to find agreements for the commit author(s). If you authored these, maybe you used a different email address in the git commits than was used to sign the CLA (login here to double check)? If these were authored by someone else, then they will need to sign a CLA as well, and confirm that they're okay with these being contributed to Google.

@googlebot googlebot added cla: no and removed cla: yes labels Dec 6, 2016
@googlebot
Copy link

CLAs look good, thanks!

1 similar comment
@googlebot
Copy link

CLAs look good, thanks!

@kaufholdr
Copy link
Contributor Author

Is there anything more that I can do to get this feature included?

Copy link
Member

@gkalpak gkalpak left a comment

Choose a reason for hiding this comment

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

We also need tests for this.

@@ -193,6 +193,8 @@ function shallowClearAndCopy(src, dst) {
* - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods -
* `response` and `responseError`. Both `response` and `responseError` interceptors get called
* with `http response` object. See {@link ng.$http $http interceptors}.
* - **`hasBody`** - `{boolean}` - allows to specify if a request body should be included or not.
* If not specified only POST,PUT and PATCH will have a request.
Copy link
Member

Choose a reason for hiding this comment

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

Nit: Missing whitespace after ,.

@gkalpak
Copy link
Member

gkalpak commented Feb 21, 2017

@kaufholdr, feel free to ping me once you've made the requested changes, so I can take another look.

@kaufholdr
Copy link
Contributor Author

Sure. I have added the required test cases. Are they sufficient?

Copy link
Member

@gkalpak gkalpak left a comment

Choose a reason for hiding this comment

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

We also need a test for POST/PUT/PATCH methods with hasBody: false.

@@ -193,6 +193,8 @@ function shallowClearAndCopy(src, dst) {
* - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods -
* `response` and `responseError`. Both `response` and `responseError` interceptors get called
* with `http response` object. See {@link ng.$http $http interceptors}.
* - **`hasBody`** - `{boolean}` - allows to specify if a request body should be included or not.
* If not specified only POST, PUT and PATCH will have a request.
Copy link
Member

Choose a reason for hiding this comment

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

only POST, PUT and PATCH will have a request --> only POST, PUT and PATCH requests will have a body

@@ -97,6 +97,43 @@ describe('basic usage', function() {
$httpBackend.flush();
});

it('should include a request body when calling custom delete with hasBody is true', function() {
Copy link
Member

Choose a reason for hiding this comment

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

custom delete --> custom method

Can you also test with another method (in the same test), just to avoid giving the impression this is just about DELETE.

Extended test case with a custom CREATE request.
Improved documentation of hasBody.
@kaufholdr
Copy link
Contributor Author

I have added the requested changes.

Should I squash down my changes to one commit on completion or would you like to take them as several commits if it's ready?


var postResponse = R.post();
var putResponse = R.put();
var patchResponse = R.patch();
Copy link
Member

Choose a reason for hiding this comment

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

Would these 3 calls fail if hasBody was not set? I don't think so (but I haven't tried it either). Can you check?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No they don't fail if hasBody is not set. This is a bug or a feature of ngResource not enforcing the described api.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I haven't found a way to distinguish between request parameters and the request body in httpBackend.

Did I miss something?

Copy link
Member

@gkalpak gkalpak Mar 12, 2017

Choose a reason for hiding this comment

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

If you pass two params (e.g. R.post(null, {foo: 'bar'})), then the first one is interpreted as params and the second one as data.

For example, the following test currently fails, because {id: -1} is not undefined. If something similar passes with your PR, then we are good:

    $httpBackend.expectPOST('/foo').respond(function(method, url, data) {
      expect(data).not.toBeUndefined();
      return [200, {id: 42}];
    });

    var R = $resource('/foo');
    var response = R.save(null, {id: -1});

    $httpBackend.flush();
    expect(response.id).toBe(42);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sry for the delayed response on my side. I have merged your proposed test case into the test method with hasBody true. The only problem for me was that I couldn't reproduce the failing of the test case. (Even when I used your version.)

Can you see my error or give me the error that you get?

Copy link
Member

Choose a reason for hiding this comment

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

I was probably not clear. What I meant is that my test fails with the code currently in master (i.e. without your PR), which is expected, because we don't support the hasBody yet. If the test passes with your PR, then it verifies that the feature you added (hasBody) does indeed work for PUT/POST/PATCH requests too, so it is good to be merged.

it('should include a request body when calling custom method with hasBody is true', function() {
var instant = {name: 'info.txt', value: 'V2hlbiB0aGUgdGltZSBlbmRzLg=='};
var fid = 42;
var created = {fid: fid, filname: 'fooresource', value: 'V2hlbiB0aGUgdGltZSBlbmRzLg=='};
Copy link
Member

Choose a reason for hiding this comment

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

Nit: You don't really need these 2 extra variables (fid, created). You could inline them (and only keep the necessary props) to avoid visual noise.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have cleaned up those variables.

@gkalpak
Copy link
Member

gkalpak commented Mar 6, 2017

You don't need to squash them, btw. We can take care of it while merging.

@kaufholdr
Copy link
Contributor Author

Is there anything that I should fix in this Pull request right now?

@gkalpak
Copy link
Member

gkalpak commented Mar 12, 2017

@kaufholdr, that should be the last bit 😃

@@ -102,6 +102,10 @@ describe('basic usage', function() {
var condition = {at: '2038-01-19 03:14:08'};

$httpBackend.expect('CREATE', '/fooresource', instant).respond({fid: 42});
$httpBackend.expectPOST('/fooresource').respond(function(method, url, data) {
Copy link
Member

Choose a reason for hiding this comment

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

This is not needed here. POST is not a custom method (which what this test is about).

expect(deleteResponse.$resolved).toBe(true);
});

it('should not include a request body if hasBody is false on POST, PUT and PATCH', function() {
$httpBackend.expect('POST', '/foo').respond({});
$httpBackend.expectPOST('/foo').respond(function(method, url, data) {
Copy link
Member

Choose a reason for hiding this comment

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

Make this change to all three methods. E.g. something like:

function verifyRequest(method, url , data) {
  expect(data).toBeUndefined();
  return [200, {id: 42}];
}

$httpBackend.expect('POST', '/foo').respond(verifyRequest);
$httpBackend.expect('PUT', '/foo').respond(verifyRequest);
$httpBackend.expect('PATCH', '/foo').respond(verifyRequest);

...

@kaufholdr
Copy link
Contributor Author

Fell free to tell me if there is still something missing. :-)


expect(postResponse.id).toBe(42);
expect(putResponse.$resolved).toBe(true);
expect(patchResponse.$resolved).toBe(true);
Copy link
Member

Choose a reason for hiding this comment

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

Please, change these two lines to ....id).toBe(42).

@kaufholdr
Copy link
Contributor Author

Thank. You. :-)

@gkalpak gkalpak closed this in d96e58f Mar 21, 2017
@gkalpak
Copy link
Member

gkalpak commented Mar 21, 2017

I tweaked the docs a little bit and merged. Thank you for working on this, @kaufholdr 👍

gkalpak pushed a commit that referenced this pull request Mar 21, 2017
By default, only `PUT`, `POST` and `PATCH` requests have a body, but you can use
`hasBody` to configure any action to either have or not have a body, regardless
of its HTTP method.

Fixes #10128

Closes #12181
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants