Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Patch verb #505

Closed
wants to merge 3 commits into from
@dlee

Update: as ncreuschling points out, there was a typo in the pull request message that said "HTML verb" instead of "HTTP verb".

Updated: update forms default to PUT instead of PATCH for current apps. Defaults to PATCH for new apps.

PATCH is the correct HTTP 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 only for new apps
  • 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.

dlee added some commits
@dlee 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
@dlee dlee Make method for update forms configurable
Make :put the default method for backwards compatibility. The generator for new
Rails projects configures :patch a the default method in config/application.rb.
985bec5
@dasch

GitHub needs a +1 button.

@benatkin

I googled for HTTP verbs and clicked the first result and PATCH isn't listed.

http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html

Where is it?

Also I hardly think that updating everything except the primary key and the created_at timestamp is a PATCH.

@dlee

@benatkin http://www.w3.org/Protocols/rfc2068/rfc2068

For past discussion, see issues #348 and #425.

actionpack/lib/action_dispatch/routing/mapper.rb
@@ -484,6 +484,16 @@ module ActionDispatch
map_method(:post, *args, &block)
end
+ # Define a route that only recognizes HTTP PATCH.
+ # For supported arguments, see <tt>Base#match</tt>.
+ #
+ # Example:
+ #
+ # patch 'bacon', :to => 'food#bacon'
@dasch
dasch added a note

The example needs to be indented 2 spaces so it'll show up correctly in the docs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@dlee

@josevalim, @fxn: Hey guys, this pull request has been sitting here for over a month unnoticed. It seems like master is now hosting code for Rails 3.2, so I'm thinking it can be merged in?

@josevalim
Owner

It seems fine for me with the view configuration option. I am a bit concerned about generating more routes (routes generation is already slow), but I don't consider it a blocker at first. /cc @nzkoz

@fxn
Owner

Yes I had it in mind, but waiting for 3.1 to start working on 3.2 proper.

@fxn
Owner

Just a ping to say I am still waiting for 3.1 and have not forgotten this patch. That's because albeit 3-1-stable is branched, I prefer a master that does not move too much because cherry-picking back to 3-1-stable is very common these days. Focus is 3.1 now.

The patch does not merge cleanly anymore, by the way, in case you want to maintain it in sync.

@dlee

@fxn, cool, glad to know this hasn't been forgotten. Let me know when you're ready to merge so I can update the pull request.

@jeremy
Owner

Nice patch!

I think this will break apps (and engines) that use explicit put routes to override a default resources route. After upgrading Rails, users will see that the form seems to bypass their put override.

@steveklabnik
Collaborator

+1 from me.

I wish GitHub had a better way to subscribe to issues instead of just commenting. I feel like "+1" is just noise, but I want to see what happens with this pull...

@bcardarella

@steveklabnik can't you just click the 'Enable notifications for this Pull Request' link at the bottom?

@guilleiguaran

@steveklabnik test clicking in the link at the end of this page:

Notifications for new comments on this Pull Request are off. Enable notifications for this Pull Request

@jeremy This one is candidate for Rails 3.2 or 4.0?

@jeremy
Owner

3.2 candidate for sure. This needs thorough attention for a clean upgrade, however.

The guides need closer attention. Perusing the diff, I see a lot of search/replace changes from PUT to PATCH. I think having PUT just disappear will be too confusing. Gotta explain this change each step of the way!

Anyone care to take up the torch on this?

@stevegraham

i must be the only person in the world that disagrees with "PUT requires a complete replacement", as per RFC2616 "HTTP/1.1 does not define how a PUT method affects the state of an origin server"

that said i like the name PATCH better than PUT for the purposes of what it is used for here.

@dlee

I can refine this patch if the core team decides it will definitely go into Rails 3.2... don't want to write another huge patch just to be pushed off to the next version :).

@jeremy can you give an example scenario where the put overrides would break? We might be able to provide a workaround, or at least put some notes in the documentation so that users aren't caught off-guard. Please suggest a workaround or an excerpt that can be added to the documentation.

@fxn
Owner

Yes, we talked about this patch after 3.1 and it is in the "roadmap" for 3.2 if it is good to go by then.

@stevegraham RFCs are not axiomatic systems, but I think there's no controversy in that PUT means "put this resource at that URL" ("The PUT method requests that the enclosed entity be stored under the supplied Request-URI.").

You're sending the resource itself, and in the target URL you either create a new resource ("If the Request-URI does not point to an existing resource, and that URI is capable of being defined as a new resource by the requesting user agent, the origin server can create the resource with that URI."), or else replace the existing one ("If the Request-URI refers to an already existing resource, the enclosed entity SHOULD be considered as a modified version of the one residing on the origin server.").

It is the app's choice to conform to that spec. Nowadays to do partial updates in a RESTFul way you need to define ad-hoc resources. For example, to toggle the "paid" flag of an invoice you may PUT to /invoices/:id/paid, but if you play by the rules you need PATCH for partial updates to /invoices/:id.

@josevalim
Owner

Aside the docs concern, this pull request looks good to me. I would just add the following changes (but I can do it myself in later commits after this is merged, this is more of a mental note):

1) We could move config.action_view.default_method_for_update to config.default_method_for_update so other frameworks can read it as well (see 2 below);

2) There is no need to generate routes for both PUT and PATCH. We could read the config in 1) and generate just one of the routes;

3) config.default_method_for_update should be uncommented in new applications as we already changed all docs

@josevalim josevalim commented on the diff
railties/guides/source/form_helpers.textile
@@ -312,6 +312,11 @@ Rails will also automatically set the +class+ and +id+ of the form appropriately
WARNING: When you're using STI (single-table inheritance) with your models, you can't rely on record identification on a subclass if only their parent class is declared a resource. You will have to specify the model name, +:url+, and +:method+ explicitly.
+NOTE: Rails can use the +PATCH+ method instead of +PUT+ for update forms if you set the following option in your +config/application.rb+:
+<ruby>
+config.action_view.default_method_for_update = :update
@josevalim Owner

Typo: should be = :patch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@fxn
Owner

@stevegraham I have found a crystal clear and authoritative answer to whether PUT allows partial updates: The very RFC 5789 (http://tools.ietf.org/html/rfc5789) states that "A new method is necessary to improve interoperability and prevent errors. The PUT method is already defined to overwrite a resource with a complete new body, and cannot be reused to do partial changes."

@NZKoz
Owner

I like the idea of moving this to be config.action_view.default_method_for_update as it lets people who care about these things choose to use patch without hitting anyone with 2x route proliferation.

However I'm pretty strongly opposed to switching the default to PATCH just because of a neurotic interpretation of the RFC. There are people with blog posts, printed books and screen casts where they do

form_for(@user, :html=>{:method=>:put}) 

Or

link_to("something", @user, :remote=>true, :method=>:put)

Switching the default will make all of those tutorials and chunks of code fail with routing errors, and "the RFC says X" doesn't seem like anywhere near a good enough reason to do that.

Maybe for a 4.0 change but absent some genuine bug or difficulty we can't do this in a random minor release.

@steveklabnik
Collaborator

Maybe for a 4.0 change but absent some genuine bug or difficulty we can't do this in a random minor release.

Yeah, sure, I mean, the asset pipeline from 3.0 -> 3.1 was a waaaaaaay smaller change. ;)

of a neurotic interpretation of the RFC

This is not neurotic. It's simply following the way that things are supposed to work. Many more people want partial updates than an upsert, that's what PATCH is.

@jeremy
Owner

@dlee see my previous comment for a concrete example

@dlee

@jeremey I was actually responding to your first comment in this thread. You say that explicit put overrides would break apps, but I don't see how they would. Can you clarify what you mean by put overrides?

@jeremy
Owner

@dlee Declare a resource in config/routes. Declare a put route that overrides the resource's update. Now upgrade the app and enable PATCH. Oops, now your forms submit to patch and your put route override is bypassed.

Not the end of the world, and docs should be enough to cover that.

@dlee

@jeremy One of the points of the change was:

  • changes default form helpers to prefer :patch instead of :put for updates only for new apps

Note that form helpers use :patch only for new apps. Would this still not cover that case?

@getconor

I agree with @NZKoz that this should not be included before 4.0 at the earliest. Unless I'm mistaken, RFC-5789 is still just a proposed standard, and it can still be changed or retracted. See: http://www.rfc-editor.org/info/rfc5789

@steveklabnik This isn't the way things are supposed to work yet, and even without all of the recent changes, we should only implement the standard when we know it won't change or be retracted- to do otherwise would be irresponsible. PATCH has been discussed by the IETF for almost ten years, why the rush to implement it in Rails now?

@josevalim
Owner
@benatkin

@mhutchin Interesting. I looked back at the link I posted at the top of the discussion and the RFC @dlee cited, which contains PATCH, appears to be obsoleted by one that I cited, which only has a reference to PATCH at the end. Here are the two documents:

The reference to PATCH at the end says:

The PATCH, LINK, UNLINK methods were defined but not commonly implemented in previous versions of this specification. See RFC 2068 [33].

@getconor

@benatkin Thanks, I knew they had been discussing it for a long time, I underestimated how long. :-)

I would only point out for those interested that RFC-2616 for HTTP/1.1 is now a DRAFT STANDARD, but at least it is further along in the process than the RFC that now covers PATCH, RFC-5789, which is a PROPOSED STANDARD, the "entry-level maturity for the standards track". For the meaning of the statuses see: http://www.rfc-editor.org/rfc/rfc2026.txt

@benatkin

Probably not the best way to check this, but on this list RFC-2616 is on Draft Standards and RFC-5789 is on Proposed Standards.

http://www.rfc-editor.org/rfcxx00.html

@benatkin

@mhutchin I didn't see your most recent comment until I posted mine. You said exactly the same thing! It took me a bit of thinking to see that there's a big difference between draft and proposed besides draft being higher up the list, but I can see it now.

@dlee

@NZKoz I don't think the "route proliferation" is so bad. In fact, even if Rails ends up supporting PATCH in the distant future, I'd prefer PUT be left as a standard route, perhaps linked to a different action (replace/upsert/etc.) This might not jive with a must-map-to-CRUD mindset, but it definitely jives with HTTP.

The interpretation of the RFC is not neurotic; I think we all share a clear and straightforward interpretation. I'll assume you meant "neurotic adherence to the RFC".

If so, let me reiterate that supporting PATCH is not to merely adhere to an RFC. The reason is because Rails currently has broken support for HTTP as commonly understood--leading to potentially problematic interoperability with RESTful clients, caching mechanisms, and proxies.

I'd say implementing, documenting, and promoting a broken implementation of an HTTP VERB is a genuine bug, warranting a fix in a minor release, but I'll leave the decision up to the Rails core team.

Finally, Rails shouldn't inhibit change just to retain backwards compatibility with blogs, books, and screencasts. In fact, I don't think Rails ever did care about that.

@dlee

@mhutchin, @benatkin, the Draft Standard is actually going away: http://www.rfc-editor.org/rfc/rfc6410.txt. RFCs currently in Draft Standard status will either be promoted to Internet Standard or demoted to Proposed Standard.

BTW, Rails supports modern cookies and content-disposition headers which are also Proposed Standards, so there's no basis to reject PATCH just because it's a Proposed Standard.

In fact, Rails should be the framework that helps PATCH attain the Internet Standard status since Rails clearly needs PATCH in order to support non-replacing updates.

Whether that happens in 4.0 or 3.2, I defer to the Rails core team.

@josevalim
Owner
@dlee

If so, let me reiterate that supporting PATCH is not to merely adhere to an RFC. The reason is because Rails currently has broken support for HTTP as commonly understood--leading to potentially problematic interoperability with RESTful clients, caching mechanisms, and proxies.

Do you have anything to support this? PATCH is a new verb in the specs, so I doubt proxies and caching mechanisms went crazy to support it and change PUT behavior.

I was referring to the broken behavior of PUT in Rails, not the absence of PATCH.

For example, caches can reasonably assume that if I PUT {"a" => "b"} to a resource, then that's what it should return on a GET. However, if the original resource was {"c" => "d"}, Rails would by convention make the resource {"a" => "b", "c" => "d"}, not the expected {"a" => "b"}.

BTW, how did you reply to a comment? Or did you just insert ">" before each line?

@fxn
Owner
@getconor

@mhutchin, @benatkin, the Draft Standard is actually going away: http://www.rfc-editor.org/rfc/rfc6410.txt. RFCs currently in Draft Standard status will either be promoted to Internet Standard or demoted to Proposed Standard.

To be clear, this would only alter the status of RFC-2616 for HTTP/1.1, which isn't under discussion, in no more than two years. RFC-5789 for PATCH, and the Proposed Standard tier, are unchanged.

BTW, Rails supports modern cookies and content-disposition headers which are also Proposed Standards, so there's no basis to reject PATCH just because it's a Proposed Standard.

No, but it shouldn't be used as a selling point either.

I'm not saying that we should reject PATCH just because it is only a Proposed Standard. I do think that we need to be very clear though, since people started saying that this was to conform to the RFCs, that the RFC in question is only a Proposed Standard, and has been for a long time. The status of the RFC dramatically impacts the cost-benefit analysis. Most would readily agree that Rails should do just about anything to conform to relevant Internet Standards and that those should be the defaults, but we obviously can't and shouldn't conform to every Proposed Standard.

This should be evaluated without regard to the RFC.

In fact, Rails should be the framework that helps PATCH attain the Internet Standard status since Rails clearly needs PATCH in order to support non-replacing updates.

If we can do so with minimal disruption.

@josevalim
Owner
@benatkin

Note also that RFC-2616 has Roy Fielding and Tim Berners-Lee among the names, while RFC-5789 only has two names, not including those two, on it. Even if it had the same status (Draft Standard), it wouldn't carry quite the same weight to me unless I saw an endorsement from Roy Fielding. The obsolete standard, 2068, doesn't count.

Here's the first thing I found while searching for a new comment by Roy Fielding on PATCH:

PATCH is another option that, once it is sufficiently deployed, might be preferred over POST for sub-resource updates. However, we make these choices for a principled reason, not just because it seems RESTful. Ultimately, the methods should be chosen by the origin server and communicated somehow to the client via the media type or relationship processing rules. If the choice of method doesn’t make any difference to the other components (i.e., neither client nor intermediaries gain any value by using PUT or PATCH over POST), then we should admit that it just doesn’t matter to the architectural style.

So it sounds like we can be pretty RESTful without PATCH, and especially without PATCH as the default.

@fxn
Owner

I was referring to the broken behavior of PUT in Rails, not the absence of PATCH.

About this point, in what sense is Rails behavior broken? Rails does what is in its hands to let you use PUT in an easy way, even imitating the correct way of doing things in browsers where that's not possible.

The user is responsible for using those tools correctly. You are free to define as many ad-hoc resources as you want to speak proper HTTP. If you construct forms for partial updates and use update_attributes that's up to you.

@stevegraham

Deleted original comment because replying from email borked my formatting :(

@stevegraham I have found a crystal clear and authoritative answer to whether PUT allows partial updates: The very RFC 5789 (http://tools.ietf.org/html/rfc5789) states that "A new method is necessary to improve interoperability and prevent errors. The PUT method is already defined to overwrite a resource with a complete new body, and cannot be reused to do partial changes."

@fxn - I'm sorry I think you're mistaken here and I have to respectfully disagree with you. First of all RFC5789 is not HTTP, let's instead look at RFC2616

"The PUT method requests that the enclosed entity be stored under the supplied Request-URI. If the Request-URI refers to an already existing resource, the enclosed entity SHOULD be considered as a modified version of the one residing on the origin server. If the Request-URI does not point to an existing resource, and that URI is capable of being defined as a new resource by the requesting user agent, the origin server can create the resource with that URI."

The word replace does not appear at all, in fact if you continue to read on you see "HTTP/1.1 does not define how a PUT method affects the state of an origin server." This implies to me that there server is free to choose whether to replace the resource in it's entirety or apply the enclosed entity as a patch. It's as clear as day as far as HTTP 1.1 is concerned and putting aside my view own personal views, neither of these outcomes are incorrect.

I think the only reason RFC5789 exists is that the authors have misinterpreted RFC2616. There is simply no language in RFC2616 that mandates "The existing HTTP PUT method only allow a complete replacement of a document." or that "The PUT method is already defined to overwrite a resource with a complete new body, and cannot be reused to do partial changes." It's simply not there. There maybe an argument that "a new method is necessary to improve interoperability and prevent errors." I don't know.

I also don't see how PUT breaks caching? As far as I understand it a successful PUT invalidates the caches for that given URI but the request response itself is not allowed to be cached as per HTTP 1.1. Maybe someone would be kind enough to explain that for me.

Having said all of the above, it would be nice if someone could actually refute my point to my satisfaction, which you have not done so far. It's not very comfortable being the only dissenting voice.

@fxn
Owner

@stevegraham I agree with you in that the authors of the RFC5789 could be wrong, though I want to believe that you wouldn't be able to push such a proposal for so many time like they have done based on such a gross false premise. But yes, that's an hypothesis.

I was for a while in rest-discuss and my feeling is that there's consensus about that interpretation of PUT. Also O'Reilly's book about REST is clear. What's a modified version of the resource? Resources in HTTP are not partial, they are resources, all or nothing, in my understanding of the terminology. You can only request and submit resources or resource representations. So in that sense I believe no verb can work with partial resources. That's where ad-hoc resources come to play, like the paid flag of an invoice in my example above.

But this thread is pulling in different directions. It's interesting. There's the objections about whether it is premature to support such a RFC because of its status. There's the objections about whether using PUT for partial updates is incorrect at all. There's the argument that Rails should be pioneer and push. Then there will be practical issues because web servers and proxies won't understand PATCH in the short term and we will only be able to tunnel it through POST.

All in all, I've written to Roy Fielding to ask for advice. I think that's the best we can do. Should Rails work in that direction? If yes, should we work on it today? Let's see whether Roy is so kind as to shed some light about this.

@benatkin

@fxn I don't see where @stevegraham suggested that the authors of RFC-2616 are wrong. I only see where he suggested that the authors of RFC-5789 are wrong.

I think that it's great that you've contacted Roy Fielding. I hope to see an interesting reply.

I wonder why this couldn't have been implemented as a plugin this summer when this and other issues were being discussed. If one was written, would it have been used? How about releasing a plugin based on this and seeing whether it takes off?

@fxn
Owner

@benatkin oh yes my bad, wrong copy & paste. I've edited the comment.

@NZKoz
Owner

All this discussion of RFCs and the like seems to miss the point entirely. What's the negative impact of the current behavior to real users. Are there actual practical things which people are being bitten by because we use PUT?

Unless there's an answer to that then this entire thread is a massive distraction, we should add the config option, not change the defaults, and move on with our lives.

@fxn
Owner

The discussion in my view is relevant because there are people arguing that NO support for PATCH should be added at all.

Arguments are: 1) it is too premature given the status of the RFC. 2) It is not clear in HTTP/1.1 that PATCH is needed at all. 3) Current web servers and proxies won't understand it, so this should add support only for tunneling.

I am enumerating, not subscribing them.

We need to answer that first question. I think the point about defaults is minor in the thread.

@NZKoz
Owner

Supporting it as an option, with no change of defaults, circumvents all 3 of those objections. Those who wish to use it can, those who don't care won't be affected in any way.

It's really the simplest solution that could possibly work and it won't break anyone's apps ever under any circumstances. Unless there's a compelling reason to do something else, why don't we add the config option and move on.

@benatkin
@getconor

Wait a minute. It was never my position that these changes are too premature given the status of the RFCs. The only reason I even mentioned the status of the RFCs was because they were being used in this thread, and the other threads mentioned, for various reasons, including trying to define the problem and justifying the changes, but without any mention of the fact that all of the RFCs mentioned were in various stages of maturity, and none of them were even final standards yet.

Other than that, I couldn't care less. I agreed with @NZKoz then, and I agree with @NZKoz now.

@fxn
Owner

+1 to what Benjamin said. I don't think it is a good idea to push code, docs, and tests to maintain if the feature ends up being dubious. If people want to play with something experimental better a plugin than a black hole in the code base hidden by a flag to the end user.

I mean, we are touching one of the strongest conventions in Rails, it touches the blessed actions. We have to do that with extra care in my view.

If I had to bet, I'd say go, but I'd prefer to have a clear understanding of the convenience of pushing this from someone as Roy Fielding, given nobody in the thread AFAICT seems to have a definitive authoritative answer. Because pushing support for PATCH has implications, it sends a message to the user base. The guide and blog post need to explain to users when or why they should use PATCH, and from this thread we have no conclusions. A blog post saying "in case you want it, why? we don't really know, you just have it there, use at your risk, have fun" doesn't sound right to me.

@fxn
Owner

To summarize, it is even easier to go with the solution that says partial updates with PUT are OK. No changes needed.

@rkh

Partial updates with PUT violate RFC 2616. You have to use POST.

@stevegraham

Where?

@rkh

I understand Section 9.6 as "if you send a PUT request, don't care if the resource already exist, the server will tell us with a 200 or a 201" (i.e. the client will transmit the complete resource), how should that allow a partial update?

@fxn
Owner

@rkh That's also what I think, but the point is "I understand", we always say in advance something like that, "my understanding", "my interpretation", which is telling us we cannot derive without doubt that from the text in the RFC. So we need to resort to "which is the intention", or "which is the general consensus".

Roy Fielding has not replied to my email so far.

On the other hand @steveklabnik has some pointers that support that PATCH is needed for partial updates I'll post later.

@steveklabnik
Collaborator

For example, in the (now obsolete) RFC2068:

The PATCH method is similar to PUT except that the entity contains a
list of differences between the original version of the resource
identified by the Request-URI and the desired content of the resource
after the PATCH action has been applied.

The definition of PUT:

The PUT method requests that the enclosed entity be stored under the
supplied Request-URI. If the Request-URI refers to an already
existing resource, the enclosed entity SHOULD be considered as a
modified version of the one residing on the origin server.

The enclosed entity be stored. Not 'some random stuff that changes things.' It's an entire entity. 'a modified version of the one,' not 'changes that should update the one.'

@fxn has the links I gave earlier, where Fielding also explicitly says PUT requires an entire representation. I don't have them handy.

@stevegraham

Please post these links because if they're the same ones I have seen Fielding does not say anything of the sort.

@rkh
@benatkin

@steveklabnik As you say RFC2068 has been obsoleted by RFC2616. http://tools.ietf.org/html/rfc2068

RFC2616, a draft standard, doesn't include a definition of PATCH. All it has to say is The PATCH, LINK, UNLINK methods were defined but not commonly implemented in previous versions of this specification. See RFC 2068 [33]. To me this doesn't say that PATCH is a part of HTTP 1.1. Also the proposed standard for PATCH (not a draft standard) doesn't include any of the authors of RFC2616 in its list of authors, and I couldn't find any endorsement for RFC5789 from Fielding online.

So PATCH feels pretty random to me. Much more random than semantically replacing a document with an updated version but leaving created_at, updated_at, and id intact, and maybe omitting some unchanged fields for efficiency.

@rkh
@stevegraham

@rkh NOPE NOPE NOPE

@benatkin

RFC5789 is nearly two years old, is still a proposed standard, and I can't find any implementations of a write-through cache with it, nor can I find evidence of the two authors trying to push it forward since then.

I would be happy to see a rails plugin and an apache or nginx module written to show how it can improve how HTTP works, and then a better discussion about whether the complexity of adding another verb is needed could take place. I think headers might be better because to me whether something is being replaced or just modified isn't a yes/no question when metadata is considered.

@steveklabnik
Collaborator

Here's one quote from Fielding:

Stefan, I think it is better to say that we only use PUT when the update action is idempotent and the representation is complete.

He goes on further to mention PATCH:

PATCH is another option that, once it is sufficiently deployed, might be preferred over POST for sub-resource updates. However, we make these choices for a principled reason, not just because it seems RESTful.

In the text of that post:

... complete replacement of a representation (PUT),

The text of the PATCH RFC, which is again, just a draft:

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

My point is this: there's overwhelming consensus that PUT means 'whole representation.' Well, modified consensus, since it's not 100%. ;) However, it is true that PATCH is a draft standard, so I can see not implementing it in Rails. That's quite valid. However, continuing to use PUT is also wrong. If it's a partial update, POST makes much more sense.

@steveklabnik
Collaborator

@benatkin

So PATCH feels pretty random to me. Much more random than semantically replacing a document with an updated version but leaving created_at, updated_at, and id intact, and maybe omitting some unchanged fields for efficiency.

No, not at all. That's a direct violation of PUT's semantics. POST is the random grab-bag method, anyway.

@rkh
@benatkin

Not because it's a proposed standard, no. But there are other reasons to support cookies: it's been implemented and experimented with in dozens of server, client, and middleware implementations long before the first version of Ruby On Rails came out, and it's a feature that's important to Ruby On Rails users.

@mikekelly

fwiw, I brought this up on SO about 18 months ago (the answer was auto-accepted):

http://stackoverflow.com/questions/2364110/whats-the-justification-behind-disallowing-partial-put

@NZKoz
Owner

We've gone back and forth on this for more than 6 months, there has never been a single case provided why we should change the defaults other than bible, sorry RFC, thumping.

As I mentioned earlier, we should simply add an option for users who want to use PATCH to be able to use it, and move on with our lives. We can address the defaults in some far distant release once we actually have real experience with the benefits.

In the interest of bringing this to a close I'm going to close this pull request, please feel free to submit another which only adds a configuration option allowing users to switch on PATCH

@NZKoz NZKoz closed this
@dlee

Would rails core accept the minimal PATCH commit? I don't want to work on the pull request just to have it debated for months and closed without resolution.

@fxn
Owner

RFCs are the tool we have to discuss this. Why don't we support MOVE in Rails. Well, because it is not in the RFC right? And if someone proposed MOVE we wouldn't accept it on the ground of not being there.

So let's accept this has to be discussed taking RFCs into account, and let's accept RFCs are not axiomatic systems and are subject to interpretation in some spots.

Having said that, if verb V is not clearly justified I see no point in hiding it behind a flag, as I said above. If the need for verb V is debatable it should not be in core.

@steveklabnik
Collaborator

@NZKoz There are good reasons for this. I've cross-posted about this to rest-discuss, and as Jan Algermissen mentions,

When a client (or intermediary, for that matter) re-does a PUT N-times (e.g.
because it did not receive any response the first N-1 times due to network
problems) the result on the server might not be what the client assumes it is,
given the idempotent nature of PUT.

A client that is aware of the server's tunneling-partial-update-through-PUT
semantics might not redo the PUT but any intermediary in between might (because
they would not be aware of the out-of-band knowledge / just like Google
accelerator in the case of GET-to-delete-account).

What is the problem of just using POST for the partial update in the first
place? This is what POST is for.

Also, 'bible-thumping' is just a way to dismiss arguments; you either follow web standards or you don't. Rails has brought REST(ish) to many, many people, and the choices Rails makes are important. When IE6 didn't follow the standard initially, it was an excellent browser. Years later, we can see how that was a mistake.

I don't even mean this as 'let's implement PATCH,' as that's not yet a true standard. But update should really use POST by default, since most people aren't actually PUT-ing full representations.

@searls

@steveklabnik +1, well said.

@mikekelly

@steveklabnik that post from Jan is written on the basis that it is impossible to make an idempotent partial update. I disagree.

In some ways it's irrelevant anyway; people are already using partial PUT requests that are intended as idempotent.

Moving to POST would:

  • have a negative impact on clients since its non-idempotent nature has now made the requests non-repeatable
  • seem to produce zero appreciable benefits over the current practice

Supposedly, RFC2616 has been enforcing 'full PUT' for over 12 years and we're still yet to see any significant mechanisms that leverage this systemic agreement on the fullness of PUT requests.. that should tell us something (i.e. it's a useless semantic). Add in the fact we have implementations, like Rails, which blatantly disregard it and don't appear to suffer any real negative consequences.. I think it's a serious stretch to compare this to IE6.

@steveklabnik
Collaborator

@mikekelly Honestly, if Rails devs knew what 'idempotent' meant, they'd be happy. I don't think they intend such things at all, I think they're just using the Rails defaults.

And if the semantics of PUT aren't useful, then they should be changed. In the spec for HTTP. And then we should follow it.

Agreements are useless if people don't actually follow them.

@fxn fxn reopened this
@fxn
Owner

Guys I am reopening this PR. There's a legitimate discussion going on here, and I was the guy that took this PR on his plate from day one.

If the discussion takes 6 months or 2 years, it just doesn't matter, it has to take the time it needs.

People tired of the thread can unsubscribe.

@steveklabnik
Collaborator

Two more things, while I think of it:

1) if PUT didn't require a full representation, there'd be no way to do the 'or create' part of PUT's semantics. While not totally damning, I think it's also a strong supporting line of reasoning.

2) Rails hasn't suffered for 'implementing PUT wrong' (really app dev's fault, but assisted by Rails' shoddy defaults) because Rails apps haven't been receiving PUT requests: they receive POST requests with a _method parameter saying PUT. This is basically exactly what I'm saying should happen with the removal of support for PATCH but without the extra parameter, pretending to be a PUT.

@NZKoz
Owner

I certainly don't agree with continuing the discussion, but as you say I can click the little "off" button :)

My objection is solely to switching the default without any actual downside to end users. It's not about whether someone's app will break (which we can obviously work around) but about yet-more additional conceptual overhead for users picking up rails. There's tonnes of books, screencasts and blog posts which show how RESTful routing works and they all say "put will update".

Users will read those blog posts or books, write :method=>:put in their html and get routing errors.

We can't just do that for the sake of feeling better about our purity. I'd suggest we really need a web-accelerator level of pain to justify changing the defaults.

Have fun guys ;)

@myronmarston

Supposedly, RFC2616 has been enforcing 'full PUT' for over 12 years and we're still yet to see any significant mechanisms that leverage this systemic agreement on the fullness of PUT requests.. that should tell us something (i.e. it's a useless semantic). Add in the fact we have implementations, like Rails, which blatantly disregard it and don't appear to suffer any real negative consequences.. I think it's a serious stretch to compare this to IE6.

@mikekelly: I don't think it's a matter of "real negative consequences"; rather, there are very, very positive benefits to implementing PUT correctly that are simply impossible to achieve when the server is not following proper PUT semantics. I've got a recent internal HTTP service that I built with sinatra and it was very easy to get proper PUT semantics. The app uses PUT both for create and update, and only allows full representations to be used. Here are some of the benefits we've received:

  • Since PUTs are idempotent, they can be re-tried as many times as you want. We rely on this a lot. The service is consumed by our main rails app. The rails app performs the PUT to the sinatra service using a resque job, and it's very easy to retry the job when we have a transient failure. It just works.
  • The fact that we use PUT for both create and update makes the client code exceptionally simple. We don't need to deal with two separate types of requests to ensure the sinatra service has the resource in the proper state. We just use a PUT, and it doesn't matter if the resource exists or not, or whether or not the resource is already in the desired state; the end result is the same. We also leverage this in a cron job that runs every few days for the express purpose of reconciling any discrepancies between the user configuration data in the rails app and the data in the sinatra app. It goes over every user, does a GET to the sinatra app to get the data, compares the data to what it has locally, and does a PUT if there is a discrepancy or if the sinatra app responds with 404 not found. Again, the idempotent, create-or-update nature of the PUT makes this extremely easy.
  • The fact that we use PUT for create (and thus allow the client to pick the identifier) makes the service very transparent and easy to troubleshoot. When one of the users of the rails app has some missing data I don't have to go look up the id of the corresponding record in the sinatra service; I can easily construct URLs directly in my browser using the user ID I already know.

None of these benefits would be possible if we had implemented the normal rails PUT semantics in our sinatra service.

So here's my two cents (not that anyone asked for it....). I'd love to see rails provide support to easily implement proper PUT semantics. As part of this, some other HTTP verb should be used for partial updates. PATCH looks interesting, but POST can be used today and is the correct verb to use. In addition, it would be great if ActiveRecord/ActiveModel provided an replace_attributes method that only supported FULL updates and no partial updates. It would effectively be like update_attributes except that it would set any unmentioned attributes to nil. Most of the time, this would cause the partial update to fail with validation errors, which is the correct behavior you would usually want.

@mikekelly

@myronmarston right, this is a common approach e.g. Riak uses it

The issue here is that your application doesn't rely on completeness being guaranteed for PUT across the web; it's an over-specification of HTTP. If your application does need full PUT (many do), then design and document it that way. I agree it's a good idea to try and make this behaviour easier to achieve in rails.

Given we now have a lot of mobile clients with unreliable connectivity - allowing them by default to submit small, partial updates idempotently is a huge benefit. Neither POST or PATCH would allow this since they are non-idempotent.

@steveklabnik
Collaborator

allowing them by default to submit small, partial updates idempotently is a huge benefit.

What's your issue with the 'expose a sub-resource' strategy?

@mikekelly

What's your issue with the 'expose a sub-resource' strategy?

off the top of my head:

  • splitting up every potential partial into individual resources is not practical if you want to be able to partially update each individual attribute of a resource.
  • it's much more efficient to put (oh snap!) this optimisation in the hands of the clients, rather than try and second guess what they want.
  • smearing your resource state is a pain for things like caching; it's redundant, you introduce shared state between resources which cases problems with cache invalidation, etc etc

having said that, I do favour using sub-resource strategy based around deletion, which does tend to overlap somewhat. There are also ways to get around the third point about caching, but at a cost of increased complexity to your system.

@myronmarston

The issue here is that your application doesn't rely on completeness being guaranteed for PUT across the web; it's an over-specification of HTTP. If your application does need full PUT (many do), then design and document it that way. I agree it's a good idea to try and make this behaviour easier to achieve in rails.

Actually, my application does rely on it being the complete resource. That's how it implements PIT as create or update. It wouldn't be able to implement create if it allowed partial resources.

Given we now have a lot of mobile clients with unreliable connectivity - allowing them by default to submit small, partial updates idempotently is a huge benefit. Neither POST or PATCH would allow this since they are non-idempotent.

The spec does not mandate that POST or PATCH be idempotent--but it doesn't say they can't be. I think it makes sense to implement the partial update as an idempotent POST or PATCH and simply document that it is idempotent (even though the HTTP verb used does not require it).

Also, while it's possible to implement some kinds of partial updates idempotently, it's not universally true that ALL partial updates are idempotent. Consider the common case of using accepts_nested_attributes_for for a has_many association. When an update comes in that is adding new associated records, it is not idempotent, because each time the update runs it will add additional records.

@fxn
Owner
@fxn
Owner

Well, that could happen also for full requests... Indeed, regarding idempotence the spec says "(aside from errors or expiration issues)". So idempotence is defined for successful requests I interpret.

If that is correct, then I see no problem in allowing creation of sub-resources.

@myronmarston then, why do you think partial updates would not allow you to use PUT for creation and update?

@myronmarston

@myronmarston then, why do you think partial updates would not allow you to use PUT for creation and update?

It's possible, but it adds lots of complexity. With how I've implemented PUT, the client assumes no knowledge of what state the resource is in on the server. It simply doesn't matter. It always PUTs the entire resource.

Likewise, the server doesn't need to know if the request body is intended to be a partial or full representation; it uniformly treats it as a complete resource.

It makes for simpler, more maintainable code all around.

@fxn
Owner

@myronmarston excellent, thanks.

So in your case the application is designed that way. Metaphorically speaking, in my invoicing applications the paid flag is an ordinary resource from the client's point of view. It is convenient, better for the semantics of the applications, we like the model, etc.

But if we focus the conversation on the raw protocol, the more I read about it, the less I am convinced that HTTP requires full representation in PUT requests.

However, Roy Fielding has stated that is the intention in a few public places. @steveklabnik quoted him, and I've also seen this patch http://trac.tools.ietf.org/wg/httpbis/trac/changeset/1158#file1 Roy points to in this thread http://tech.groups.yahoo.com/group/rest-discuss/message/17415 (@mikekelly participated in that one).

So, my personal take on this is: the HTTP spec is a bit ambiguous about partial updates via PUT. This opens the door to endless controversy because it does not follow from the spec crystal clear that you cannot do it. But Roy Fielding says you should send full representations. If you want an authoritative answer, Roy's the one. Thus, if Rails as a framework has to take sides, I think the most sensible choice is agreeing that partial updates via PUT are not the way to go.

That does not mean the path to address that in Rails is to support PATCH, but at least I believe we have a sensible conclusion about this.

Does anybody disagree?

@dlee

@fxn I agree PUT should not be used for partial updates.

PATCH sounds like the perfect solution for this (in fact, designed specifically for this purpose). Other than backwards compatibility issues (which we can avoid by making PATCH optional) and lacking the stamp of RFC Internet Standard (which wasn't an issue for cookies and content-disposition), what reasons are there for not using PATCH?

@steveklabnik
Collaborator

Oh, also, some clarification from a changeset authored by fielding 9 months ago in the upcoming changes to the spec.

An origin server SHOULD reject any PUT request that contains a Content-Range header field, since it might be misinterpreted as partial content (or might be partial content that is being mistakenly PUT as a full representation). Partial content updates are possible by targeting a separately identified resource with state that overlaps a portion of the larger resource, or by using a different method that has been specifically defined for partial updates (for example, the PATCH method defined in RFC5789).

@mikekelly

personally I think it's better to ignore the specifications and focus on the practical problems inflicted by using PUT for partial updates - what are they?

@reschke

@dlee "lacking the stamp of RFC Internet Standard"

How exactly are PATCH (RFC 5789), Content-Disposition (RFC 6266) or Cookies (RFC 6265) "lacking the stamp of RFC Internet Standard"?

@dlee

@mikekelly The practical problems stem precisely from not following specifications. It's as if your server did an update on a GET, or a delete on a PUT--you're breaking conventions/expectations. HTTP participants expect certain behavior following specifications, and breaking that expectation will lead to troubles.

@fxn
Owner

@mikekelly I think there are two ways to look at it.

One way is the pragmatic choice one does for a particular application. People can do whatever they want. You can use GET for deletion, you can tunnel everything trough POST. You can submit partial updates through PUT... Individual programmers for particular projects decide to adhere to the spec or not and in what degree.

The other point of view is the one of a web framework. In my view it is the duty of Rails to follow and promote best practices. When designing the framework, specs are what we need to base the design on, they are what define what's a good web citizen.

If you are writing your own private links manager running in localhost, and you want to delete links with GET quick & dirty, please go ahead. But Rails should push the message you should be using DELETE. By design, the easy path does that. And it does going as far as having builtin macros, routing, conventions, and even tunneling the method through a hack while at the same time routing the verb correctly (BTW, someone said people do POSTs with _method really not PUTs anyway. No, browsers do POSTs, Rails routes PUT).

This PUT vs PATCH is no different. Though we need to take into account backwards compatibility, I think it is our responsibility to give the user the tools to do partial updates in a proper way.

@mikekelly

@dlee @fxn what is this unexpected behaviour exactly?

Is it that a client might make a PUT request intended to 'shrink' a resource but instead only partially updates it as a partial? If so; is that a real problem? Aren't client devs reading documentation telling them what can be PUT where anyway?

@dlee

@reschke #505 (comment)

@mikekelly What you're describing is one of the problems. Another could be that a PUT request could be cached with a partial representation. Still another is that a client might assume the idempotent properties of PUT.

@mikekelly

@dlee it's not practical to prime a cache with the contents of a PUT request - do you know of implementations which actually do this? The client is meant assume the idempotent properties of PUT, so that's not really a problem either.

@exviva

Let's agree that for vast majority of Rails applications, a "partial update" (a.k.a. update_attributes) is the most common use-case for an update-like request. Just imagine having to re-submit full representations of large/complex resources on every update (like nested attributes or file attachments).

Having said that, I'd vote for routing PATCH to the update action, and coming up with a separate action with PUT semantics (create/update full representations) in Rails 4.

@reschke

@dlee I'm aware of that.

I'm just confused about "lacking the stamp of RFC Internet Standard". PATCH, Cookies, and C-D are Proposed Standards. HTTP/1.1 currently is a draft standard, but will be back at "proposed" soonish. As a matter of fact, most of the internet runs on "proposed" standards.

@dlee

@mikekelly No, I don't know of any implementations off the top of my head. As for idempotent PUTs, the problem is precisely that clients assume the idempotent properties of PUT, but the server might not do idempotent updates in PUT.

@exviva I agree. But I think there should be some sort of transitional stage in 3.x that introduces devs to PATCH and allows them to opt-in before Rails 4.

@mikekelly

@dlee ok, but what does the invalid use of PUT for non-idempotent requests have to do with the use of idempotent partial updates? Are you implying that idempotent partial updates aren't possible?

@dlee

@mikekelly Sorry, I don't understand why you're talking about "idempotent partial updates". My worry is with non-idempotent partial updates happening behind a PUT request (expected to be idempotent).

@fxn
Owner

@mikekelly If you document you can delete a resource via POST requests, that's your business. Users of your API can certainly read that documentation and understand it.

If you allow idempotent partial updates via PUT requests also that's your business. Users of your API can read that documentation and understand it.

But If you are a Rails programmer and want to provide DELETE for deletion and PUT for full updates the framework provides the means[*]. As of today there's no builtin way to do partial updates, because there's no builtin way in HTTP. This thread is about whether Rails as a framework should do something about this, not about the practical urgency you may feel. People have done delete via POST for ages.

[*] The current idioms with update_attributes are perfectly fine for full representations via PUT. "Full representation" belongs to your interface, update_attributes is your implementation. If your API requires a full representation (except perhaps for some internal stuff like cache counts), update_attributes allows you to store one just fine.

@mikekelly

@fxn

As of today there's no builtin way to do partial updates, because there's no builtin way in HTTP

it's already built into rails and works ok over HTTP, afaict:

curl -H "Accept: application/json" http://test.dev/widgets/1

{"id":1,"foo":"bar", "fizz": "buzz", "car": "blue" }

curl -H "Content-Type: application/json" -X PUT -d "{ \"foo\": \"partially updated\" }" http://test.dev/widgets/1

curl -H "Accept: application/json" http://test.dev/widgets/1

{"id":1,"foo":"partially updated", "fizz": "buzz", "car": "blue" }

@steveklabnik
Collaborator

Sure, and so does GET /foos/1&action=delete to delete a resource, but that doesn't mean you're not violating HTTP's semantics to do it.

@mikekelly

@steveklabnik most people agree that 2616 is not clear enough for this to be a serious issue, hence why httpbis has looked to improve it. I'm yet to figure out why exactly that semantic needs to be put in place by httpbis. It doesn't appear to be necessary and it prevents partial idempotent updates (PIUs).

PIUs are important, particularly for mobile devices that operate on crappy networks and need to be able to make extremely granular requests (i.e. partials) that can be retried easily on failure (i.e. idempotently).

The fact is, rails behaves this way - it's demonstrably useful, and non-demonstrably detrimental to either rails applications or the web as a whole.

I think it's probably better to leave it as it is - given the contention over 2616 - and wait to see what happens with httpbis.

@dlee

@mikekelly I think 2616 is clear enough on the issue of PUT being full-resource replacements, but just in case it wasn't clear enough, there's also the RFC 5789 to make it explicitly clear.

PIUs, and just partial updates in general, are important, we all agree. That's the whole point of this pull request--let's support the proposed standard of using PATCH to do partial updates.

PUT mandates idempotent full (non-partial) updates.
PATCH allows full or partial updates, idempotent or not.

@reschke Yes, a lot of the internet runs on Proposed Standards. That's why we shouldn't dismiss PATCH even though it's a Proposed Standard, because it's too restrictive to only support Internet Standard.

@mikekelly

@dlee I don't want to argue semantics because it won't go anywhere, but would you agree there must've be some ambiguity that causes it to need updating by httpbis?

The problem with moving to PATCH is that it's not equivalent since its definition is not idempotent: "PATCH is neither safe nor idempotent", this means that - on the web - these requests cannot be considered idempotent (even if your client and server know better). This means that any supporting infrastructure e.g. HTTP client libraries which know they can retry PUT requests on network failure, or generic accelerating proxies that perform a similar function cannot do so on these PATCH requests - even if they are intended idempotently, on the network they aren't.

Fwiw, that kind of argument does not hold up in the same way for the guaranteed fullness of a PUT request, which is exactly my problem with that (over)specification.

@dlee

@mikekelly From what I can tell, it's clear that PUT means full-resource updates, and the purpose of RFC 5789 (PATCH) is not mainly to clarify PUT's definition (it does clarify, if only by taking for granted that PUT is full-resource replacement), but to provide a solution for a glaring hole in HTTP--namely, the lack of partial updates.

I agree with you that PATCH does not guarantee idempotent updates. I agree with you it might be useful to have a verb that allows partial and idempotent updates. However, PUT is not it (no partial updates). Neither is PATCH (updates not guaranteed to be idempotent). There is no verb in HTTP that guarantees idempotent behavior while allowing partial updates.

Your desire for such a verb should not prevent PATCH from being supported in Rails. As for idempotent partial updates, it seems like using subresources (as discussed in previous comments) is the best strategy for now.

After all this, though, it seems we're pretty much on the same page. I think you'd agree it would be good to support PATCH in Rails if it's clear that PUT does not allow partial updates. Our task, then, is to ascertain whether or not PUT supports partial updates. And between RFCs (new and old) and Roy Fielding, it seems pretty clear the authorities are against partial updates in PUT. Can we agree on this?

@fxn
Owner

(With infinite love to @tenderlove).

@mikekelly Ruby on Rails does not define HTTP semantics, as a web framework it has to provide means to its users to be able to adhere to them if they want to, promote proper usage of them, and still allow users to do delete via GET and partial updates via PUT (ie, promoting, not lecturing).

Today, to toggle the paid flag of an invoice and follow proper HTTP semantics, you have to make that flag a resource. That's for me a conclusion of this thread, based on quotes from Roy Fielding.

So the topic of the thread is: given these definitions, what should Rails do next? Perhaps the answer is PATCH, or perhaps is nothing, we still don't know.

And if Rails had some sort of support for PATCH, you still would not be able to do idempotent partial updates and adhere to HTTP semantics at the same time. That's just how things are defined, it's not under the control of Rails. To discuss the convenience of this you need to discuss with the HTTP guys, not the Rails guys.

@mikekelly

@fxn the problem here is that you're insisting that your interpretation of the semantics in 2616 is the only valid one, which is not the case. Debating semantics is utterly pointless, but it is worth acknowledging that this particular discussion about PUT has been around for lot longer than this PR - which suggests that there is, in fact, more than one valid interpretation.

So even if "we must follow the spec at all costs" was a valid logical argument - in this instance it doesn't really move us forward due to the unavoidable reality that part of the spec in question is up to interpretation.

So, as I've said previously in this thread, I think we're far better off focusing on the actual benefits and problems of moving away from or staying with partial idempotent updates. I've already made a case for sticking with what we have:

PIUs are important, particularly for mobile devices that operate on crappy networks and need to be able to make extremely granular requests (i.e. partials) that can be retried easily on failure (i.e. idempotently).

The fact is, rails behaves this way - it's demonstrably useful, and non-demonstrably detrimental to either rails applications or the web as a whole.

I think it's probably better to leave it as it is - given the contention over 2616 - and wait to see what happens with httpbis.

Aside from the concerns about following the spec, why do you think partial updates via PUT are bad for either rails apps or the web?

@reschke

@mikekelly you can make a PATCH request idempotent by adding "If-Match".

@fxn
Owner

@mikekelly I don't say this is the only valid interpretation. At the beginning of the thread it was my reading of the spec, and it was what other sources interpreted. It is also clear that we are not inventing this controversy, it is old.

After this thread, we have a pulse of the generic consensus and Roy Fielding's view on this topic. And I believe that opinion is key. If Roy said "go do partial updates with PUT" we had already closed the PR.

This tells me that if I have to bet, I know where to bet. Not saying the other interpretation is wrong. The spec is ambiguous, so there's no right or wrong. It could be the case that the ambiguity was an overlook, I personally don't know. But what we have is consensus (different from unanimity), and the +1 of Roy Fielding regarding not doing partial updates with PUT.

So, Rails is (probably) going to provide a way for the majority of people participating in this thread that would like to do partial updates without PUT. At the same time, you will be able to continue doing partial updates via PUT.

Mind you, if we do something I don't expect it to be a dramatic change. At least at the beginning. It could be something discreet, like being able to say :method => :patch, and adding routing and conventions without changing defaults. Backwards compatibility must be respected, and indeed you may be doing form_for with a full-representation, which is the canonical use case of form_for reused in create and update views.

And yes, adhering to the spec/semantics is enough for me to consider adding support for this. Why does Rails support DELETE? Couldn't people delete via POST? Sure, and we are doing it all the time, but that's irrelevant, it is the duty of Rails to give you a way to do proper DELETE. When you work with polynomials you don't say "superscript", you say "exponent". And when people mean partial update, they are going to be able to say PATCH.

@mikekelly

@reschke is that an explicit part of PATCH in its latest incarnation? Of course - that approach would also prevent non-conditional PIUs, right?

@fxn I'm definitely not keen on this change - it's unnecessary, more complex, and stands to invalidate existing standard rails practices. It's unnecessary because it doesn't actually appear to achieve very much other than to satisfy:

  • a part of 2616 that doesn't even have to exist according to a valid interpretation
  • Roy Fielding's proposed clarifications in an spec that's not been published yet

we're going round in circles here and I have nothing to add at the moment, so I'm going to bow out for now

@reschke

@mikekelly this applies to any HTTP method; for PATCH, see http://greenbytes.de/tech/webdav/rfc5789.html#rfc.section.2.p.4

re: "that's not been published yet" -- oh, it is published as Internet Draft, just not approved as RFC yet. Publication of Internet Drafts is for community review and gathering feedback. Hint, hint.

@myronmarston

@mikekelly -- Over and over gain you've pointed out that rails supports partial idempotent updates. This is true, but update_attributes and the common idioms of rails (i.e. using accepts_nested_attributes_for) often result in a non-idempotent partial update. Consider this params hash from the API docs:

params = { :member => {
  :name => 'joe', :posts_attributes => [
    { :title => 'Kari, the awesome Ruby documentation browser!' },
    { :title => 'The egalitarian assumption of the modern citizen' },
    { :title => '', :_destroy => '1' } # this will be ignored
  ]
}}

This is not idempotent, because every time the update_attributes processes this hash, it creates two new posts (one with the title "Kari, the awesome Ruby documentation browser!", one with the title "The egalitarian assumption of the modern citizen").

Thus the conventions and idioms that rails encourages developers to use result in non-idempotent PUTs. Putting aside the full vs. partial question for a second, the non-idempotency here is a big problem.

@steveklabnik
Collaborator

And Fielding with a non-snarky answer:

PUT means PUT. There are no
partial updates in PUT. There was a half-assed attempt to add those
semantics by committee in the midst of standardizing HTTP, but that
attempt failed because PUT's existing semantics had already been deployed
and we can't graft partial updates on top of existing replace semantics.
Period. End of story. Hence, PATCH was defined in 1995 (and finally
standardized much later because the WebDAV group was lazy).

This answer is final. If anyone implements it differently in Rails,
then Rails will be neither compliant with HTTP nor compliant with REST.
Whether that matters to anyone developing Rails is besides the point.

Game over, man. Game over.

Now, to the question he addresses in his final paragraph: does this actually matter, to us? I obviously think it does.

@fxn
Owner

Case closed!

Yes, it matters.

We are about to release 3.2 RC, it's too late for 3.2, but I think the time it took to discuss this has been worthwhile because we didn't have such a clear and authoritative point of view at the beginning. Knowing which are the rules without doubt, we have the right mindset to work on the PR.

@mikekelly

please consider my response to that, and wait for the discussion to play out.

obligatory meme:

recognize, bitches.

@mikekelly

Roy is responsible for the changes in httpbis so it's hardly an earth shattering revelation that he believes 2616 should be interpreted that way.

@steveklabnik
Collaborator

He's also an author of 2616, so I'd expect him to be a pretty valid source when requesting clarification of it, too.

Knowing which are the rules without doubt, we have the right mindset to work on the PR.

Right. The real question is this: how far do we want Rails to go? Obviously, Rails cannot make every web service RESTful; that relies on human design. I would like to see Rails make it easier to comply with HTTP and HATEOAS, though, by providing helpful things in that regard. That's obviously longer-term and out of the scope of this pull request. However, this is a good start. I think the correct option as far as this pull request goes is this:

1) Add two new actions: patch and upsert. The update action should match to POST, the patch to PATCH, and upsert to PUT.
2) Start off with having this as a configuration option, ActionController.update_verb, which is set to :post in new rails apps, but has a default of :put so that old apps still work.
3) In Rails+1, change the default to :post but leave the option so that people can set it if they still don't want to update.

'upsert' isn't a great name. I've run across some people who don't know what this means, but it does capture what PUT is supposed to do in one, short word.

@reschke

@steveklabnik keep in mind that just changing PUT to PATCH doesn't make things better magically, you also need an Internet Media Type describing the patch format. And no, "application/json" doesn't.

@steveklabnik
Collaborator

Also, @mikekelly asked me to make something perfectly clear, so here it is:

I think that standards should be followed, because they're standards. They're the working agreements that we all go by, and if we discover that we're not compliant, software should changed based on that and nothing else. This doesn't mean they're infallible, and when they get revised, things should be changed again. Because compliance with a spec is the most important thing.

We now return to your regularly scheduled pull request.

@steveklabnik
Collaborator

@reschke right. Baby steps. That's why I think that POST actually works better for what rails calls update.

@mikekelly

it was tongue in cheek, and my point was that until httpbis makes it completely clear - there is no requirement to churn rails, because there are valid interpretations of 2616 which are perfectly fine with the current behaviour.

So, essentially, jumping the httpbis gun w/ this PR is driven by speculation and/or pedantry and not a need to conform to a spec which unquestionably requires the change (there isn't one, HTTPbis is a WIP) or fix a functional problem with rails (there isn't one of these, either). I think everyone should be clear about that.

@myronmarston

So, essentially, jumping the httpbis gun w/ this PR is driven by speculation and/or pedantry rather than conforming to a spec which unquestionably requires the change (there isn't one, HTTPbis is a WIP) or fixing a functional problem with rails (there isn't one of these, either). I think everyone should be clear about that.

That's not it at all. We have the one of the authors of the HTTP 1.1 spec, and the inventor of REST (which rails at least tries to adhere to...) saying that PUT means only full representations, and that rails is not in line with the HTTP or REST to allow it any other way.

@mikekelly

tl;dr cliffnotes for @tenderlove: change from this PR is significant, doesn't need to happen - may do in future if/when next revision of HTTP invalidates the approach. Roy Fielding is the ineffable fountain of HTTP wisdom.

@mikekelly

That's not it at all. We have the one of the authors of the HTTP 1.1 spec, and the inventor of REST (which rails at least tries to adhere to...) saying that PUT means only full representations, and that rails is not in line with the HTTP or REST to allow it any other way.

Roy is an insanely smart guy and definitely an authoritative source on HTTP. His insight and opinion is important. But his opinion does not magically make 2616 more clear on this issue than it is. It's not clear. This is why Roy himself has revised it.

@fxn
Owner

@mikekelly we have not outlined yet the path, as I said before I don't expect to do any dramatic changes any time soon. Let's explore.

At this point in the thread, the conclusion is that we are going to add some support for PATCH and promote good practices regarding PUT/PATCH in Rails applications. Good practices as described by Roy Fielding.

But progressively, and with backwards compatibility as a must. At least for the time being.

@mikekelly

At this point in the thread, the conclusion is that we are going to add some support for PATCH and promote good practices regarding PUT/PATCH in Rails applications. Good practices as described by Roy Fielding.

But progressively, and with backwards compatibility as a must. At least for the time being.

Ok fair enough, in that case it looks like @NZKoz hit the nail on the head over a month ago.. #505 (comment)

@fxn
Owner

The point of following the discussion was to know whether we should have to add anything at all. At that point we didn't know.

If Roy said "yeah, just do partial updates with PUT" no flag should be added, no code should be in core.

If Roy says "PUT means PUT. There are no partial updates in PUT." that justifies working on the PR. Working on the PR means what I said above, it does not mean applying the very patch.

@gducharme

Allowing access to PATCH as a configuration option will help drive awareness and adoption. Further down the line, we might well see a larger number of devs and applications clamoring for a change of default configuration, in much the same way we saw jQuery make its way in core.

Count this as +1 to a configuration option.

@dlee

Rails 4 has been announced for the summer. How much PATCH support should we get in? There have been various proposals of how to support PATCH in Rails 4:

  • make PATCH and PUT both map to update and:
    • make edit forms use PATCH by default and PUT by configuration opt-in
    • make edit forms use PUT by default and PATCH by configuration opt-in
  • split update into different actions that map better to PUT and PATCH
@steveklabnik
Collaborator

@dhh mentioned on Twitter that he supports some kind of support.

Really, to be honest, I think this pull request should probably be closed, and a discussion thread should be made on rails-core about it. That's sorta out of the scope of this particular PR.

@josevalim
Owner

We should have a config option called config.method_for_update. Both routes and forms should respect this option. There is no need to generate both routes. It makes no sense to split update in two actions.

@josevalim
Owner

In other words, this pull request is almost good imo. It just needs to provide a config.method_for_update, make both AV and the router read from this config and change the router to stop generating both put and patch for update.

@steveklabnik
Collaborator

@josevalim It really depends on how far you actually want to go. A 'rails 4' gives a big opportunity to step back and evaluate the whole 'Rails REST' thing in general.

Then again, it seems that rails core wants rails 4 to be more of a normal release, so...

Regardless, I agree about this patch. It's good regardless of that stuff.

@dlee

If Rails core gives the OK, I can fix the patch according to @josevalim's recommendations.

@fxn
Owner

PATCH and PUT have different semantics. PUT means create/replace and it is idempotent. PATCH means update (full or partial) and it is not idempotent.

If your application is a s3 clone, you want to upload assets with PUT. I think we need to be able to have both working, rather than a exclusive option.

@josevalim
Owner

@fxn I agree both options should work, it is just a matter of what we choose as default. For instance, the router should generate just put or patch routes by default for resources, however if for some specific case I want to use both PUT and PATCH or another, I should be able to do it. The same for the view, responders, etc... they should work for both put and patch.

@fxn
Owner
@josevalim
Owner

So as both me and @fxn agree, we would love to receive a new pull request that adds those small changes to this already existing pull request. Thanks @dlee for holding on all this time.

@myronmarston

I hinted at this above, but to reiterate: besides changing rails so that it supports PATCH, I'd also like to see improved support for proper PUT semantics. Currently, rails idioms and conventions do not make this easy. Specifically:

  • The common AR method used for updates is update_attributes. It supports updates of the full representation, but it also supports partial updates, which, as we've discussed above, is a violation of PUT semantics (at least in the mind of Fielding and other HTTP experts).
  • update_attributes also supports non-idempotent updates when used in combination w/ accepts_nested_attributes_for.
  • I think the simplest way to provide an update method that does provide proper PUT semantics is a new method, replace_attributes. It would set any attribute that is not included in the hash to nil, and thus treat the given attribute hash as the full resource.

Would the rails core team by interested in the addition of this API? I'm willing to take a stab at it, and can open another ticket to discuss further details, but figured it was worth inquiring here first.

@dlee

I like that idea; it'd make adhering to HTTP much easier.

I'd prefer a method name that more strongly indicates the full-resource replacement. What do you think about full_replace, replace_all_attributes, replace_resource, replace_with, or simply replace? An even stronger word might be supplant, but that just doesn't have a nice ring to it.

@josevalim
Owner

If there would be a method, I would simply call it replace_attributes. BUT in my opinion and experience it doesn't make sense at all to have a method like this in Rails because replace_attributes would be tied to the model while its behavior should actually be given by the resource (which is then defined by the application). Remember that different actors in the system (for example user and admin) may see the same model as two different resources where PUT would behave differently for each. Even without those actors, a single model may be exposed as two different resources or vice-versa.

In other words, replace_attributes (or anything like that) would be a poor fit and ultimately lead to poorly designed APIs as people would believe that a call to replace_attributes would be everything you would need to have proper PUT semantics.

@myronmarston

You bring up good points, @josevalim. I agree that people tend to think "model == HTTP resource" (or not even really think in terms of HTTP resources) and replace_attributes may seem to implicitly condone that line of thinking.

That said, rails doesn't currently have anything that helps make it easy to implement proper PUT semantics. If you want to implement proper PUTs you are essentially totally on your own. Do you have a better suggestion? (Also, should we take this to another thread? I don't want to derail the discussion here, but it's certainly related).

As I see it, replace_attributes isn't the perfect answer to HTTP spec or REST compliance any more than the rails scaffolds are. But it is a tool that can help make it easier to implement proper semantics (FWIW--I implemented this in one of my models on a recent project, and it worked great).

@josevalim
Owner

How Rails doesn't provide anything? Rails is a framework, anyone can provide his own PUT setup according to his application semantics, as you did. And probably in less than 30LOC! Please start a new discussion as I have no interest in the direction this is going.

@fxn
Owner

Yeah, me neither.

Resources are conceptual, ARs model database tables. There's a gap there that I think would show as a complicated setup for such a simple thing.

I prefer to discuss that in another issue or PR.

@myronmarston

@josevalim and @fxn: no need for another ticket. Two core rails members have weighed in against the idea. Thanks for the input. That's one less open source contribution I need to worry about; I have enough on my plate as it is.

@fxn fxn closed this
@arunagw
Collaborator

#5130 merged.

@miguelsan

In the risk on being pedant by arguing on a closed discussion against the last three comments (@josevalim, @fxn and @myronmarston), where I even haven't any personal use case yet, I will still make my point here, since I don't know where it goes better.

Rails does not provide a clean way to map resources to models "to and fro", which makes josevalim's point somewhat valid. I mean, devs usually call a model into a variable (normally in the controlle's show method), but they might as well make a mix of some related models to represent the resource that they want to expose. In this second case, they then have to rip the params off in the update action, so that they get the different models to validate and save and so on. That's what I see as a weak part of the framework. I feel that josevalim's argument could backing up a kind of "presenters-and-back" as a best way to translate/map models into resources, and that the implementation of such a paradigm into Rails should be seriously weighted. Were they dismissed (which I suppose, for that is a whole subject in itself), I would then come to my conclusion: I dislike disregarding a new method called replace_resource , which sets all empty attributes to nil (or blank, depending on validation rules), supposed that it gets applied on a single model. You can afterwards put all needed warnings in the documentation about the semantic meaning of it. I mean, Rails 4 is going to promote a new verb just to adhere to standards, without any appreciable gain in the short term, just for the sake of it? Do you really mean that you are going to leave devs on their own, having to understand why to use this or that, without further help? "Boy, if you want to make proper use of this new feature that we advocate, you'll have to write your own methods for it, it's just 30LOC, boy. After all, Rails is just a framework, don't expect too much from us."

See my point? I know I am a bit sarcastic at giving reasons for my petition, but I do it just in order to make it crystal clear and avoid another six months of discussion on the need of such method. I hope I got it.

PS. Sorry for coming in so late, I just saw the thread linked in the Rails Blog. I got through the whole of it, oh my!

@miguelsan

I elaborated a bit more on the subject of ResourceAccessor and how and why the replace_resource method is needed..

@bf4

fwiw, I backported the HTTP PATCH verb work for Rails 3.2 https://gist.github.com/bf4/8940203

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on May 10, 2011
  1. @dlee

    Use PATCH instead of PUT; Fixes issue #348

    dlee authored
    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.
Commits on May 11, 2011
  1. @dlee

    Make method for update forms configurable

    dlee authored
    Make :put the default method for backwards compatibility. The generator for new
    Rails projects configures :patch a the default method in config/application.rb.
Commits on Jun 25, 2011
  1. @dlee
This page is out of date. Refresh to see the latest.
Showing with 326 additions and 87 deletions.
  1. +2 −2 actionpack/lib/action_controller/metal/http_authentication.rb
  2. +3 −2 actionpack/lib/action_controller/metal/responder.rb
  3. +6 −1 actionpack/lib/action_controller/test_case.rb
  4. +6 −0 actionpack/lib/action_dispatch/http/request.rb
  5. +9 −6 actionpack/lib/action_dispatch/routing.rb
  6. +28 −7 actionpack/lib/action_dispatch/routing/mapper.rb
  7. +19 −7 actionpack/lib/action_dispatch/testing/integration.rb
  8. +4 −2 actionpack/lib/action_view/helpers/form_helper.rb
  9. +3 −3 actionpack/lib/action_view/helpers/form_tag_helper.rb
  10. +4 −4 actionpack/lib/action_view/helpers/url_helper.rb
  11. +1 −1  actionpack/test/controller/caching_test.rb
  12. +23 −1 actionpack/test/controller/integration_test.rb
  13. +64 −0 actionpack/test/controller/mime_responds_test.rb
  14. +14 −1 actionpack/test/controller/request_forgery_protection_test.rb
  15. +22 −9 actionpack/test/controller/resources_test.rb
  16. +12 −1 actionpack/test/controller/routing_test.rb
  17. +4 −4 actionpack/test/dispatch/request_test.rb
  18. +1 −0  actionpack/test/dispatch/routing_assertions_test.rb
  19. +9 −0 actionpack/test/template/form_helper_test.rb
  20. +6 −0 actionpack/test/template/form_tag_helper_test.rb
  21. +3 −3 activemodel/lib/active_model/lint.rb
  22. +7 −0 activeresource/lib/active_resource/connection.rb
  23. +10 −0 activeresource/lib/active_resource/custom_methods.rb
  24. +5 −3 activeresource/lib/active_resource/http_mock.rb
  25. +8 −4 activeresource/test/cases/format_test.rb
  26. +2 −2 activeresource/test/cases/http_mock_test.rb
  27. +1 −1  railties/guides/source/action_controller_overview.textile
  28. +3 −2 railties/guides/source/ajax_on_rails.textile
  29. +5 −2 railties/guides/source/configuring.textile
  30. +10 −5 railties/guides/source/form_helpers.textile
  31. +14 −6 railties/guides/source/routing.textile
  32. +1 −1  railties/guides/source/security.textile
  33. +4 −2 railties/guides/source/testing.textile
  34. +3 −3 railties/lib/rails/generators/active_model.rb
  35. +3 −0  railties/lib/rails/generators/rails/app/templates/config/application.rb
  36. +2 −2 railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
  37. +5 −0 railties/test/generators/app_generator_test.rb
View
4 actionpack/lib/action_controller/metal/http_authentication.rb
@@ -276,7 +276,7 @@ def secret_token(request)
#
# An implementation might choose not to accept a previously used nonce or a previously used digest, in order to
# protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for
- # POST or PUT requests and a time-stamp for GET requests. For more details on the issues involved see Section 4
+ # POST, PUT, or PATCH requests and a time-stamp for GET requests. For more details on the issues involved see Section 4
# of this document.
#
# The nonce is opaque to the client. Composed of Time, and hash of Time with secret
@@ -290,7 +290,7 @@ def nonce(secret_key, time = Time.now)
end
# Might want a shorter timeout depending on whether the request
- # is a PUT or POST, and if client is browser or web service.
+ # is a PATCH, PUT, or POST, and if client is browser or web service.
# Can be much shorter if the Stale directive is implemented. This would
# allow a user to use new nonce without prompting user again for their
# username and password.
View
5 actionpack/lib/action_controller/metal/responder.rb
@@ -53,7 +53,7 @@ module ActionController #:nodoc:
# end
# end
#
- # The same happens for PUT and DELETE requests.
+ # The same happens for PATCH/PUT and DELETE requests.
#
# === Nested resources
#
@@ -118,6 +118,7 @@ class Responder
ACTIONS_FOR_VERBS = {
:post => :new,
+ :patch => :edit,
:put => :edit
}
@@ -133,7 +134,7 @@ def initialize(controller, resources, options={})
end
delegate :head, :render, :redirect_to, :to => :controller
- delegate :get?, :post?, :put?, :delete?, :to => :request
+ delegate :get?, :post?, :patch?, :put?, :delete?, :to => :request
# Undefine :to_json and :to_yaml since it's defined on Object
undef_method(:to_json) if method_defined?(:to_json)
View
7 actionpack/lib/action_controller/test_case.rb
@@ -224,7 +224,7 @@ def exists?
# == Basic example
#
# Functional tests are written as follows:
- # 1. First, one uses the +get+, +post+, +put+, +delete+ or +head+ method to simulate
+ # 1. First, one uses the +get+, +post+, +patch+, +put+, +delete+ or +head+ method to simulate
# an HTTP request.
# 2. Then, one asserts whether the current state is as expected. "State" can be anything:
# the controller's HTTP response, the database contents, etc.
@@ -369,6 +369,11 @@ def post(action, parameters = nil, session = nil, flash = nil)
process(action, parameters, session, flash, "POST")
end
+ # Executes a request simulating PATCH HTTP method and set/volley the response
+ def patch(action, parameters = nil, session = nil, flash = nil)
+ process(action, parameters, session, flash, "PATCH")
+ end
+
# Executes a request simulating PUT HTTP method and set/volley the response
def put(action, parameters = nil, session = nil, flash = nil)
process(action, parameters, session, flash, "PUT")
View
6 actionpack/lib/action_dispatch/http/request.rb
@@ -105,6 +105,12 @@ def post?
HTTP_METHOD_LOOKUP[request_method] == :post
end
+ # Is this a PATCH request?
+ # Equivalent to <tt>request.request_method == :patch</tt>.
+ def patch?
+ HTTP_METHOD_LOOKUP[request_method] == :patch
+ end
+
# Is this a PUT request?
# Equivalent to <tt>request.request_method == :put</tt>.
def put?
View
15 actionpack/lib/action_dispatch/routing.rb
@@ -182,10 +182,13 @@ module ActionDispatch
#
# == HTTP Methods
#
- # Using the <tt>:via</tt> option when specifying a route allows you to restrict it to a specific HTTP method.
- # Possible values are <tt>:post</tt>, <tt>:get</tt>, <tt>:put</tt>, <tt>:delete</tt> and <tt>:any</tt>.
- # If your route needs to respond to more than one method you can use an array, e.g. <tt>[ :get, :post ]</tt>.
- # The default value is <tt>:any</tt> which means that the route will respond to any of the HTTP methods.
+ # Using the <tt>:via</tt> option when specifying a route allows you to
+ # restrict it to a specific HTTP method. Possible values are <tt>:post</tt>,
+ # <tt>:get</tt>, <tt>:patch</tt>, <tt>:put</tt>, <tt>:delete</tt> and
+ # <tt>:any</tt>. If your route needs to respond to more than one method you
+ # can use an array, e.g. <tt>[ :get, :post ]</tt>. The default value is
+ # <tt>:any</tt> which means that the route will respond to any of the HTTP
+ # methods.
#
# Examples:
#
@@ -198,7 +201,7 @@ module ActionDispatch
# === HTTP helper methods
#
# An alternative method of specifying which HTTP method a route should respond to is to use the helper
- # methods <tt>get</tt>, <tt>post</tt>, <tt>put</tt> and <tt>delete</tt>.
+ # methods <tt>get</tt>, <tt>post</tt>, <tt>patch</tt>, <tt>put</tt> and <tt>delete</tt>.
#
# Examples:
#
@@ -284,7 +287,7 @@ module Routing
autoload :PolymorphicRoutes, 'action_dispatch/routing/polymorphic_routes'
SEPARATORS = %w( / . ? ) #:nodoc:
- HTTP_METHODS = [:get, :head, :post, :put, :delete, :options] #:nodoc:
+ HTTP_METHODS = [:get, :head, :post, :patch, :put, :delete, :options] #:nodoc:
# A helper module to hold URL related helpers.
module Helpers #:nodoc:
View
35 actionpack/lib/action_dispatch/routing/mapper.rb
@@ -469,7 +469,7 @@ module HttpHelpers
#
# Example:
#
- # get 'bacon', :to => 'food#bacon'
+ # get 'bacon', :to => 'food#bacon'
def get(*args, &block)
map_method(:get, *args, &block)
end
@@ -479,27 +479,37 @@ def get(*args, &block)
#
# Example:
#
- # post 'bacon', :to => 'food#bacon'
+ # post 'bacon', :to => 'food#bacon'
def post(*args, &block)
map_method(:post, *args, &block)
end
+ # Define a route that only recognizes HTTP PATCH.
+ # For supported arguments, see <tt>Base#match</tt>.
+ #
+ # Example:
+ #
+ # patch 'bacon', :to => 'food#bacon'
+ def patch(*args, &block)
+ map_method(:patch, *args, &block)
+ end
+
# Define a route that only recognizes HTTP PUT.
# For supported arguments, see <tt>Base#match</tt>.
#
# Example:
#
- # put 'bacon', :to => 'food#bacon'
+ # put 'bacon', :to => 'food#bacon'
def put(*args, &block)
map_method(:put, *args, &block)
end
- # Define a route that only recognizes HTTP PUT.
+ # Define a route that only recognizes HTTP DELETE.
# For supported arguments, see <tt>Base#match</tt>.
#
# Example:
#
- # delete 'broccoli', :to => 'food#broccoli'
+ # delete 'broccoli', :to => 'food#broccoli'
def delete(*args, &block)
map_method(:delete, *args, &block)
end
@@ -532,6 +542,7 @@ def map_method(method, *args, &block)
# POST /admin/posts
# GET /admin/posts/1
# GET /admin/posts/1/edit
+ # PATCH /admin/posts/1
# PUT /admin/posts/1
# DELETE /admin/posts/1
#
@@ -566,6 +577,7 @@ def map_method(method, *args, &block)
# POST /admin/posts
# GET /admin/posts/1
# GET /admin/posts/1/edit
+ # PATCH /admin/posts/1
# PUT /admin/posts/1
# DELETE /admin/posts/1
module Scoping
@@ -661,6 +673,7 @@ def controller(controller, options={})
# new_admin_post GET /admin/posts/new(.:format) {:action=>"new", :controller=>"admin/posts"}
# edit_admin_post GET /admin/posts/:id/edit(.:format) {:action=>"edit", :controller=>"admin/posts"}
# admin_post GET /admin/posts/:id(.:format) {:action=>"show", :controller=>"admin/posts"}
+ # admin_post PATCH /admin/posts/:id(.:format) {:action=>"update", :controller=>"admin/posts"}
# admin_post PUT /admin/posts/:id(.:format) {:action=>"update", :controller=>"admin/posts"}
# admin_post DELETE /admin/posts/:id(.:format) {:action=>"destroy", :controller=>"admin/posts"}
#
@@ -974,7 +987,7 @@ def resources_path_names(options)
#
# resource :geocoder
#
- # creates six different routes in your application, all mapping to
+ # creates seven different routes in your application, all mapping to
# the GeoCoders controller (note that the controller is named after
# the plural):
#
@@ -982,6 +995,7 @@ def resources_path_names(options)
# POST /geocoder
# GET /geocoder
# GET /geocoder/edit
+ # PATCH /geocoder
# PUT /geocoder
# DELETE /geocoder
#
@@ -1008,6 +1022,7 @@ def resource(*resources, &block)
member do
get :edit if parent_resource.actions.include?(:edit)
get :show if parent_resource.actions.include?(:show)
+ patch :update if parent_resource.actions.include?(:update)
put :update if parent_resource.actions.include?(:update)
delete :destroy if parent_resource.actions.include?(:destroy)
end
@@ -1023,13 +1038,15 @@ def resource(*resources, &block)
#
# resources :photos
#
- # creates seven different routes in your application, all mapping to
+ # creates eight different routes in your application, all mapping to
# the Photos controller:
#
+ # GET /photos
# GET /photos/new
# POST /photos
# GET /photos/:id
# GET /photos/:id/edit
+ # PATCH /photos/:id
# PUT /photos/:id
# DELETE /photos/:id
#
@@ -1041,10 +1058,12 @@ def resource(*resources, &block)
#
# This generates the following comments routes:
#
+ # GET /photos/:id/comments
# GET /photos/:id/comments/new
# POST /photos/:id/comments
# GET /photos/:id/comments/:id
# GET /photos/:id/comments/:id/edit
+ # PATCH /photos/:id/comments/:id
# PUT /photos/:id/comments/:id
# DELETE /photos/:id/comments/:id
#
@@ -1102,6 +1121,7 @@ def resource(*resources, &block)
# new_post_comment GET /sekret/posts/:post_id/comments/new(.:format)
# edit_comment GET /sekret/comments/:id/edit(.:format)
# comment GET /sekret/comments/:id(.:format)
+ # comment PATCH /sekret/comments/:id(.:format)
# comment PUT /sekret/comments/:id(.:format)
# comment DELETE /sekret/comments/:id(.:format)
#
@@ -1134,6 +1154,7 @@ def resources(*resources, &block)
member do
get :edit if parent_resource.actions.include?(:edit)
get :show if parent_resource.actions.include?(:show)
+ patch :update if parent_resource.actions.include?(:update)
put :update if parent_resource.actions.include?(:update)
delete :destroy if parent_resource.actions.include?(:destroy)
end
View
26 actionpack/lib/action_dispatch/testing/integration.rb
@@ -27,8 +27,8 @@ module RequestHelpers
# object's <tt>@response</tt> instance variable will point to the same
# response object.
#
- # You can also perform POST, PUT, DELETE, and HEAD requests with +#post+,
- # +#put+, +#delete+, and +#head+.
+ # You can also perform POST, PATCH, PUT, DELETE, and HEAD requests with
+ # +#post+, +#patch+, +#put+, +#delete+, and +#head+.
def get(path, parameters = nil, headers = nil)
process :get, path, parameters, headers
end
@@ -39,6 +39,12 @@ def post(path, parameters = nil, headers = nil)
process :post, path, parameters, headers
end
+ # Performs a PATCH request with the given parameters. See +#get+ for more
+ # details.
+ def patch(path, parameters = nil, headers = nil)
+ process :patch, path, parameters, headers
+ end
+
# Performs a PUT request with the given parameters. See +#get+ for more
# details.
def put(path, parameters = nil, headers = nil)
@@ -60,10 +66,10 @@ def head(path, parameters = nil, headers = nil)
# Performs an XMLHttpRequest request with the given parameters, mirroring
# a request from the Prototype library.
#
- # The request_method is +:get+, +:post+, +:put+, +:delete+ or +:head+; the
- # parameters are +nil+, a hash, or a url-encoded or multipart string;
- # the headers are a hash. Keys are automatically upcased and prefixed
- # with 'HTTP_' if not already.
+ # The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or
+ # +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart
+ # string; the headers are a hash. Keys are automatically upcased and
+ # prefixed with 'HTTP_' if not already.
def xml_http_request(request_method, path, parameters = nil, headers = nil)
headers ||= {}
headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
@@ -103,6 +109,12 @@ def post_via_redirect(path, parameters = nil, headers = nil)
request_via_redirect(:post, path, parameters, headers)
end
+ # Performs a PATCH request, following any subsequent redirect.
+ # See +request_via_redirect+ for more information.
+ def patch_via_redirect(path, parameters = nil, headers = nil)
+ request_via_redirect(:patch, path, parameters, headers)
+ end
+
# Performs a PUT request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
def put_via_redirect(path, parameters = nil, headers = nil)
@@ -316,7 +328,7 @@ def reset!
@integration_session = Integration::Session.new(app)
end
- %w(get post put head delete cookies assigns
+ %w(get post patch put head delete cookies assigns
xml_http_request xhr get_via_redirect post_via_redirect).each do |method|
define_method(method) do |*args|
reset! unless integration_session
View
6 actionpack/lib/action_view/helpers/form_helper.rb
@@ -245,7 +245,7 @@ def convert_to_model(object)
#
# You can force the form to use the full array of HTTP verbs by setting
#
- # :method => (:get|:post|:put|:delete)
+ # :method => (:get|:post|:patch|:put|:delete)
#
# in the options hash. If the verb is not GET or POST, which are natively supported by HTML forms, the
# form will be set to POST and a hidden input called _method will carry the intended verb for the server
@@ -381,7 +381,7 @@ def apply_form_for_options!(object_or_array, options) #:nodoc:
object = convert_to_model(object)
as = options[:as]
- action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :put] : [:new, :post]
+ action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, ActionView::Base.default_method_for_update] : [:new, :post]
options[:html].reverse_merge!(
:class => as ? "#{as}_#{action}" : dom_class(object, action),
:id => as ? "#{as}_#{action}" : dom_id(object, action),
@@ -1383,7 +1383,9 @@ def convert_to_model(object)
ActiveSupport.on_load(:action_view) do
class ActionView::Base
cattr_accessor :default_form_builder
+ cattr_accessor :default_method_for_update
@@default_form_builder = ::ActionView::Helpers::FormBuilder
+ @@default_method_for_update = :put
end
end
end
View
6 actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -23,7 +23,7 @@ module FormTagHelper
# ==== Options
# * <tt>:multipart</tt> - If set to true, the enctype is set to "multipart/form-data".
# * <tt>:method</tt> - The method to use when submitting the form, usually either "get" or "post".
- # If "put", "delete", or another verb is used, a hidden input with name <tt>_method</tt>
+ # If "patch", "delete", or another verb is used, a hidden input with name <tt>_method</tt>
# is added to simulate the verb over post.
# * <tt>:authenticity_token</tt> - Authenticity token to use in the form. Use only if you need to
# pass custom authenticity token string, or to not add authenticity_token field at all
@@ -36,8 +36,8 @@ module FormTagHelper
# form_tag('/posts')
# # => <form action="/posts" method="post">
#
- # form_tag('/posts/1', :method => :put)
- # # => <form action="/posts/1" method="put">
+ # form_tag('/posts/1', :method => :patch)
+ # # => <form action="/posts/1" method="patch">
#
# form_tag('/upload', :multipart => true)
# # => <form action="/upload" method="post" enctype="multipart/form-data">
View
8 actionpack/lib/action_view/helpers/url_helper.rb
@@ -146,12 +146,12 @@ def url_for(options = {})
# create an HTML form and immediately submit the form for processing using
# the HTTP verb specified. Useful for having links perform a POST operation
# in dangerous actions like deleting a record (which search bots can follow
- # while spidering your site). Supported verbs are <tt>:post</tt>, <tt>:delete</tt> and <tt>:put</tt>.
+ # while spidering your site). Supported verbs are <tt>:post</tt>, <tt>:delete</tt>, <tt>:patch</tt>, and <tt>:put</tt>.
# Note that if the user has JavaScript disabled, the request will fall back
# to using GET. If <tt>:href => '#'</tt> is used and the user has JavaScript
# disabled clicking the link will have no effect. If you are relying on the
# POST behavior, you should check for it in your controller's action by using
- # the request object's methods for <tt>post?</tt>, <tt>delete?</tt> or <tt>put?</tt>.
+ # the request object's methods for <tt>post?</tt>, <tt>delete?</tt>, <tt>:patch</tt>, or <tt>put?</tt>.
# * <tt>:remote => true</tt> - This will allow the unobtrusive JavaScript
# driver to make an Ajax request to the URL in question instead of following
# the link. The drivers each provide mechanisms for listening for the
@@ -272,7 +272,7 @@ def link_to(*args, &block)
#
# There are a few special +html_options+:
# * <tt>:method</tt> - Symbol of HTTP verb. Supported verbs are <tt>:post</tt>, <tt>:get</tt>,
- # <tt>:delete</tt> and <tt>:put</tt>. By default it will be <tt>:post</tt>.
+ # <tt>:delete</tt>, <tt>:patch</tt>, and <tt>:put</tt>. By default it will be <tt>:post</tt>.
# * <tt>:disabled</tt> - If set to true, it will generate a disabled button.
# * <tt>:confirm</tt> - This will use the unobtrusive JavaScript driver to
# prompt with the question specified. If the user accepts, the link is
@@ -319,7 +319,7 @@ def button_to(name, options = {}, html_options = {})
convert_boolean_attributes!(html_options, %w( disabled ))
method_tag = ''
- if (method = html_options.delete('method')) && %w{put delete}.include?(method.to_s)
+ if (method = html_options.delete('method')) && %w{patch put delete}.include?(method.to_s)
method_tag = tag('input', :type => 'hidden', :name => '_method', :value => method.to_s)
end
View
2  actionpack/test/controller/caching_test.rb
@@ -140,7 +140,7 @@ def test_should_cache_ok_at_custom_path
end
[:ok, :no_content, :found, :not_found].each do |status|
- [:get, :post, :put, :delete].each do |method|
+ [:get, :post, :patch, :put, :delete].each do |method|
unless method == :get and status == :ok
define_method "test_shouldnt_cache_#{method}_with_#{status}_status" do
send(method, status)
View
24 actionpack/test/controller/integration_test.rb
@@ -63,6 +63,12 @@ def test_post_via_redirect
@session.post_via_redirect(path, args, headers)
end
+ def test_patch_via_redirect
+ path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" }
+ @session.expects(:request_via_redirect).with(:patch, path, args, headers)
+ @session.patch_via_redirect(path, args, headers)
+ end
+
def test_put_via_redirect
path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" }
@session.expects(:request_via_redirect).with(:put, path, args, headers)
@@ -87,6 +93,12 @@ def test_post
@session.post(path,params,headers)
end
+ def test_patch
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ @session.expects(:process).with(:patch,path,params,headers)
+ @session.patch(path,params,headers)
+ end
+
def test_put
path = "/index"; params = "blah"; headers = {:location => 'blah'}
@session.expects(:process).with(:put,path,params,headers)
@@ -125,6 +137,16 @@ def test_xml_http_request_post
@session.xml_http_request(:post,path,params,headers)
end
+ def test_xml_http_request_patch
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ headers_after_xhr = headers.merge(
+ "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
+ "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*"
+ )
+ @session.expects(:process).with(:patch,path,params,headers_after_xhr)
+ @session.xml_http_request(:patch,path,params,headers)
+ end
+
def test_xml_http_request_put
path = "/index"; params = "blah"; headers = {:location => 'blah'}
headers_after_xhr = headers.merge(
@@ -212,7 +234,7 @@ def test_integration_methods_called
@integration_session.stubs(:generic_url_rewriter)
@integration_session.stubs(:process)
- %w( get post head put delete ).each do |verb|
+ %w( get post head patch put delete ).each do |verb|
assert_nothing_raised("'#{verb}' should use integration test methods") { __send__(verb, '/') }
end
end
View
64 actionpack/test/controller/mime_responds_test.rb
@@ -723,6 +723,69 @@ def test_using_resource_for_post_with_xml_yields_unprocessable_entity_on_failure
end
end
+ def test_using_resource_for_patch_with_html_redirects_on_success
+ with_test_route_set do
+ patch :using_resource
+ assert_equal "text/html", @response.content_type
+ assert_equal 302, @response.status
+ assert_equal "http://www.example.com/customers/13", @response.location
+ assert @response.redirect?
+ end
+ end
+
+ def test_using_resource_for_patch_with_html_rerender_on_failure
+ with_test_route_set do
+ errors = { :name => :invalid }
+ Customer.any_instance.stubs(:errors).returns(errors)
+ patch :using_resource
+ assert_equal "text/html", @response.content_type
+ assert_equal 200, @response.status
+ assert_equal "Edit world!\n", @response.body
+ assert_nil @response.location
+ end
+ end
+
+ def test_using_resource_for_patch_with_html_rerender_on_failure_even_on_method_override
+ with_test_route_set do
+ errors = { :name => :invalid }
+ Customer.any_instance.stubs(:errors).returns(errors)
+ @request.env["rack.methodoverride.original_method"] = "POST"
+ patch :using_resource
+ assert_equal "text/html", @response.content_type
+ assert_equal 200, @response.status
+ assert_equal "Edit world!\n", @response.body
+ assert_nil @response.location
+ end
+ end
+
+ def test_using_resource_for_patch_with_xml_yields_ok_on_success
+ @request.accept = "application/xml"
+ patch :using_resource
+ assert_equal "application/xml", @response.content_type
+ assert_equal 200, @response.status
+ assert_equal " ", @response.body
+ end
+
+ def test_using_resource_for_patch_with_json_yields_ok_on_success
+ Customer.any_instance.stubs(:to_json).returns('{"name": "David"}')
+ @request.accept = "application/json"
+ patch :using_resource
+ assert_equal "application/json", @response.content_type
+ assert_equal 200, @response.status
+ assert_equal "{}", @response.body
+ end
+
+ def test_using_resource_for_patch_with_xml_yields_unprocessable_entity_on_failure
+ @request.accept = "application/xml"
+ errors = { :name => :invalid }
+ Customer.any_instance.stubs(:errors).returns(errors)
+ patch :using_resource
+ assert_equal "application/xml", @response.content_type
+ assert_equal 422, @response.status
+ assert_equal errors.to_xml, @response.body
+ assert_nil @response.location
+ end
+
def test_using_resource_for_put_with_html_redirects_on_success
with_test_route_set do
put :using_resource
@@ -786,6 +849,7 @@ def test_using_resource_for_put_with_xml_yields_unprocessable_entity_on_failure
assert_nil @response.location
end
+
def test_using_resource_for_delete_with_html_redirects_on_success
with_test_route_set do
Customer.any_instance.stubs(:destroyed?).returns(true)
View
15 actionpack/test/controller/request_forgery_protection_test.rb
@@ -115,6 +115,10 @@ def test_should_not_allow_post_without_token_irrespective_of_format
assert_blocked { post :index, :format=>'xml' }
end
+ def test_should_not_allow_patch_without_token
+ assert_blocked { patch :index }
+ end
+
def test_should_not_allow_put_without_token
assert_blocked { put :index }
end
@@ -131,6 +135,10 @@ def test_should_allow_post_with_token
assert_not_blocked { post :index, :authenticity_token => @token }
end
+ def test_should_allow_patch_with_token
+ assert_not_blocked { patch :index, :authenticity_token => @token }
+ end
+
def test_should_allow_put_with_token
assert_not_blocked { put :index, :authenticity_token => @token }
end
@@ -149,6 +157,11 @@ def test_should_allow_delete_with_token_in_header
assert_not_blocked { delete :index }
end
+ def test_should_allow_patch_with_token_in_header
+ @request.env['HTTP_X_CSRF_TOKEN'] = @token
+ assert_not_blocked { patch :index }
+ end
+
def test_should_allow_put_with_token_in_header
@request.env['HTTP_X_CSRF_TOKEN'] = @token
assert_not_blocked { put :index }
@@ -210,7 +223,7 @@ def test_should_not_render_button_to_with_token_tag
end
def test_should_allow_all_methods_without_token
- [:post, :put, :delete].each do |method|
+ [:post, :patch, :put, :delete].each do |method|
assert_nothing_raised { send(method, :index)}
end
end
View
31 actionpack/test/controller/resources_test.rb
@@ -150,7 +150,7 @@ def test_with_name_prefix
end
def test_with_collection_actions
- actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete }
+ actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete, 'e' => :patch }
with_routing do |set|
set.draw do
@@ -159,6 +159,7 @@ def test_with_collection_actions
put :b, :on => :collection
post :c, :on => :collection
delete :d, :on => :collection
+ patch :e, :on => :collection
end
end
@@ -177,7 +178,7 @@ def test_with_collection_actions
end
def test_with_collection_actions_and_name_prefix
- actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete }
+ actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete, 'e' => :patch }
with_routing do |set|
set.draw do
@@ -187,6 +188,7 @@ def test_with_collection_actions_and_name_prefix
put :b, :on => :collection
post :c, :on => :collection
delete :d, :on => :collection
+ patch :e, :on => :collection
end
end
end
@@ -233,7 +235,7 @@ def test_with_collection_actions_and_name_prefix_and_member_action_with_same_nam
end
def test_with_collection_action_and_name_prefix_and_formatted
- actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete }
+ actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete, 'e' => :patch }
with_routing do |set|
set.draw do
@@ -243,6 +245,7 @@ def test_with_collection_action_and_name_prefix_and_formatted
put :b, :on => :collection
post :c, :on => :collection
delete :d, :on => :collection
+ patch :e, :on => :collection
end
end
end
@@ -262,7 +265,7 @@ def test_with_collection_action_and_name_prefix_and_formatted
end
def test_with_member_action
- [:put, :post].each do |method|
+ [:patch, :put, :post].each do |method|
with_restful_routing :messages, :member => { :mark => method } do
mark_options = {:action => 'mark', :id => '1'}
mark_path = "/messages/1/mark"
@@ -286,7 +289,7 @@ def test_with_member_action_and_requirement
end
def test_member_when_override_paths_for_default_restful_actions_with
- [:put, :post].each do |method|
+ [:patch, :put, :post].each do |method|
with_restful_routing :messages, :member => { :mark => method }, :path_names => {:new => 'nuevo'} do
mark_options = {:action => 'mark', :id => '1', :controller => "messages"}
mark_path = "/messages/1/mark"
@@ -303,7 +306,7 @@ def test_member_when_override_paths_for_default_restful_actions_with
end
def test_with_two_member_actions_with_same_method
- [:put, :post].each do |method|
+ [:patch, :put, :post].each do |method|
with_routing do |set|
set.draw do
resources :messages do
@@ -556,7 +559,7 @@ def test_should_create_nested_singleton_resource_routes
end
def test_singleton_resource_with_member_action
- [:put, :post].each do |method|
+ [:patch, :put, :post].each do |method|
with_routing do |set|
set.draw do
resource :account do
@@ -578,7 +581,7 @@ def test_singleton_resource_with_member_action
end
def test_singleton_resource_with_two_member_actions_with_same_method
- [:put, :post].each do |method|
+ [:patch, :put, :post].each do |method|
with_routing do |set|
set.draw do
resource :account do
@@ -643,13 +646,17 @@ def test_should_nest_singleton_resource_in_resources
end
end
- def test_should_not_allow_delete_or_put_on_collection_path
+ def test_should_not_allow_delete_or_patch_or_put_on_collection_path
controller_name = :messages
with_restful_routing controller_name do
options = { :controller => controller_name.to_s }
collection_path = "/#{controller_name}"
assert_raise(ActionController::RoutingError) do
+ assert_recognizes(options.merge(:action => 'update'), :path => collection_path, :method => :patch)
+ end
+
+ assert_raise(ActionController::RoutingError) do
assert_recognizes(options.merge(:action => 'update'), :path => collection_path, :method => :put)
end
@@ -1165,6 +1172,7 @@ def assert_restful_routes_for(controller_name, options = {})
assert_recognizes(options[:shallow_options].merge(:action => 'show', :id => '1'), :path => member_path, :method => :get)
assert_recognizes(options[:shallow_options].merge(:action => 'edit', :id => '1'), :path => edit_member_path, :method => :get)
assert_recognizes(options[:shallow_options].merge(:action => 'update', :id => '1'), :path => member_path, :method => :put)
+ assert_recognizes(options[:shallow_options].merge(:action => 'update', :id => '1'), :path => member_path, :method => :patch)
assert_recognizes(options[:shallow_options].merge(:action => 'destroy', :id => '1'), :path => member_path, :method => :delete)
assert_recognizes(options[:options].merge(:action => 'index', :format => 'xml'), :path => "#{collection_path}.xml", :method => :get)
@@ -1173,6 +1181,7 @@ def assert_restful_routes_for(controller_name, options = {})
assert_recognizes(options[:shallow_options].merge(:action => 'show', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :get)
assert_recognizes(options[:shallow_options].merge(:action => 'edit', :id => '1', :format => 'xml'), :path => formatted_edit_member_path, :method => :get)
assert_recognizes(options[:shallow_options].merge(:action => 'update', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :put)
+ assert_recognizes(options[:shallow_options].merge(:action => 'update', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :patch)
assert_recognizes(options[:shallow_options].merge(:action => 'destroy', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :delete)
yield options[:options] if block_given?
@@ -1252,6 +1261,7 @@ def assert_singleton_routes_for(singleton_name, options = {})
assert_recognizes(options[:options].merge(:action => 'edit'), :path => edit_path, :method => :get)
assert_recognizes(options[:options].merge(:action => 'create'), :path => full_path, :method => :post)
assert_recognizes(options[:options].merge(:action => 'update'), :path => full_path, :method => :put)
+ assert_recognizes(options[:options].merge(:action => 'update'), :path => full_path, :method => :patch)
assert_recognizes(options[:options].merge(:action => 'destroy'), :path => full_path, :method => :delete)
assert_recognizes(options[:options].merge(:action => 'show', :format => 'xml'), :path => "#{full_path}.xml", :method => :get)
@@ -1259,6 +1269,7 @@ def assert_singleton_routes_for(singleton_name, options = {})
assert_recognizes(options[:options].merge(:action => 'edit', :format => 'xml'), :path => formatted_edit_path, :method => :get)
assert_recognizes(options[:options].merge(:action => 'create', :format => 'xml'), :path => "#{full_path}.xml", :method => :post)
assert_recognizes(options[:options].merge(:action => 'update', :format => 'xml'), :path => "#{full_path}.xml", :method => :put)
+ assert_recognizes(options[:options].merge(:action => 'update', :format => 'xml'), :path => "#{full_path}.xml", :method => :patch)
assert_recognizes(options[:options].merge(:action => 'destroy', :format => 'xml'), :path => "#{full_path}.xml", :method => :delete)
yield options[:options] if block_given?
@@ -1310,6 +1321,7 @@ def assert_resource_allowed_routes(controller, options, shallow_options, allowed
assert_whether_allowed(allowed, not_allowed, shallow_options, 'show', "#{shallow_path}#{format}", :get)
assert_whether_allowed(allowed, not_allowed, shallow_options, 'edit', "#{shallow_path}/edit#{format}", :get)
assert_whether_allowed(allowed, not_allowed, shallow_options, 'update', "#{shallow_path}#{format}", :put)
+ assert_whether_allowed(allowed, not_allowed, shallow_options, 'update', "#{shallow_path}#{format}", :patch)
assert_whether_allowed(allowed, not_allowed, shallow_options, 'destroy', "#{shallow_path}#{format}", :delete)
end
@@ -1322,6 +1334,7 @@ def assert_singleton_resource_allowed_routes(controller, options, allowed, not_a
assert_whether_allowed(allowed, not_allowed, options, 'show', "#{path}#{format}", :get)
assert_whether_allowed(allowed, not_allowed, options, 'edit', "#{path}/edit#{format}", :get)
assert_whether_allowed(allowed, not_allowed, options, 'update', "#{path}#{format}", :put)
+ assert_whether_allowed(allowed, not_allowed, options, 'update', "#{path}#{format}", :patch)
assert_whether_allowed(allowed, not_allowed, options, 'destroy', "#{path}#{format}", :delete)
end
View
13 actionpack/test/controller/routing_test.rb
@@ -531,11 +531,12 @@ def setup_request_method_routes_for(method)
match '/match' => 'books#get', :via => :get
match '/match' => 'books#post', :via => :post
match '/match' => 'books#put', :via => :put
+ match '/match' => 'books#patch', :via => :patch
match '/match' => 'books#delete', :via => :delete
end
end
- %w(GET POST PUT DELETE).each do |request_method|
+ %w(GET PATCH POST PUT DELETE).each do |request_method|
define_method("test_request_method_recognized_with_#{request_method}") do
setup_request_method_routes_for(request_method)
params = rs.recognize_path("/match", :method => request_method)
@@ -918,6 +919,7 @@ def test_recognize_with_http_methods
post "/people" => "people#create"
get "/people/:id" => "people#show", :as => "person"
put "/people/:id" => "people#update"
+ patch "/people/:id" => "people#update"
delete "/people/:id" => "people#destroy"
end
@@ -930,6 +932,9 @@ def test_recognize_with_http_methods
params = set.recognize_path("/people/5", :method => :put)
assert_equal("update", params[:action])
+ params = set.recognize_path("/people/5", :method => :patch)
+ assert_equal("update", params[:action])
+
assert_raise(ActionController::UnknownHttpMethod) {
set.recognize_path("/people", :method => :bacon)
}
@@ -942,6 +947,10 @@ def test_recognize_with_http_methods
assert_equal("update", params[:action])
assert_equal("5", params[:id])
+ params = set.recognize_path("/people/5", :method => :patch)
+ assert_equal("update", params[:action])
+ assert_equal("5", params[:id])
+
params = set.recognize_path("/people/5", :method => :delete)
assert_equal("destroy", params[:action])
assert_equal("5", params[:id])
@@ -1596,6 +1605,7 @@ def test_recognize_path
assert_equal({:controller => 'admin/users', :action => 'new'}, @routes.recognize_path('/admin/users/new', :method => :get))
assert_equal({:controller => 'admin/users', :action => 'show', :id => '1'}, @routes.recognize_path('/admin/users/1', :method => :get))
assert_equal({:controller => 'admin/users', :action => 'update', :id => '1'}, @routes.recognize_path('/admin/users/1', :method => :put))
+ assert_equal({:controller => 'admin/users', :action => 'update', :id => '1'}, @routes.recognize_path('/admin/users/1', :method => :patch))
assert_equal({:controller => 'admin/users', :action => 'destroy', :id => '1'}, @routes.recognize_path('/admin/users/1', :method => :delete))
assert_equal({:controller => 'admin/users', :action => 'edit', :id => '1'}, @routes.recognize_path('/admin/users/1/edit', :method => :get))
@@ -1619,6 +1629,7 @@ def test_recognize_path
assert_equal({:controller => 'people', :action => 'show', :id => '1'}, @routes.recognize_path('/people/1', :method => :get))
assert_equal({:controller => 'people', :action => 'show', :id => '1', :format => 'xml'}, @routes.recognize_path('/people/1.xml', :method => :get))
assert_equal({:controller => 'people', :action => 'update', :id => '1'}, @routes.recognize_path('/people/1', :method => :put))
+ assert_equal({:controller => 'people', :action => 'update', :id => '1'}, @routes.recognize_path('/people/1', :method => :patch))
assert_equal({:controller => 'people', :action => 'destroy', :id => '1'}, @routes.recognize_path('/people/1', :method => :delete))
assert_equal({:controller => 'people', :action => 'edit', :id => '1'}, @routes.recognize_path('/people/1/edit', :method => :get))
assert_equal({:controller => 'people', :action => 'edit', :id => '1', :format => 'xml'}, @routes.recognize_path('/people/1/edit.xml', :method => :get))
View
8 actionpack/test/dispatch/request_test.rb
@@ -308,14 +308,14 @@ def url_for(options = {})
end
test "String request methods" do
- [:get, :post, :put, :delete].each do |method|
+ [:get, :post, :patch, :put, :delete].each do |method|
request = stub_request 'REQUEST_METHOD' => method.to_s.upcase
assert_equal method.to_s.upcase, request.method
end
end
test "Symbol forms of request methods via method_symbol" do
- [:get, :post, :put, :delete].each do |method|
+ [:get, :post, :patch, :put, :delete].each do |method|
request = stub_request 'REQUEST_METHOD' => method.to_s.upcase
assert_equal method, request.method_symbol
end
@@ -329,7 +329,7 @@ def url_for(options = {})
end
test "allow method hacking on post" do
- %w(GET OPTIONS PUT POST DELETE).each do |method|
+ %w(GET OPTIONS PATCH PUT POST DELETE).each do |method|
request = stub_request "REQUEST_METHOD" => method.to_s.upcase
assert_equal(method == "HEAD" ? "GET" : method, request.method)
end
@@ -343,7 +343,7 @@ def url_for(options = {})
end
test "restrict method hacking" do
- [:get, :put, :delete].each do |method|
+ [:get, :patch, :put, :delete].each do |method|
request = stub_request 'REQUEST_METHOD' => method.to_s.upcase,
'action_dispatch.request.request_parameters' => { :_method => 'put' }
assert_equal method.to_s.upcase, request.method
View
1  actionpack/test/dispatch/routing_assertions_test.rb
@@ -45,6 +45,7 @@ def test_assert_recognizes_with_extras
def test_assert_recognizes_with_method
assert_recognizes({ :controller => 'articles', :action => 'create' }, { :path => '/articles', :method => :post })
+ assert_recognizes({ :controller => 'articles', :action => 'update', :id => '1' }, { :path => '/articles/1', :method => :patch })
assert_recognizes({ :controller => 'articles', :action => 'update', :id => '1' }, { :path => '/articles/1', :method => :put })
end
View
9 actionpack/test/template/form_helper_test.rb
@@ -1941,6 +1941,15 @@ def test_form_for_with_existing_object_and_custom_url
assert_equal expected, output_buffer
end
+ def test_form_for_with_default_method_as_patch
+ ActionView::Base.default_method_for_update = :patch
+ form_for(@post) {}
+ expected = whole_form("/posts/123", "edit_post_123", "edit_post", "patch")
+ assert_dom_equal expected, output_buffer
+ ensure
+ ActionView::Base.default_method_for_update = :put
+ end
+
def test_fields_for_returns_block_result
output = fields_for(Post.new) { |f| "fields" }
assert_equal "fields", output
View
6 actionpack/test/template/form_tag_helper_test.rb
@@ -76,6 +76,12 @@ def test_form_tag_multipart
assert_dom_equal expected, actual
end
+ def test_form_tag_with_method_patch
+ actual = form_tag({}, { :method => :patch })
+ expected = whole_form("http://www.example.com", :method => :patch)
+ assert_dom_equal expected, actual
+ end
+
def test_form_tag_with_method_put
actual = form_tag({}, { :method => :put })
expected = whole_form("http://www.example.com", :method => :put)
View
6 activemodel/lib/active_model/lint.rb
@@ -56,9 +56,9 @@ def test_valid?
#
# Returns a boolean that specifies whether the object has been persisted yet.
# This is used when calculating the URL for an object. If the object is
- # not persisted, a form for that object, for instance, will be POSTed to the
- # collection. If it is persisted, a form for the object will be PUT to the
- # URL for the object.
+ # not persisted, a form for that object, for instance, will route to the
+ # create action. If it is persisted, a form for the object will routes to
+ # the update action.
def test_persisted?
assert model.respond_to?(:persisted?), "The model should respond to persisted?"
assert_boolean model.persisted?, "persisted?"
View
7 activeresource/lib/active_resource/connection.rb
@@ -15,6 +15,7 @@ class Connection
HTTP_FORMAT_HEADER_NAMES = { :get => 'Accept',
:put => 'Content-Type',
:post => 'Content-Type',
+ :patch => 'Content-Type',
:delete => 'Accept',
:head => 'Accept'
}
@@ -86,6 +87,12 @@ def delete(path, headers = {})
with_auth { request(:delete, path, build_request_headers(headers, :delete, self.site.merge(path))) }
end
+ # Executes a PATCH request (see HTTP protocol documentation if unfamiliar).
+ # Used to update resources.
+ def patch(path, body = '', headers = {})
+ with_auth { request(:patch, path, body.to_s, build_request_headers(headers, :patch, self.site.merge(path))) }
+ end
+
# Executes a PUT request (see HTTP protocol documentation if unfamiliar).
# Used to update resources.
def put(path, body = '', headers = {})
View
10 activeresource/lib/active_resource/custom_methods.rb
@@ -13,6 +13,7 @@ module ActiveResource
#
# POST /people/new/register.xml # PeopleController.register
# PUT /people/1/promote.xml # PeopleController.promote with :id => 1
+ # PATCH /people/1/promote.xml # PeopleController.promote with :id => 1
# DELETE /people/1/deactivate.xml # PeopleController.deactivate with :id => 1
# GET /people/active.xml # PeopleController.active
#
@@ -27,6 +28,7 @@ module ActiveResource
# # => { :id => 1, :name => 'Ryan' }
#
# Person.find(1).put(:promote, :position => 'Manager') # PUT /people/1/promote.xml
+ # Person.find(1).patch(:promote, :position => 'Manager') # PATCH /people/1/promote.xml
# Person.find(1).delete(:deactivate) # DELETE /people/1/deactivate.xml
#
# Person.get(:active) # GET /people/active.xml
@@ -61,6 +63,10 @@ def post(custom_method_name, options = {}, body = '')
connection.post(custom_method_collection_url(custom_method_name, options), body, headers)
end
+ def patch(custom_method_name, options = {}, body = '')
+ connection.patch(custom_method_collection_url(custom_method_name, options), body, headers)
+ end
+
def put(custom_method_name, options = {}, body = '')
connection.put(custom_method_collection_url(custom_method_name, options), body, headers)
end
@@ -97,6 +103,10 @@ def post(method_name, options = {}, body = nil)
end
end
+ def patch(method_name, options = {}, body = '')
+ connection.patch(custom_method_element_url(method_name, options), body, self.class.headers)
+ end
+
def put(method_name, options = {}, body = '')
connection.put(custom_method_element_url(method_name, options), body, self.class.headers)
end
View
8 activeresource/lib/active_resource/http_mock.rb
@@ -15,7 +15,7 @@ class InvalidRequestError < StandardError; end #:nodoc:
#
# mock.http_method(path, request_headers = {}, body = nil, status = 200, response_headers = {})
#
- # * <tt>http_method</tt> - The HTTP method to listen for. This can be +get+, +post+, +put+, +delete+ or
+ # * <tt>http_method</tt> - The HTTP method to listen for. This can be +get+, +post+, +patch+, +put+, +delete+ or
# +head+.
# * <tt>path</tt> - A string, starting with a "/", defining the URI that is expected to be
# called.
@@ -39,6 +39,7 @@ class InvalidRequestError < StandardError; end #:nodoc:
# ActiveResource::HttpMock.respond_to do |mock|
# mock.post "/people.xml", {}, @matz, 201, "Location" => "/people/1.xml"
# mock.get "/people/1.xml", {}, @matz
+ # mock.patch "/people/1.xml", {}, nil, 204
# mock.put "/people/1.xml", {}, nil, 204
# mock.delete "/people/1.xml", {}, nil, 200
# end
@@ -55,7 +56,7 @@ def initialize(responses)
@responses = responses
end
- for method in [ :post, :put, :get, :delete, :head ]
+ for method in [ :post, :patch, :put, :get, :delete, :head ]
# def post(path, request_headers = {}, body = nil, status = 200, response_headers = {})
# @responses[Request.new(:post, path, nil, request_headers)] = Response.new(body || "", status, response_headers)
# end
@@ -121,6 +122,7 @@ def responses
# ActiveResource::HttpMock.respond_to do |mock|
# mock.post "/people.xml", {}, @matz, 201, "Location" => "/people/1.xml"
# mock.get "/people/1.xml", {}, @matz
+ # mock.patch "/people/1.xml", {}, nil, 204
# mock.put "/people/1.xml", {}, nil, 204
# mock.delete "/people/1.xml", {}, nil, 200
# end
@@ -217,7 +219,7 @@ def reset!
end
# body? methods
- { true => %w(post put),
+ { true => %w(post patch put),
false => %w(get delete head) }.each do |has_body, methods|
methods.each do |method|
# def post(path, body, headers)
View
12 activeresource/test/cases/format_test.rb
@@ -11,11 +11,15 @@ def setup
end
def test_http_format_header_name
- header_name = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[:get]
- assert_equal 'Accept', header_name
+ [:get, :head].each do |verb|
+ header_name = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[verb]
+ assert_equal 'Accept', header_name
+ end
- headers_names = [ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[:put], ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[:post]]
- headers_names.each{ |name| assert_equal 'Content-Type', name }
+ [:patch, :put, :post].each do |verb|
+ header_name = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[verb]
+ assert_equal 'Content-Type', header_name
+ end
end
def test_formats_on_single_element
View
4 activeresource/test/cases/http_mock_test.rb
@@ -8,7 +8,7 @@ class HttpMockTest < ActiveSupport::TestCase
FORMAT_HEADER = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES
- [:post, :put, :get, :delete, :head].each do |method|
+ [:post, :patch, :put, :get, :delete, :head].each do |method|
test "responds to simple #{method} request" do
ActiveResource::HttpMock.respond_to do |mock|
mock.send(method, "/people/1", {FORMAT_HEADER[method] => "application/xml"}, "Response")
@@ -193,7 +193,7 @@ class HttpMockTest < ActiveSupport::TestCase
end
def request(method, path, headers = {}, body = nil)
- if method.in?([:put, :post])
+ if method.in?([:patch, :put, :post])
@http.send(method, path, body, headers)
else
@http.send(method, path, headers)
View
2  railties/guides/source/action_controller_overview.textile
@@ -535,7 +535,7 @@ The request object contains a lot of useful information about the request coming
|domain(n=2)|The hostname's first +n+ segments, starting from the right (the TLD).|
|format|The content type requested by the client.|
|method|The HTTP method used for the request.|
-|get?, post?, put?, delete?, head?|Returns true if the HTTP method is GET/POST/PUT/DELETE/HEAD.|
+|get?, post?, patch?, put?, delete?, head?|Returns true if the HTTP method is GET/POST/PATCH/PUT/DELETE/HEAD.|
|headers|Returns a hash containing the headers associated with the request.|
|port|The port number (integer) used for the request.|
|protocol|Returns a string containing the protocol used plus "://", for example "http://".|
View
5 railties/guides/source/ajax_on_rails.textile
@@ -87,7 +87,8 @@ link_to_remote "Add new item",
:position => :bottom
</ruby>
-** *:method* Most typically you want to use a POST request when adding a remote link to your view so this is the default behavior. However, sometimes you'll want to update (PUT) or delete/destroy (DELETE) something and you can specify this with the +:method+ option. Let's see an example for a typical AJAX link for deleting an item from a list:
+** *:method* Most typically you want to use a POST request when adding a remote
+link to your view so this is the default behavior. However, sometimes you'll want to update (PATCH) or delete/destroy (DELETE) something and you can specify this with the +:method+ option. Let's see an example for a typical AJAX link for deleting an item from a list:
<ruby>
link_to_remote "Delete the item",
@@ -108,7 +109,7 @@ Note that if we wouldn't override the default behavior (POST), the above snippet
<ruby>
link_to_remote "Update record",
:url => record_url(record),
- :method => :put,
+ :method => :patch,
:with => "'status=' + 'encodeURIComponent($('status').value) + '&completed=' + $('completed')"
</ruby>
View
7 railties/guides/source/configuring.textile
@@ -165,7 +165,7 @@ Every Rails application comes with a standard set of middleware which it uses in
* +ActionDispatch::Session::CookieStore+ is responsible for storing the session in cookies. An alternate middleware can be used for this by changing the +config.action_controller.session_store+ to an alternate value. Additionally, options passed to this can be configured by using +config.action_controller.session_options+.
* +ActionDispatch::Flash+ sets up the +flash+ keys. Only available if +config.action_controller.session_store+ is set to a value.
* +ActionDispatch::ParamsParser+ parses out parameters from the request into +params+
-* +Rack::MethodOverride+ allows the method to be overridden if +params[:_method]+ is set. This is the middleware which supports the PUT and DELETE HTTP method types.
+* +Rack::MethodOverride+ allows the method to be overridden if +params[:_method]+ is set. This is the middleware which supports the PATCH, PUT, and DELETE HTTP method types.
* +ActionDispatch::Head+ converts HEAD requests to GET requests and serves them as so.
* +ActionDispatch::BestStandardsSupport+ enables "best standards support" so that IE8 renders some elements correctly.
@@ -291,7 +291,10 @@ h4. Configuring Action Dispatch
h4. Configuring Action View
-There are only a few configuration options for Action View, starting with four on +ActionView::Base+:
+There are only a few configuration options for Action View, starting with six on +ActionView::Base+:
+
+
+* +config.action_view.default_method_for_update+ tells Rails which method to use for update forms by default. Set this to +:patch+ for proper HTTP update semantics. The default is +:put+ for backwards compatibility.
* +config.action_view.field_error_proc+ provides an HTML generator for displaying errors that come from Active Record. The default is <tt>Proc.new{ |html_tag, instance| %Q(%&lt;div class=&quot;field_with_errors&quot;&gt;#{html_tag}&lt;/div&gt;).html_safe }</tt>
View
15 railties/guides/source/form_helpers.textile
@@ -312,6 +312,11 @@ Rails will also automatically set the +class+ and +id+ of the form appropriately
WARNING: When you're using STI (single-table inheritance) with your models, you can't rely on record identification on a subclass if only their parent class is declared a resource. You will have to specify the model name, +:url+, and +:method+ explicitly.
+NOTE: Rails can use the +PATCH+ method instead of +PUT+ for update forms if you set the following option in your +config/application.rb+:
+<ruby>
+config.action_view.default_method_for_update = :update
@josevalim Owner

Typo: should be = :patch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+</ruby>
+
h5. Dealing with Namespaces
If you have created namespaced routes, +form_for+ has a nifty shorthand for that too. If your application has an admin namespace then
@@ -329,14 +334,14 @@ form_for [:admin, :management, @article]
For more information on Rails' routing system and the associated conventions, please see the "routing guide":routing.html.
-h4. How do forms with PUT or DELETE methods work?
+h4. How do forms with PATCH, PUT, or DELETE methods work?
-The Rails framework encourages RESTful design of your applications, which means you'll be making a lot of "PUT" and "DELETE" requests (besides "GET" and "POST"). However, most browsers _don't support_ methods other than "GET" and "POST" when it comes to submitting forms.
+The Rails framework encourages RESTful design of your applications, which means you'll be making a lot of "PATCH" and "DELETE" requests (besides "GET" and "POST"). However, most browsers _don't support_ methods other than "GET" and "POST" when it comes to submitting forms.
Rails works around this issue by emulating other methods over POST with a hidden input named +"_method"+, which is set to reflect the desired method:
<ruby>
-form_tag(search_path, :method => "put")
+form_tag(search_path, :method => "patch")
</ruby>
output:
@@ -344,14 +349,14 @@ output:
<html>
<form accept-charset="UTF-8" action="/search" method="post">
<div style="margin:0;padding:0">
- <input name="_method" type="hidden" value="put" />
+ <input name="_method" type="hidden" value="patch" />
<input name="utf8" type="hidden" value="&#x2713;" />
<input name="authenticity_token" type="hidden" value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71" />
</div>
...
</html>
-When parsing POSTed data, Rails will take into account the special +_method+ parameter and acts as if the HTTP method was the one specified inside it ("PUT" in this example).
+When parsing POSTed data, Rails will take into account the special +_method+ parameter and acts as if the HTTP method was the one specified inside it ("PATCH" in this example).
h3. Making Select Boxes with Ease