From 002713c64568114f3754799acc0723ea0d442f7a Mon Sep 17 00:00:00 2001
From: David Lee <davidomundo@gmail.com>
Date: Fri, 6 May 2011 14:03:55 -0700
Subject: Add config.default_method_for_update to support PATCH

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 config.default_method_for_update you can set to :patch
* optionally use PATCH instead of PUT in resource routes and forms
* adds the #patch verb to routes to detect PATCH requests
* adds #patch? to Request
* changes documentation and comments to indicate support for PATCH

This change maintains complete backwards compatibility by keeping :put
as the default for config.default_method_for_update.
---
 .../action_controller/metal/http_authentication.rb |   4 +-
 .../lib/action_controller/metal/responder.rb       |  13 ++-
 actionpack/lib/action_controller/test_case.rb      |   7 +-
 actionpack/lib/action_dispatch/http/request.rb     |   6 +
 actionpack/lib/action_dispatch/railtie.rb          |   1 +
 actionpack/lib/action_dispatch/routing.rb          |  15 ++-
 actionpack/lib/action_dispatch/routing/mapper.rb   | 125 ++++++++++++---------
 .../lib/action_dispatch/testing/integration.rb     |  26 +++--
 actionpack/lib/action_view/base.rb                 |   2 +
 actionpack/lib/action_view/helpers/form_helper.rb  |   4 +-
 actionpack/lib/action_view/helpers/url_helper.rb   |   8 +-
 actionpack/lib/action_view/railtie.rb              |   1 +
 actionpack/test/controller/caching_test.rb         |   2 +-
 actionpack/test/controller/integration_test.rb     |  24 +++-
 actionpack/test/controller/mime_responds_test.rb   |  35 ++++++
 .../controller/request_forgery_protection_test.rb  |  15 ++-
 actionpack/test/controller/resources_test.rb       |  25 +++--
 actionpack/test/controller/routing_test.rb         |  15 ++-
 actionpack/test/dispatch/request_test.rb           |  15 ++-
 actionpack/test/template/form_helper_test.rb       |   9 ++
 actionpack/test/template/form_tag_helper_test.rb   |   6 +
 21 files changed, 259 insertions(+), 99 deletions(-)

(limited to 'actionpack')

diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 3d46163b74..b8b43ea9ef 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -279,7 +279,7 @@ module ActionController
       #
       # 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
@@ -293,7 +293,7 @@ module ActionController
       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.
diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb
index daa1ddd65f..ccda01ed44 100644
--- a/actionpack/lib/action_controller/metal/responder.rb
+++ b/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
   #
@@ -116,8 +116,9 @@ module ActionController #:nodoc:
   class Responder
     attr_reader :controller, :request, :format, :resource, :resources, :options
 
-    ACTIONS_FOR_VERBS = {
+    DEFAULT_ACTIONS_FOR_VERBS = {
       :post => :new,
+      :patch => :edit,
       :put => :edit
     }
 
@@ -132,7 +133,7 @@ module ActionController #:nodoc:
     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)
@@ -259,11 +260,11 @@ module ActionController #:nodoc:
       resource.respond_to?(:errors) && !resource.errors.empty?
     end
 
-    # By default, render the <code>:edit</code> action for HTML requests with failure, unless
-    # the verb is POST.
+    # By default, render the <code>:edit</code> action for HTML requests with errors, unless
+    # the verb was POST.
     #
     def default_action
-      @action ||= ACTIONS_FOR_VERBS[request.request_method_symbol]
+      @action ||= DEFAULT_ACTIONS_FOR_VERBS[request.request_method_symbol]
     end
 
     def resource_errors
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 1e226fc336..3509e74d5e 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -225,7 +225,7 @@ module ActionController
   # == 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.
@@ -392,6 +392,11 @@ module ActionController
         process(action, "POST", *args)
       end
 
+      # Executes a request simulating PATCH HTTP method and set/volley the response
+      def patch(action, *args)
+        process(action, "PATCH", *args)
+      end
+
       # Executes a request simulating PUT HTTP method and set/volley the response
       def put(action, *args)
         process(action, "PUT", *args)
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 0a0ebe7fad..de014a9c00 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -97,6 +97,12 @@ module ActionDispatch
       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_symbol == :put</tt>.
     def put?
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index 35f901c575..4135f3c142 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -23,6 +23,7 @@ module ActionDispatch
       ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length
       ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header
       ActionDispatch::Response.default_charset = app.config.action_dispatch.default_charset || app.config.encoding
+      ActionDispatch::Routing::Mapper.default_method_for_update = app.config.default_method_for_update
 
       ActionDispatch::ExceptionWrapper.rescue_responses.merge!(config.action_dispatch.rescue_responses)
       ActionDispatch::ExceptionWrapper.rescue_templates.merge!(config.action_dispatch.rescue_templates)
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index 107fe80d1f..38a0270151 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/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:
   #
@@ -283,6 +286,6 @@ module ActionDispatch
     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:
   end
 end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index cf9c0d7b6a..bb0441c100 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -7,6 +7,8 @@ require 'action_dispatch/routing/redirection'
 module ActionDispatch
   module Routing
     class Mapper
+      cattr_accessor(:default_method_for_update) {:put}
+
       class Constraints #:nodoc:
         def self.new(app, constraints, request = Rack::Request)
           if constraints.any?
@@ -465,7 +467,7 @@ module ActionDispatch
         #
         # Example:
         #
-        # get 'bacon', :to => 'food#bacon'
+        #   get 'bacon', :to => 'food#bacon'
         def get(*args, &block)
           map_method(:get, args, &block)
         end
@@ -475,17 +477,27 @@ module ActionDispatch
         #
         # 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
@@ -495,7 +507,7 @@ module ActionDispatch
         #
         # Example:
         #
-        # delete 'broccoli', :to => 'food#broccoli'
+        #   delete 'broccoli', :to => 'food#broccoli'
         def delete(*args, &block)
           map_method(:delete, args, &block)
         end
@@ -522,13 +534,13 @@ module ActionDispatch
       # This will create a number of routes for each of the posts and comments
       # controller. For <tt>Admin::PostsController</tt>, Rails will create:
       #
-      #   GET	    /admin/posts
-      #   GET	    /admin/posts/new
-      #   POST	  /admin/posts
-      #   GET	    /admin/posts/1
-      #   GET	    /admin/posts/1/edit
-      #   PUT	    /admin/posts/1
-      #   DELETE  /admin/posts/1
+      #   GET       /admin/posts
+      #   GET       /admin/posts/new
+      #   POST      /admin/posts
+      #   GET       /admin/posts/1
+      #   GET       /admin/posts/1/edit
+      #   PUT/PATCH /admin/posts/1
+      #   DELETE    /admin/posts/1
       #
       # If you want to route /posts (without the prefix /admin) to
       # <tt>Admin::PostsController</tt>, you could use
@@ -556,13 +568,13 @@ module ActionDispatch
       # not use scope. In the last case, the following paths map to
       # +PostsController+:
       #
-      #   GET	    /admin/posts
-      #   GET	    /admin/posts/new
-      #   POST	  /admin/posts
-      #   GET	    /admin/posts/1
-      #   GET	    /admin/posts/1/edit
-      #   PUT	    /admin/posts/1
-      #   DELETE  /admin/posts/1
+      #   GET       /admin/posts
+      #   GET       /admin/posts/new
+      #   POST      /admin/posts
+      #   GET       /admin/posts/1
+      #   GET       /admin/posts/1/edit
+      #   PUT/PATCH /admin/posts/1
+      #   DELETE    /admin/posts/1
       module Scoping
         # Scopes a set of routes to the given default options.
         #
@@ -651,13 +663,13 @@ module ActionDispatch
         #
         # This generates the following routes:
         #
-        #       admin_posts GET    /admin/posts(.:format)          admin/posts#index
-        #       admin_posts POST   /admin/posts(.:format)          admin/posts#create
-        #    new_admin_post GET    /admin/posts/new(.:format)      admin/posts#new
-        #   edit_admin_post GET    /admin/posts/:id/edit(.:format) admin/posts#edit
-        #        admin_post GET    /admin/posts/:id(.:format)      admin/posts#show
-        #        admin_post PUT    /admin/posts/:id(.:format)      admin/posts#update
-        #        admin_post DELETE /admin/posts/:id(.:format)      admin/posts#destroy
+        #       admin_posts GET       /admin/posts(.:format)          admin/posts#index
+        #       admin_posts POST      /admin/posts(.:format)          admin/posts#create
+        #    new_admin_post GET       /admin/posts/new(.:format)      admin/posts#new
+        #   edit_admin_post GET       /admin/posts/:id/edit(.:format) admin/posts#edit
+        #        admin_post GET       /admin/posts/:id(.:format)      admin/posts#show
+        #        admin_post PUT/PATCH /admin/posts/:id(.:format)      admin/posts#update
+        #        admin_post DELETE    /admin/posts/:id(.:format)      admin/posts#destroy
         #
         # === Options
         #
@@ -972,12 +984,12 @@ module ActionDispatch
         # the +GeoCoders+ controller (note that the controller is named after
         # the plural):
         #
-        #   GET     /geocoder/new
-        #   POST    /geocoder
-        #   GET     /geocoder
-        #   GET     /geocoder/edit
-        #   PUT     /geocoder
-        #   DELETE  /geocoder
+        #   GET       /geocoder/new
+        #   POST      /geocoder
+        #   GET       /geocoder
+        #   GET       /geocoder/edit
+        #   PUT/PATCH /geocoder
+        #   DELETE    /geocoder
         #
         # === Options
         # Takes same options as +resources+.
@@ -1002,8 +1014,10 @@ module ActionDispatch
             member do
               get    :edit if parent_resource.actions.include?(:edit)
               get    :show if parent_resource.actions.include?(:show)
-              put    :update if parent_resource.actions.include?(:update)
               delete :destroy if parent_resource.actions.include?(:destroy)
+              if parent_resource.actions.include?(:update)
+                send default_method_for_update, :update
+              end
             end
           end
 
@@ -1020,13 +1034,13 @@ module ActionDispatch
         # creates seven 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
-        #   PUT     /photos/:id
-        #   DELETE  /photos/:id
+        #   GET       /photos
+        #   GET       /photos/new
+        #   POST      /photos
+        #   GET       /photos/:id
+        #   GET       /photos/:id/edit
+        #   PUT/PATCH /photos/:id
+        #   DELETE    /photos/:id
         #
         # Resources can also be nested infinitely by using this block syntax:
         #
@@ -1036,13 +1050,13 @@ module ActionDispatch
         #
         # This generates the following comments routes:
         #
-        #   GET     /photos/:photo_id/comments
-        #   GET     /photos/:photo_id/comments/new
-        #   POST    /photos/:photo_id/comments
-        #   GET     /photos/:photo_id/comments/:id
-        #   GET     /photos/:photo_id/comments/:id/edit
-        #   PUT     /photos/:photo_id/comments/:id
-        #   DELETE  /photos/:photo_id/comments/:id
+        #   GET       /photos/:photo_id/comments
+        #   GET       /photos/:photo_id/comments/new
+        #   POST      /photos/:photo_id/comments
+        #   GET       /photos/:photo_id/comments/:id
+        #   GET       /photos/:photo_id/comments/:id/edit
+        #   PUT/PATCH /photos/:photo_id/comments/:id
+        #   DELETE    /photos/:photo_id/comments/:id
         #
         # === Options
         # Takes same options as <tt>Base#match</tt> as well as:
@@ -1104,13 +1118,13 @@ module ActionDispatch
         #
         #   The +comments+ resource here will have the following routes generated for it:
         #
-        #     post_comments    GET    /posts/:post_id/comments(.:format)
-        #     post_comments    POST   /posts/:post_id/comments(.:format)
-        #     new_post_comment GET    /posts/:post_id/comments/new(.:format)
-        #     edit_comment     GET    /sekret/comments/:id/edit(.:format)
-        #     comment          GET    /sekret/comments/:id(.:format)
-        #     comment          PUT    /sekret/comments/:id(.:format)
-        #     comment          DELETE /sekret/comments/:id(.:format)
+        #     post_comments    GET       /posts/:post_id/comments(.:format)
+        #     post_comments    POST      /posts/:post_id/comments(.:format)
+        #     new_post_comment GET       /posts/:post_id/comments/new(.:format)
+        #     edit_comment     GET       /sekret/comments/:id/edit(.:format)
+        #     comment          GET       /sekret/comments/:id(.:format)
+        #     comment          PUT/PATCH /sekret/comments/:id(.:format)
+        #     comment          DELETE    /sekret/comments/:id(.:format)
         #
         # === Examples
         #
@@ -1138,11 +1152,14 @@ module ActionDispatch
               get :new
             end if parent_resource.actions.include?(:new)
 
+            # TODO: Only accept patch or put depending on config
             member do
               get    :edit if parent_resource.actions.include?(:edit)
               get    :show if parent_resource.actions.include?(:show)
-              put    :update if parent_resource.actions.include?(:update)
               delete :destroy if parent_resource.actions.include?(:destroy)
+              if parent_resource.actions.include?(:update)
+                send default_method_for_update, :update
+              end
             end
           end
 
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 0287e7728b..62b3a344f8 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -26,8 +26,8 @@ module ActionDispatch
       # 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
@@ -38,6 +38,12 @@ module ActionDispatch
         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)
@@ -65,10 +71,10 @@ module ActionDispatch
       # 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'
@@ -108,6 +114,12 @@ module ActionDispatch
         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)
@@ -318,7 +330,7 @@ module ActionDispatch
         @integration_session = Integration::Session.new(app)
       end
 
-      %w(get post put head delete options cookies assigns
+      %w(get post put patch head delete options cookies assigns
          xml_http_request xhr get_via_redirect post_via_redirect).each do |method|
         define_method(method) do |*args|
           reset! unless integration_session
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index 23329d7f35..4641f10dc8 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -132,6 +132,8 @@ module ActionView #:nodoc:
   class Base
     include Helpers, ::ERB::Util, Context
 
+    cattr_accessor(:default_method_for_update) {:put}
+
     # Specify the proc used to decorate input tags that refer to attributes with errors.
     cattr_accessor :field_error_proc
     @@field_error_proc = Proc.new{ |html_tag, instance| "<div class=\"field_with_errors\">#{html_tag}</div>".html_safe }
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index cae345a1d6..33d509d968 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -250,7 +250,7 @@ module ActionView
       #
       # 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
@@ -385,7 +385,7 @@ module ActionView
         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 ? "#{action}_#{as}" : dom_class(object, action),
           :id     => as ? "#{action}_#{as}" : [options[:namespace], dom_id(object, action)].compact.join("_").presence,
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index b5fc882e31..93f476926c 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -146,12 +146,12 @@ module ActionView
       #   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 @@ module ActionView
       #
       # 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
@@ -329,7 +329,7 @@ module ActionView
         remote = html_options.delete('remote')
 
         method     = html_options.delete('method').to_s
-        method_tag = %w{put delete}.include?(method) ? method_tag(method) : ""
+        method_tag = %w{put patch delete}.include?(method) ? method_tag(method) : ""
 
         form_method  = method == 'get' ? 'get' : 'post'
         form_options = html_options.delete('form') || {}
diff --git a/actionpack/lib/action_view/railtie.rb b/actionpack/lib/action_view/railtie.rb
index 43371a1c49..b2926006b4 100644
--- a/actionpack/lib/action_view/railtie.rb
+++ b/actionpack/lib/action_view/railtie.rb
@@ -33,6 +33,7 @@ module ActionView
     end
 
     initializer "action_view.set_configs" do |app|
+      ActionView::Base.default_method_for_update = app.config.default_method_for_update
       ActiveSupport.on_load(:action_view) do
         app.config.action_view.each do |k,v|
           send "#{k}=", v
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index 10f73c3da3..a42c68a628 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -180,7 +180,7 @@ class PageCachingTest < ActionController::TestCase
   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 && status == :ok
         define_method "test_shouldnt_cache_#{method}_with_#{status}_status" do
           send(method, status)
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index 64c4682015..44f033119d 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -63,6 +63,12 @@ class SessionTest < ActiveSupport::TestCase
     @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 @@ class SessionTest < ActiveSupport::TestCase
     @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)
@@ -131,6 +143,16 @@ class SessionTest < ActiveSupport::TestCase
     @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(
@@ -228,7 +250,7 @@ class IntegrationTestUsesCorrectClass < ActionDispatch::IntegrationTest
     @integration_session.stubs(:generic_url_rewriter)
     @integration_session.stubs(:process)
 
-    %w( get post head put delete options ).each do |verb|
+    %w( get post head patch put delete options ).each do |verb|
       assert_nothing_raised("'#{verb}' should use integration test methods") { __send__(verb, '/') }
     end
   end
diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb
index 69a8f4f213..ae368842b5 100644
--- a/actionpack/test/controller/mime_responds_test.rb
+++ b/actionpack/test/controller/mime_responds_test.rb
@@ -770,6 +770,41 @@ class RespondWithControllerTest < ActionController::TestCase
     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_put_with_html_redirects_on_success
     with_test_route_set do
       put :using_resource
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index e6d3fa74f2..64ed7f667f 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -114,6 +114,10 @@ module RequestForgeryProtectionTests
     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
@@ -130,6 +134,10 @@ module RequestForgeryProtectionTests
     assert_not_blocked { post :index, :custom_authenticity_token => @token }
   end
 
+  def test_should_allow_patch_with_token
+    assert_not_blocked { patch :index, :custom_authenticity_token => @token }
+  end
+
   def test_should_allow_put_with_token
     assert_not_blocked { put :index, :custom_authenticity_token => @token }
   end
@@ -148,6 +156,11 @@ module RequestForgeryProtectionTests
     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 }
@@ -232,7 +245,7 @@ class FreeCookieControllerTest < ActionController::TestCase
   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
diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb
index 73d72fe4d6..3c0a5d36ca 100644
--- a/actionpack/test/controller/resources_test.rb
+++ b/actionpack/test/controller/resources_test.rb
@@ -158,7 +158,7 @@ class ResourcesTest < ActionController::TestCase
   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
@@ -167,6 +167,7 @@ class ResourcesTest < ActionController::TestCase
           put    :b, :on => :collection
           post   :c, :on => :collection
           delete :d, :on => :collection
+          patch  :e, :on => :collection
         end
       end
 
@@ -185,7 +186,7 @@ class ResourcesTest < ActionController::TestCase
   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
@@ -195,6 +196,7 @@ class ResourcesTest < ActionController::TestCase
             put    :b, :on => :collection
             post   :c, :on => :collection
             delete :d, :on => :collection
+            patch  :e, :on => :collection
           end
         end
       end
@@ -241,7 +243,7 @@ class ResourcesTest < ActionController::TestCase
   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
@@ -251,6 +253,7 @@ class ResourcesTest < ActionController::TestCase
             put    :b, :on => :collection
             post   :c, :on => :collection
             delete :d, :on => :collection
+            patch  :e, :on => :collection
           end
         end
       end
@@ -270,7 +273,7 @@ class ResourcesTest < ActionController::TestCase
   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"
@@ -294,7 +297,7 @@ class ResourcesTest < ActionController::TestCase
   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"
@@ -311,7 +314,7 @@ class ResourcesTest < ActionController::TestCase
   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
@@ -564,7 +567,7 @@ class ResourcesTest < ActionController::TestCase
   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
@@ -586,7 +589,7 @@ class ResourcesTest < ActionController::TestCase
   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
@@ -651,12 +654,16 @@ class ResourcesTest < ActionController::TestCase
     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
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index ee9374cc91..807905c7b5 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -648,11 +648,12 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
       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)
@@ -1035,6 +1036,7 @@ class RouteSetTest < ActiveSupport::TestCase
       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
 
@@ -1047,6 +1049,9 @@ class RouteSetTest < ActiveSupport::TestCase
     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)
     }
@@ -1059,6 +1064,10 @@ class RouteSetTest < ActiveSupport::TestCase
     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])
@@ -1112,6 +1121,7 @@ class RouteSetTest < ActiveSupport::TestCase
     set.draw do
       get "people/:id" => "people#show", :as => "person"
       put "people/:id" => "people#update"
+      patch "people/:id" => "people#update"
       get "people/:id(.:format)" => "people#show"
     end
 
@@ -1122,6 +1132,9 @@ class RouteSetTest < ActiveSupport::TestCase
     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])
+
     params = set.recognize_path("/people/5.png", :method => :get)
     assert_equal("show", params[:action])
     assert_equal("5", params[:id])
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index 8f0ac5310e..6c8b22c47f 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -325,14 +325,14 @@ class RequestTest < ActiveSupport::TestCase
   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
@@ -346,7 +346,7 @@ class RequestTest < ActiveSupport::TestCase
   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
@@ -360,7 +360,7 @@ class RequestTest < ActiveSupport::TestCase
   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
@@ -375,6 +375,13 @@ class RequestTest < ActiveSupport::TestCase
     assert request.head?
   end
 
+  test "post masquerading as patch" do
+    request = stub_request 'REQUEST_METHOD' => 'PATCH', "rack.methodoverride.original_method" => "POST"
+    assert_equal "POST", request.method
+    assert_equal "PATCH",  request.request_method
+    assert request.patch?
+  end
+
   test "post masquerading as put" do
     request = stub_request 'REQUEST_METHOD' => 'PUT', "rack.methodoverride.original_method" => "POST"
     assert_equal "POST", request.method
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index d072d3bce0..e4cb7e02a0 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -2195,6 +2195,15 @@ class FormHelperTest < ActionView::TestCase
     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
diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb
index 809102e5c2..6ef4cf4dd2 100644
--- a/actionpack/test/template/form_tag_helper_test.rb
+++ b/actionpack/test/template/form_tag_helper_test.rb
@@ -78,6 +78,12 @@ class FormTagHelperTest < ActionView::TestCase
     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)
-- 
cgit v1.2.3