Skip to content
This repository

Use PATCH verb instead of PUT for "update" #348

Closed
dlee opened this Issue April 29, 2011 · 37 comments

8 participants

dlee José Valim Мар'ян Крекотень (Marjan Krekoteń) Xavier Noria Damien Mathieu Nick Sutterer Diego Carrion Myron Marston
dlee
dlee commented April 29, 2011

Rails should map the PATCH HTTP verb to the update action to respect proper HTTP/REST semantics.

The current mapping of PUT to update is an imprecise mapping of HTTP verbs ("REST") to CRUD. PUT more accurately means "place" or "replace", and can even be used to create new records at a specific URI.

From the HTTP specs for PATCH:

The PUT method is already defined to overwrite a resource
with a complete new body, and cannot be reused to do partial changes.
Otherwise, proxies and caches, and even clients and servers, may get
confused as to the result of the operation.

Furthermore, Rails convention for the update action is not idempotent--a requirement for PUT according to the specs.

As a solution, I propose that PATCH requests be routed to the update action alongside PUT. Furthermore, PATCH should be made the default form method when editing existing model records, since 99% of the time, users want to modify an existing record instead of replacing it. In later versions, PUT can be deprecated or mapped to a different action with proper semantics.

This change should not break browser compatibility since browsers are already using _method param override for PUT; changing that to PATCH should be simple. Furthermore, leaving the default PUT routing (alongside PATCH) in restful routes should make this change backwards compatible for most users.

If this is something the core developers agree with, I can contribute a ... um ... patch.

Reference: http://tools.ietf.org/html/rfc5789

Мар'ян Крекотень (Marjan Krekoteń)

In most cases you are actually replacing data and not updating it. Your code renders full resource data, user modifies it and sends it back. That is what PUT designed for. PATCH meant to be used when partial data sent back to server.

Also I do not agree that update action is not idempotent.

Xavier Noria
Owner
fxn commented April 29, 2011

While PATCH should definitely be in the radar, let me say that Rails does not force you to do partial updates.

For example, if an invoice has a "paid" flag, and you have a check box to toggle it via Ajax, it is up to you which request is that. In particular, a REST design would have the "paid" flag of an existing invoice exposed as a resource, with its own URI and actions. The fact that the boolean may belong to the invoices table is irrelevant from a REST viewpoint.

So, in my view the current features are fine, I would not change the default method for edit actions.

How to add support for PATCH to the framework is something to be discussed.

dlee
dlee commented April 29, 2011

I would argue that the vast majority of users implement update as "updating" the resource and not "replacing" it. Even the word "update" says, "update".

Furthermore, convention and the myriads of Rails examples (official and unofficial) show a controller's update calling an ActiveRecord object's update, which is definitely meant to be PATCH-type action and not a PUT-type action.

dlee
dlee commented April 29, 2011

@fxn I would agree to a certain degree that Rails does not force you to do partial updates. ActiveRecord's semantics for update is partial updates, and that's the method called the majority of time in an "update" action.

Regardless of whether it's forced, it is acceptable to do partial updates in Rails, and that means we should not be using PUT, because it is **un**acceptable to do partial updates in PUT.

Damien Mathieu
Collaborator

Based on the way most forms currently exists, I believe 97% of currently PUT requests should be PATCH.

Please don't forget though that this is a very big change, for any application. It shouldn't be applied on a minor version.
I don't see this kind of change in any version earlier to 4.0.

Xavier Noria
Owner
fxn commented April 29, 2011

@dlee but what do you understand by "replacing"?

The "body" of a resource is something you define. In particular the ID or whatever identifier you use in the URL does not need to change. The same way the name of a static file whose contents you are "replacing" need no change.

You are not forced by HTTP to delete and create database rows. Database rows are implementation. Resources are conceptual. The standard update_attributes idiom is OK.

Мар'ян Крекотень (Marjan Krekoteń)

@fxn that is what I was trying to say, so +1 :)

dlee
dlee commented April 29, 2011

@dmathieu My proposal is to put PATCH alongside PUT in the routes and to make PATCH the default method in forms. This should not break backwards compatibility for the majority of applications.

Later releases of 3.x can then deprecate the PUT. 4.0 can remove it completely.

@fxn I agree that concept and implementation are two different things. I argue that in Rails, the concept of "update" means partial updates. I also agree with dmathieu that 97% of how "update" is used is in the partial update way.

Xavier Noria
Owner
fxn commented April 29, 2011

@dlee there's no disagreement in that Rails does not support PATCH.

dlee
dlee commented April 29, 2011

@fxn rereading your comment, I think you're misunderstanding what I meant by "replacing" the content on a PUT. I'm not saying that PUT should issue a new URI or a resource ID. I'm saying that the contents of the resource are completely replaced (not updated or patched) by a PUT.

Xavier Noria
Owner
fxn commented April 29, 2011

@dlee do you agree that if you get the full public resource representation (all public fields, no ID, no timestamps), you are updating the resource with update_attributes as per PUT semantics?

Xavier Noria
Owner
fxn commented April 29, 2011

@dlee another thought: if it was true that PUT can be seen as a particular case of PATCH, then I agree with your proposal in principle, including the change in the default.

dlee
dlee commented April 29, 2011

@fxn If you are replacing every single field of a resource, and you do that every time without exception, then I agree that that's PUT semantics.

However, if you only update some of the fields, then that's PATCH semantics.

dlee
dlee commented April 29, 2011

Hopefully the nail in the coffin from the HTTP spec (emphasis mine):

The PUT method is already defined to overwrite a resource
with a complete new body, and cannot be reused to do partial changes.

Xavier Noria
Owner
fxn commented April 29, 2011

@dlee Yes I know.

My only concern is that the RFC does not seem to allow partial bodies, or at least that's not totally clear to me. It says you need to send a description of how to modify the resource... Do you have real world example of valid PATCH requests?

dlee
dlee commented April 29, 2011

@fxn in my understanding, nothing needs to change from the way we currently use POST params. The body of a PATCH request is system-specific and unspecified in the HTTP spec.

Xavier Noria
Owner
fxn commented April 29, 2011

@dlee if that's the case PUT seems to be unnecessary for most web programming, you could perfectly update always using PATCH, partial or full, doesn't matter most of the time.

Damien Mathieu
Collaborator

We could then keep the default update action, which would by default support PUT and PATCH.
And if the controller responds to an other action (like replace for example), it'd be used for PUT actions and only update would be used for PATCH.

This would keep backward compatibility and allow advanced customization of the thing.

dlee
dlee commented April 29, 2011

@fxn yes, you should be able to use PATCH whenever you want to update a resource. The only thing PUT can do that PATCH cannot is to place a new resource at an explicit location:

PUT /posts/my_new_post

But this ability of PUT is not being used by Rails.

@dmathieu yes, that's my proposal. I think it should be possible in 3.x. In 4.0 PUT should by default not route to update

Xavier Noria
Owner
fxn commented April 29, 2011

@dlee You probably mean that's not what the resources macro produces. Rails does support that use case of PUT via the put macro for example.

Nick Sutterer

The problem is not PUT or PATCH but #update_attributes which lotta people use in their #update action. This model method is definitely not PUT-conform in a RESTful meaning, as it doesn't replace but extends. Here's a clarification: http://nicksda.apotomo.de/2010/12/rails-misapprehensions-understanding-restful-put-and-post/

Xavier Noria
Owner
fxn commented April 29, 2011

@apotonick This debate about PUT is old.

Before PATCH (and PATCH is very recent) there was no theoretical solution to partial updates. I was subscribed for a while to the REST mailing list and saw big names discussing about it. Consensus was, you can't. If you need to, be pragmatic and use something, for example PUT. Not pure, but there's no pure solution (other than definining ad-hoc resources and do proper PUTs to them).

So people using update_attributes for PUT are/were doing it right. It is the best you can do.

On the other hand, it was up to you to define REST-conformant interfaces. There's nothing wrong with the update_attributes method. If you are using update_atrributes for partial updates then it is the programmer who is not following strictly REST, not the AR method. You are responsible for your design, and there's nothing in Rails that forces you to do partial updates. The routing DSL is very rich.

Now that there's PATCH, we (all) need to start catching up. Rails has to address PATCH necessarily at some point.

Nick Sutterer

Of course, it's not the AR #update_attributes method which is wrong but the programmer who uses it in PUT and calls his interface "RESTful". I didn't know that PATCH is available in Rails now, and as "this debate about PUT is old" I will remain silent for now ;-)

Xavier Noria
Owner
fxn commented April 29, 2011

@apotonick not yet in Rails, this issue is proposing a roadmap for it :).

Diego Carrion

The point is that Rails by default is patching the resource (update_attributes) when receiving a PUT request and doing nothing when receiving a PATCH one and that the patch will not affect anybody.

I think @dlee should be working on the patch right now, shouldn't he?

Xavier Noria
Owner
fxn commented April 30, 2011

@dcrec1 Rails does not do that by default. It depends on how the programmer uses the method. If you publish a REST API and your application is designed so that update actions only receive full updates, then everything is fine.

I'd wait green light/guidelines from someone in core before starting a patch.

Myron Marston

I agree that it would be good for rails to sort out partial updates vs. full replacement with PATCH and PUT.

A few people on this thread have mentioned that update_attributes can be used in combination with PUT if the client gets the full public representation of the resource, and includes that, with the desired changes, in the PUT request. This is true, but unless you control both the server and client code, there's no way to enforce this. The problem is that update_attributes only affects mentioned attributes, not all attributes included in the resource representation. So if a client does a put with a JSON hash like { "name": "John Doe"} and the resource also contains an age attribute, update_attributes will leave age set to the current value...but the semantics of PUT dictate that it should be a complete replacement, and therefore age should be set to nil (or, it is't invalid to let age be nil, the entire change should be rejected with an appropriate status code and explanatory message).

You can of course work around this, and write a custom replace method on your model that sets unmentioned attributes to nil, but it would be nice to have better out-of-the-box support in rails itself. I think we need a replace_attributes method as part of the ActiveModel API so that PUT requests can use this. PATCH requests could then continue to use update_attributes.

I think we could add at least come of this functionality without breaking backwards compatibility:

  • Add a replace_attributes method to the AM API.
  • Add support for the PATCH method to the router.

The hard part is finding a way to make the resource(s) declaration in the router support routing PATCH to Controller#update and PUT to Controller#replace without breaking backwards compatibility. Maybe it could be a configuration option that defaults to true in newly generated rails apps (kinda like the rails 3 default configs that got generated with new rails 2.3 apps)

Diego Carrion
dcrec1 commented May 01, 2011

@fxn when you create a scaffold in Rails, this code is generated:

# PUT /cars/1
# PUT /cars/1.xml
def update
  @car = Car.find(params[:id])

  respond_to do |format|
    if @car.update_attributes(params[:car])
      format.html { redirect_to(@car, :notice => 'Car was successfully updated.') }
      format.xml  { head :ok }
    else
      format.html { render :action => "edit" }
      format.xml  { render :xml => @car.errors, :status => :unprocessable_entity }
    end
  end

This is the reason I said that by default Rails patches a resources when receiving a PUT request.

dlee
dlee commented May 02, 2011

How do we get the green light from core?

Xavier Noria
Owner
fxn commented May 02, 2011

@dlee I believe this definitely needs to be addressed.

We are totally focused on publishing a beta right now, have not talked a lot about it because of that but there are some +1s as first impression. I'd like to work on this for 3.2 (I was not in core when I wrote the comment!). Some discussion is needed about the design, I hope we can provide some concrete feedback soon.

Thanks! Will write back here.

dlee
dlee commented May 05, 2011

@fxn "discussion is needed" within core or here in this issue? I'd like to be involved in the discussion if possible. I'm itching to cook up this patch.

Xavier Noria
Owner
fxn commented May 05, 2011

@dlee green light to your proposal, PATCH all the way :). No need for a replace action.

dlee
dlee commented May 05, 2011

@fxn: sweet! For clarification, we still want the PUT backwards compatibility, right? Or by "PATCH all the way", did you mean no more PUT?

Xavier Noria
Owner
fxn commented May 05, 2011

@dlee Yeah, as you proposed: both PUT and PATCH route to #update for backwards compatibility. And default _method for editing existing records is patch. Also for completeness the routes and test APIs should have a patch method as well, etc.

dlee
dlee commented May 05, 2011

Basically, the question boils down to whether or not we want #resources to keep the PUT => controller#update route alongside the new PATCH => controller#update route.

Xavier Noria
Owner
fxn commented May 05, 2011

@dlee Not sure if I follow. Since REST routing is so fundamental, goal is full backwards compatibility. If you upgrade a 3.1 application, it just works. No matter whether you use #resources and helpers that encapsulate all of this, or ad-hoc put routes and manuals :method => :put in your views.

dlee dlee referenced this issue from a commit in dlee/rails May 06, 2011
dlee Use PATCH instead of PUT; Fixes issue #348
PATCH is the correct HTML verb to map to the #update action. The semantics for
PATCH allows for partial updates, whereas PUT requires a complete replacement.

Changes:
* adds the #patch verb to routes to detect PATCH requests
* adds #patch? to Request
* adds the PATCH -> update mapping in the #resource(s) routes.
* changes default form helpers to prefer :patch instead of :put for updates
* changes documentation and comments to indicate the preference for PATCH

This change tries to maintain complete backwards compatibility by keeping the
original PUT -> update mapping. Users using the #resource(s) routes should not
notice a change in behavior since both PUT and PATCH requests get mapped to
update.
d5619a2
José Valim
Owner

Closing this as now we have a pull request with real code. :)

José Valim josevalim closed this May 07, 2011
dlee dlee referenced this issue from a commit in dlee/rails May 06, 2011
dlee Use PATCH instead of PUT; Fixes issue #348
PATCH is the correct HTML verb to map to the #update action. The semantics for
PATCH allows for partial updates, whereas PUT requires a complete replacement.

Changes:
* adds the #patch verb to routes to detect PATCH requests
* adds #patch? to Request
* adds the PATCH -> update mapping in the #resource(s) routes.
* changes default form helpers to prefer :patch instead of :put for updates
* changes documentation and comments to indicate the preference for PATCH

This change tries to maintain complete backwards compatibility by keeping the
original PUT -> update mapping. Users using the #resource(s) routes should not
notice a change in behavior since both PUT and PATCH requests get mapped to
update.
137ee03
dlee dlee referenced this issue from a commit in dlee/rails May 06, 2011
dlee Use PATCH instead of PUT; Fixes issue #348
PATCH is the correct HTML verb to map to the #update action. The semantics for
PATCH allows for partial updates, whereas PUT requires a complete replacement.

Changes:
* adds the #patch verb to routes to detect PATCH requests
* adds #patch? to Request
* adds the PATCH -> update mapping in the #resource(s) routes.
* changes default form helpers to prefer :patch instead of :put for updates
* changes documentation and comments to indicate the preference for PATCH

This change tries to maintain complete backwards compatibility by keeping the
original PUT -> update mapping. Users using the #resource(s) routes should not
notice a change in behavior since both PUT and PATCH requests get mapped to
update.
3f9a09c
Andrey Kozhyn SlyNet referenced this issue in stevehodgkiss/restful-routing October 26, 2012
Open

Use PATCH verb instead of PUT for "update" #50

Nykolas Laurentino de Lima nykolaslima referenced this issue in caelum/vraptor October 30, 2012
Closed

Support for PATCH http verb #474

Danny Whalen invisiblefunnel referenced this issue in codeforamerica/transitmix April 16, 2014
Open

Switch from Parse to Rails #7

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.