Skip to content
This repository
Browse code

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.
  • Loading branch information...
commit 002713c64568114f3754799acc0723ea0d442f7a 1 parent 66b7eb1
dlee authored May 06, 2011

Showing 40 changed files with 402 additions and 151 deletions. Show diff stats Hide diff stats

  1. 1  Gemfile
  2. 4  actionpack/lib/action_controller/metal/http_authentication.rb
  3. 13  actionpack/lib/action_controller/metal/responder.rb
  4. 7  actionpack/lib/action_controller/test_case.rb
  5. 6  actionpack/lib/action_dispatch/http/request.rb
  6. 1  actionpack/lib/action_dispatch/railtie.rb
  7. 15  actionpack/lib/action_dispatch/routing.rb
  8. 125  actionpack/lib/action_dispatch/routing/mapper.rb
  9. 26  actionpack/lib/action_dispatch/testing/integration.rb
  10. 2  actionpack/lib/action_view/base.rb
  11. 4  actionpack/lib/action_view/helpers/form_helper.rb
  12. 8  actionpack/lib/action_view/helpers/url_helper.rb
  13. 1  actionpack/lib/action_view/railtie.rb
  14. 2  actionpack/test/controller/caching_test.rb
  15. 24  actionpack/test/controller/integration_test.rb
  16. 35  actionpack/test/controller/mime_responds_test.rb
  17. 15  actionpack/test/controller/request_forgery_protection_test.rb
  18. 25  actionpack/test/controller/resources_test.rb
  19. 15  actionpack/test/controller/routing_test.rb
  20. 15  actionpack/test/dispatch/request_test.rb
  21. 9  actionpack/test/template/form_helper_test.rb
  22. 6  actionpack/test/template/form_tag_helper_test.rb
  23. 6  activemodel/lib/active_model/lint.rb
  24. 7  activeresource/lib/active_resource/connection.rb
  25. 16  activeresource/lib/active_resource/custom_methods.rb
  26. 6  activeresource/lib/active_resource/http_mock.rb
  27. 12  activeresource/test/cases/format_test.rb
  28. 4  activeresource/test/cases/http_mock_test.rb
  29. 2  railties/guides/source/action_controller_overview.textile
  30. 3  railties/guides/source/ajax_on_rails.textile
  31. 7  railties/guides/source/configuring.textile
  32. 15  railties/guides/source/form_helpers.textile
  33. 42  railties/guides/source/routing.textile
  34. 6  railties/guides/source/testing.textile
  35. 3  railties/lib/rails/application/configuration.rb
  36. 6  railties/lib/rails/generators/active_model.rb
  37. 3  railties/lib/rails/generators/rails/app/templates/config/application.rb
  38. 4  railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
  39. 47  railties/test/application/configuration_test.rb
  40. 5  railties/test/generators/app_generator_test.rb
1  Gemfile
@@ -8,6 +8,7 @@ else
8 8
   gem 'arel'
9 9
 end
10 10
 
  11
+gem 'rack-test', :git => "https://github.com/brynary/rack-test.git"
11 12
 gem 'bcrypt-ruby', '~> 3.0.0'
12 13
 gem 'jquery-rails'
13 14
 
4  actionpack/lib/action_controller/metal/http_authentication.rb
@@ -279,7 +279,7 @@ def secret_token(request)
279 279
       #
280 280
       # An implementation might choose not to accept a previously used nonce or a previously used digest, in order to
281 281
       # protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for
282  
-      # POST or PUT requests and a time-stamp for GET requests. For more details on the issues involved see Section 4
  282
+      # POST, PUT, or PATCH requests and a time-stamp for GET requests. For more details on the issues involved see Section 4
283 283
       # of this document.
284 284
       #
285 285
       # The nonce is opaque to the client. Composed of Time, and hash of Time with secret
@@ -293,7 +293,7 @@ def nonce(secret_key, time = Time.now)
293 293
       end
294 294
 
295 295
       # Might want a shorter timeout depending on whether the request
296  
-      # is a PUT or POST, and if client is browser or web service.
  296
+      # is a PATCH, PUT, or POST, and if client is browser or web service.
297 297
       # Can be much shorter if the Stale directive is implemented. This would
298 298
       # allow a user to use new nonce without prompting user again for their
299 299
       # username and password.
13  actionpack/lib/action_controller/metal/responder.rb
@@ -53,7 +53,7 @@ module ActionController #:nodoc:
53 53
   #     end
54 54
   #   end
55 55
   #
56  
-  # The same happens for PUT and DELETE requests.
  56
+  # The same happens for PATCH/PUT and DELETE requests.
57 57
   #
58 58
   # === Nested resources
59 59
   #
@@ -116,8 +116,9 @@ module ActionController #:nodoc:
116 116
   class Responder
117 117
     attr_reader :controller, :request, :format, :resource, :resources, :options
118 118
 
119  
-    ACTIONS_FOR_VERBS = {
  119
+    DEFAULT_ACTIONS_FOR_VERBS = {
120 120
       :post => :new,
  121
+      :patch => :edit,
121 122
       :put => :edit
122 123
     }
123 124
 
@@ -132,7 +133,7 @@ def initialize(controller, resources, options={})
132 133
     end
133 134
 
134 135
     delegate :head, :render, :redirect_to,   :to => :controller
135  
-    delegate :get?, :post?, :put?, :delete?, :to => :request
  136
+    delegate :get?, :post?, :patch?, :put?, :delete?, :to => :request
136 137
 
137 138
     # Undefine :to_json and :to_yaml since it's defined on Object
138 139
     undef_method(:to_json) if method_defined?(:to_json)
@@ -259,11 +260,11 @@ def has_errors?
259 260
       resource.respond_to?(:errors) && !resource.errors.empty?
260 261
     end
261 262
 
262  
-    # By default, render the <code>:edit</code> action for HTML requests with failure, unless
263  
-    # the verb is POST.
  263
+    # By default, render the <code>:edit</code> action for HTML requests with errors, unless
  264
+    # the verb was POST.
264 265
     #
265 266
     def default_action
266  
-      @action ||= ACTIONS_FOR_VERBS[request.request_method_symbol]
  267
+      @action ||= DEFAULT_ACTIONS_FOR_VERBS[request.request_method_symbol]
267 268
     end
268 269
 
269 270
     def resource_errors
7  actionpack/lib/action_controller/test_case.rb
@@ -225,7 +225,7 @@ def exists?
225 225
   # == Basic example
226 226
   #
227 227
   # Functional tests are written as follows:
228  
-  # 1. First, one uses the +get+, +post+, +put+, +delete+ or +head+ method to simulate
  228
+  # 1. First, one uses the +get+, +post+, +patch+, +put+, +delete+ or +head+ method to simulate
229 229
   #    an HTTP request.
230 230
   # 2. Then, one asserts whether the current state is as expected. "State" can be anything:
231 231
   #    the controller's HTTP response, the database contents, etc.
@@ -392,6 +392,11 @@ def post(action, *args)
392 392
         process(action, "POST", *args)
393 393
       end
394 394
 
  395
+      # Executes a request simulating PATCH HTTP method and set/volley the response
  396
+      def patch(action, *args)
  397
+        process(action, "PATCH", *args)
  398
+      end
  399
+
395 400
       # Executes a request simulating PUT HTTP method and set/volley the response
396 401
       def put(action, *args)
397 402
         process(action, "PUT", *args)
6  actionpack/lib/action_dispatch/http/request.rb
@@ -97,6 +97,12 @@ def post?
97 97
       HTTP_METHOD_LOOKUP[request_method] == :post
98 98
     end
99 99
 
  100
+    # Is this a PATCH request?
  101
+    # Equivalent to <tt>request.request_method == :patch</tt>.
  102
+    def patch?
  103
+      HTTP_METHOD_LOOKUP[request_method] == :patch
  104
+    end
  105
+
100 106
     # Is this a PUT request?
101 107
     # Equivalent to <tt>request.request_method_symbol == :put</tt>.
102 108
     def put?
1  actionpack/lib/action_dispatch/railtie.rb
@@ -23,6 +23,7 @@ class Railtie < Rails::Railtie
23 23
       ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length
24 24
       ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header
25 25
       ActionDispatch::Response.default_charset = app.config.action_dispatch.default_charset || app.config.encoding
  26
+      ActionDispatch::Routing::Mapper.default_method_for_update = app.config.default_method_for_update
26 27
 
27 28
       ActionDispatch::ExceptionWrapper.rescue_responses.merge!(config.action_dispatch.rescue_responses)
28 29
       ActionDispatch::ExceptionWrapper.rescue_templates.merge!(config.action_dispatch.rescue_templates)
15  actionpack/lib/action_dispatch/routing.rb
@@ -182,10 +182,13 @@ module ActionDispatch
182 182
   #
183 183
   # == HTTP Methods
184 184
   #
185  
-  # Using the <tt>:via</tt> option when specifying a route allows you to restrict it to a specific HTTP method.
186  
-  # Possible values are <tt>:post</tt>, <tt>:get</tt>, <tt>:put</tt>, <tt>:delete</tt> and <tt>:any</tt>.
187  
-  # If your route needs to respond to more than one method you can use an array, e.g. <tt>[ :get, :post ]</tt>.
188  
-  # The default value is <tt>:any</tt> which means that the route will respond to any of the HTTP methods.
  185
+  # Using the <tt>:via</tt> option when specifying a route allows you to
  186
+  # restrict it to a specific HTTP method.  Possible values are <tt>:post</tt>,
  187
+  # <tt>:get</tt>, <tt>:patch</tt>, <tt>:put</tt>, <tt>:delete</tt> and
  188
+  # <tt>:any</tt>.  If your route needs to respond to more than one method you
  189
+  # can use an array, e.g. <tt>[ :get, :post ]</tt>.  The default value is
  190
+  # <tt>:any</tt> which means that the route will respond to any of the HTTP
  191
+  # methods.
189 192
   #
190 193
   # Examples:
191 194
   #
@@ -198,7 +201,7 @@ module ActionDispatch
198 201
   # === HTTP helper methods
199 202
   #
200 203
   # An alternative method of specifying which HTTP method a route should respond to is to use the helper
201  
-  # methods <tt>get</tt>, <tt>post</tt>, <tt>put</tt> and <tt>delete</tt>.
  204
+  # methods <tt>get</tt>, <tt>post</tt>, <tt>patch</tt>, <tt>put</tt> and <tt>delete</tt>.
202 205
   #
203 206
   # Examples:
204 207
   #
@@ -283,6 +286,6 @@ module Routing
283 286
     autoload :PolymorphicRoutes, 'action_dispatch/routing/polymorphic_routes'
284 287
 
285 288
     SEPARATORS = %w( / . ? ) #:nodoc:
286  
-    HTTP_METHODS = [:get, :head, :post, :put, :delete, :options] #:nodoc:
  289
+    HTTP_METHODS = [:get, :head, :post, :patch, :put, :delete, :options] #:nodoc:
287 290
   end
288 291
 end
125  actionpack/lib/action_dispatch/routing/mapper.rb
@@ -7,6 +7,8 @@
7 7
 module ActionDispatch
8 8
   module Routing
9 9
     class Mapper
  10
+      cattr_accessor(:default_method_for_update) {:put}
  11
+
10 12
       class Constraints #:nodoc:
11 13
         def self.new(app, constraints, request = Rack::Request)
12 14
           if constraints.any?
@@ -465,7 +467,7 @@ module HttpHelpers
465 467
         #
466 468
         # Example:
467 469
         #
468  
-        # get 'bacon', :to => 'food#bacon'
  470
+        #   get 'bacon', :to => 'food#bacon'
469 471
         def get(*args, &block)
470 472
           map_method(:get, args, &block)
471 473
         end
@@ -475,17 +477,27 @@ def get(*args, &block)
475 477
         #
476 478
         # Example:
477 479
         #
478  
-        # post 'bacon', :to => 'food#bacon'
  480
+        #   post 'bacon', :to => 'food#bacon'
479 481
         def post(*args, &block)
480 482
           map_method(:post, args, &block)
481 483
         end
482 484
 
  485
+        # Define a route that only recognizes HTTP PATCH.
  486
+        # For supported arguments, see <tt>Base#match</tt>.
  487
+        #
  488
+        # Example:
  489
+        #
  490
+        #   patch 'bacon', :to => 'food#bacon'
  491
+        def patch(*args, &block)
  492
+          map_method(:patch, args, &block)
  493
+        end
  494
+
483 495
         # Define a route that only recognizes HTTP PUT.
484 496
         # For supported arguments, see <tt>Base#match</tt>.
485 497
         #
486 498
         # Example:
487 499
         #
488  
-        # put 'bacon', :to => 'food#bacon'
  500
+        #   put 'bacon', :to => 'food#bacon'
489 501
         def put(*args, &block)
490 502
           map_method(:put, args, &block)
491 503
         end
@@ -495,7 +507,7 @@ def put(*args, &block)
495 507
         #
496 508
         # Example:
497 509
         #
498  
-        # delete 'broccoli', :to => 'food#broccoli'
  510
+        #   delete 'broccoli', :to => 'food#broccoli'
499 511
         def delete(*args, &block)
500 512
           map_method(:delete, args, &block)
501 513
         end
@@ -522,13 +534,13 @@ def map_method(method, args, &block)
522 534
       # This will create a number of routes for each of the posts and comments
523 535
       # controller. For <tt>Admin::PostsController</tt>, Rails will create:
524 536
       #
525  
-      #   GET	    /admin/posts
526  
-      #   GET	    /admin/posts/new
527  
-      #   POST	  /admin/posts
528  
-      #   GET	    /admin/posts/1
529  
-      #   GET	    /admin/posts/1/edit
530  
-      #   PUT	    /admin/posts/1
531  
-      #   DELETE  /admin/posts/1
  537
+      #   GET       /admin/posts
  538
+      #   GET       /admin/posts/new
  539
+      #   POST      /admin/posts
  540
+      #   GET       /admin/posts/1
  541
+      #   GET       /admin/posts/1/edit
  542
+      #   PUT/PATCH /admin/posts/1
  543
+      #   DELETE    /admin/posts/1
532 544
       #
533 545
       # If you want to route /posts (without the prefix /admin) to
534 546
       # <tt>Admin::PostsController</tt>, you could use
@@ -556,13 +568,13 @@ def map_method(method, args, &block)
556 568
       # not use scope. In the last case, the following paths map to
557 569
       # +PostsController+:
558 570
       #
559  
-      #   GET	    /admin/posts
560  
-      #   GET	    /admin/posts/new
561  
-      #   POST	  /admin/posts
562  
-      #   GET	    /admin/posts/1
563  
-      #   GET	    /admin/posts/1/edit
564  
-      #   PUT	    /admin/posts/1
565  
-      #   DELETE  /admin/posts/1
  571
+      #   GET       /admin/posts
  572
+      #   GET       /admin/posts/new
  573
+      #   POST      /admin/posts
  574
+      #   GET       /admin/posts/1
  575
+      #   GET       /admin/posts/1/edit
  576
+      #   PUT/PATCH /admin/posts/1
  577
+      #   DELETE    /admin/posts/1
566 578
       module Scoping
567 579
         # Scopes a set of routes to the given default options.
568 580
         #
@@ -651,13 +663,13 @@ def controller(controller, options={})
651 663
         #
652 664
         # This generates the following routes:
653 665
         #
654  
-        #       admin_posts GET    /admin/posts(.:format)          admin/posts#index
655  
-        #       admin_posts POST   /admin/posts(.:format)          admin/posts#create
656  
-        #    new_admin_post GET    /admin/posts/new(.:format)      admin/posts#new
657  
-        #   edit_admin_post GET    /admin/posts/:id/edit(.:format) admin/posts#edit
658  
-        #        admin_post GET    /admin/posts/:id(.:format)      admin/posts#show
659  
-        #        admin_post PUT    /admin/posts/:id(.:format)      admin/posts#update
660  
-        #        admin_post DELETE /admin/posts/:id(.:format)      admin/posts#destroy
  666
+        #       admin_posts GET       /admin/posts(.:format)          admin/posts#index
  667
+        #       admin_posts POST      /admin/posts(.:format)          admin/posts#create
  668
+        #    new_admin_post GET       /admin/posts/new(.:format)      admin/posts#new
  669
+        #   edit_admin_post GET       /admin/posts/:id/edit(.:format) admin/posts#edit
  670
+        #        admin_post GET       /admin/posts/:id(.:format)      admin/posts#show
  671
+        #        admin_post PUT/PATCH /admin/posts/:id(.:format)      admin/posts#update
  672
+        #        admin_post DELETE    /admin/posts/:id(.:format)      admin/posts#destroy
661 673
         #
662 674
         # === Options
663 675
         #
@@ -972,12 +984,12 @@ def resources_path_names(options)
972 984
         # the +GeoCoders+ controller (note that the controller is named after
973 985
         # the plural):
974 986
         #
975  
-        #   GET     /geocoder/new
976  
-        #   POST    /geocoder
977  
-        #   GET     /geocoder
978  
-        #   GET     /geocoder/edit
979  
-        #   PUT     /geocoder
980  
-        #   DELETE  /geocoder
  987
+        #   GET       /geocoder/new
  988
+        #   POST      /geocoder
  989
+        #   GET       /geocoder
  990
+        #   GET       /geocoder/edit
  991
+        #   PUT/PATCH /geocoder
  992
+        #   DELETE    /geocoder
981 993
         #
982 994
         # === Options
983 995
         # Takes same options as +resources+.
@@ -1002,8 +1014,10 @@ def resource(*resources, &block)
1002 1014
             member do
1003 1015
               get    :edit if parent_resource.actions.include?(:edit)
1004 1016
               get    :show if parent_resource.actions.include?(:show)
1005  
-              put    :update if parent_resource.actions.include?(:update)
1006 1017
               delete :destroy if parent_resource.actions.include?(:destroy)
  1018
+              if parent_resource.actions.include?(:update)
  1019
+                send default_method_for_update, :update
  1020
+              end
1007 1021
             end
1008 1022
           end
1009 1023
 
@@ -1020,13 +1034,13 @@ def resource(*resources, &block)
1020 1034
         # creates seven different routes in your application, all mapping to
1021 1035
         # the +Photos+ controller:
1022 1036
         #
1023  
-        #   GET     /photos
1024  
-        #   GET     /photos/new
1025  
-        #   POST    /photos
1026  
-        #   GET     /photos/:id
1027  
-        #   GET     /photos/:id/edit
1028  
-        #   PUT     /photos/:id
1029  
-        #   DELETE  /photos/:id
  1037
+        #   GET       /photos
  1038
+        #   GET       /photos/new
  1039
+        #   POST      /photos
  1040
+        #   GET       /photos/:id
  1041
+        #   GET       /photos/:id/edit
  1042
+        #   PUT/PATCH /photos/:id
  1043
+        #   DELETE    /photos/:id
1030 1044
         #
1031 1045
         # Resources can also be nested infinitely by using this block syntax:
1032 1046
         #
@@ -1036,13 +1050,13 @@ def resource(*resources, &block)
1036 1050
         #
1037 1051
         # This generates the following comments routes:
1038 1052
         #
1039  
-        #   GET     /photos/:photo_id/comments
1040  
-        #   GET     /photos/:photo_id/comments/new
1041  
-        #   POST    /photos/:photo_id/comments
1042  
-        #   GET     /photos/:photo_id/comments/:id
1043  
-        #   GET     /photos/:photo_id/comments/:id/edit
1044  
-        #   PUT     /photos/:photo_id/comments/:id
1045  
-        #   DELETE  /photos/:photo_id/comments/:id
  1053
+        #   GET       /photos/:photo_id/comments
  1054
+        #   GET       /photos/:photo_id/comments/new
  1055
+        #   POST      /photos/:photo_id/comments
  1056
+        #   GET       /photos/:photo_id/comments/:id
  1057
+        #   GET       /photos/:photo_id/comments/:id/edit
  1058
+        #   PUT/PATCH /photos/:photo_id/comments/:id
  1059
+        #   DELETE    /photos/:photo_id/comments/:id
1046 1060
         #
1047 1061
         # === Options
1048 1062
         # Takes same options as <tt>Base#match</tt> as well as:
@@ -1104,13 +1118,13 @@ def resource(*resources, &block)
1104 1118
         #
1105 1119
         #   The +comments+ resource here will have the following routes generated for it:
1106 1120
         #
1107  
-        #     post_comments    GET    /posts/:post_id/comments(.:format)
1108  
-        #     post_comments    POST   /posts/:post_id/comments(.:format)
1109  
-        #     new_post_comment GET    /posts/:post_id/comments/new(.:format)
1110  
-        #     edit_comment     GET    /sekret/comments/:id/edit(.:format)
1111  
-        #     comment          GET    /sekret/comments/:id(.:format)
1112  
-        #     comment          PUT    /sekret/comments/:id(.:format)
1113  
-        #     comment          DELETE /sekret/comments/:id(.:format)
  1121
+        #     post_comments    GET       /posts/:post_id/comments(.:format)
  1122
+        #     post_comments    POST      /posts/:post_id/comments(.:format)
  1123
+        #     new_post_comment GET       /posts/:post_id/comments/new(.:format)
  1124
+        #     edit_comment     GET       /sekret/comments/:id/edit(.:format)
  1125
+        #     comment          GET       /sekret/comments/:id(.:format)
  1126
+        #     comment          PUT/PATCH /sekret/comments/:id(.:format)
  1127
+        #     comment          DELETE    /sekret/comments/:id(.:format)
1114 1128
         #
1115 1129
         # === Examples
1116 1130
         #
@@ -1138,11 +1152,14 @@ def resources(*resources, &block)
1138 1152
               get :new
1139 1153
             end if parent_resource.actions.include?(:new)
1140 1154
 
  1155
+            # TODO: Only accept patch or put depending on config
1141 1156
             member do
1142 1157
               get    :edit if parent_resource.actions.include?(:edit)
1143 1158
               get    :show if parent_resource.actions.include?(:show)
1144  
-              put    :update if parent_resource.actions.include?(:update)
1145 1159
               delete :destroy if parent_resource.actions.include?(:destroy)
  1160
+              if parent_resource.actions.include?(:update)
  1161
+                send default_method_for_update, :update
  1162
+              end
1146 1163
             end
1147 1164
           end
1148 1165
 
26  actionpack/lib/action_dispatch/testing/integration.rb
@@ -26,8 +26,8 @@ module RequestHelpers
26 26
       # object's <tt>@response</tt> instance variable will point to the same
27 27
       # response object.
28 28
       #
29  
-      # You can also perform POST, PUT, DELETE, and HEAD requests with +#post+,
30  
-      # +#put+, +#delete+, and +#head+.
  29
+      # You can also perform POST, PATCH, PUT, DELETE, and HEAD requests with
  30
+      # +#post+, +#patch+, +#put+, +#delete+, and +#head+.
31 31
       def get(path, parameters = nil, headers = nil)
32 32
         process :get, path, parameters, headers
33 33
       end
@@ -38,6 +38,12 @@ def post(path, parameters = nil, headers = nil)
38 38
         process :post, path, parameters, headers
39 39
       end
40 40
 
  41
+      # Performs a PATCH request with the given parameters. See +#get+ for more
  42
+      # details.
  43
+      def patch(path, parameters = nil, headers = nil)
  44
+        process :patch, path, parameters, headers
  45
+      end
  46
+
41 47
       # Performs a PUT request with the given parameters. See +#get+ for more
42 48
       # details.
43 49
       def put(path, parameters = nil, headers = nil)
@@ -65,10 +71,10 @@ def options(path, parameters = nil, headers = nil)
65 71
       # Performs an XMLHttpRequest request with the given parameters, mirroring
66 72
       # a request from the Prototype library.
67 73
       #
68  
-      # The request_method is +:get+, +:post+, +:put+, +:delete+ or +:head+; the
69  
-      # parameters are +nil+, a hash, or a url-encoded or multipart string;
70  
-      # the headers are a hash. Keys are automatically upcased and prefixed
71  
-      # with 'HTTP_' if not already.
  74
+      # The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or
  75
+      # +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart
  76
+      # string; the headers are a hash.  Keys are automatically upcased and
  77
+      # prefixed with 'HTTP_' if not already.
72 78
       def xml_http_request(request_method, path, parameters = nil, headers = nil)
73 79
         headers ||= {}
74 80
         headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
@@ -108,6 +114,12 @@ def post_via_redirect(path, parameters = nil, headers = nil)
108 114
         request_via_redirect(:post, path, parameters, headers)
109 115
       end
110 116
 
  117
+      # Performs a PATCH request, following any subsequent redirect.
  118
+      # See +request_via_redirect+ for more information.
  119
+      def patch_via_redirect(path, parameters = nil, headers = nil)
  120
+        request_via_redirect(:patch, path, parameters, headers)
  121
+      end
  122
+
111 123
       # Performs a PUT request, following any subsequent redirect.
112 124
       # See +request_via_redirect+ for more information.
113 125
       def put_via_redirect(path, parameters = nil, headers = nil)
@@ -318,7 +330,7 @@ def reset!
318 330
         @integration_session = Integration::Session.new(app)
319 331
       end
320 332
 
321  
-      %w(get post put head delete options cookies assigns
  333
+      %w(get post put patch head delete options cookies assigns
322 334
          xml_http_request xhr get_via_redirect post_via_redirect).each do |method|
323 335
         define_method(method) do |*args|
324 336
           reset! unless integration_session
2  actionpack/lib/action_view/base.rb
@@ -132,6 +132,8 @@ module ActionView #:nodoc:
132 132
   class Base
133 133
     include Helpers, ::ERB::Util, Context
134 134
 
  135
+    cattr_accessor(:default_method_for_update) {:put}
  136
+
135 137
     # Specify the proc used to decorate input tags that refer to attributes with errors.
136 138
     cattr_accessor :field_error_proc
137 139
     @@field_error_proc = Proc.new{ |html_tag, instance| "<div class=\"field_with_errors\">#{html_tag}</div>".html_safe }
4  actionpack/lib/action_view/helpers/form_helper.rb
@@ -250,7 +250,7 @@ def convert_to_model(object)
250 250
       #
251 251
       # You can force the form to use the full array of HTTP verbs by setting
252 252
       #
253  
-      #    :method => (:get|:post|:put|:delete)
  253
+      #    :method => (:get|:post|:patch|:put|:delete)
254 254
       #
255 255
       # in the options hash. If the verb is not GET or POST, which are natively supported by HTML forms, the
256 256
       # form will be set to POST and a hidden input called _method will carry the intended verb for the server
@@ -385,7 +385,7 @@ def apply_form_for_options!(record, object, options) #:nodoc:
385 385
         object = convert_to_model(object)
386 386
 
387 387
         as = options[:as]
388  
-        action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :put] : [:new, :post]
  388
+        action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, ActionView::Base.default_method_for_update] : [:new, :post]
389 389
         options[:html].reverse_merge!(
390 390
           :class  => as ? "#{action}_#{as}" : dom_class(object, action),
391 391
           :id     => as ? "#{action}_#{as}" : [options[:namespace], dom_id(object, action)].compact.join("_").presence,
8  actionpack/lib/action_view/helpers/url_helper.rb
@@ -146,12 +146,12 @@ def url_for(options = nil)
146 146
       #   create an HTML form and immediately submit the form for processing using
147 147
       #   the HTTP verb specified. Useful for having links perform a POST operation
148 148
       #   in dangerous actions like deleting a record (which search bots can follow
149  
-      #   while spidering your site). Supported verbs are <tt>:post</tt>, <tt>:delete</tt> and <tt>:put</tt>.
  149
+      #   while spidering your site). Supported verbs are <tt>:post</tt>, <tt>:delete</tt>, <tt>:patch</tt>, and <tt>:put</tt>.
150 150
       #   Note that if the user has JavaScript disabled, the request will fall back
151 151
       #   to using GET. If <tt>:href => '#'</tt> is used and the user has JavaScript
152 152
       #   disabled clicking the link will have no effect. If you are relying on the
153 153
       #   POST behavior, you should check for it in your controller's action by using
154  
-      #   the request object's methods for <tt>post?</tt>, <tt>delete?</tt> or <tt>put?</tt>.
  154
+      #   the request object's methods for <tt>post?</tt>, <tt>delete?</tt>, <tt>:patch</tt>, or <tt>put?</tt>.
155 155
       # * <tt>:remote => true</tt> - This will allow the unobtrusive JavaScript
156 156
       #   driver to make an Ajax request to the URL in question instead of following
157 157
       #   the link. The drivers each provide mechanisms for listening for the
@@ -272,7 +272,7 @@ def link_to(*args, &block)
272 272
       #
273 273
       # There are a few special +html_options+:
274 274
       # * <tt>:method</tt> - Symbol of HTTP verb. Supported verbs are <tt>:post</tt>, <tt>:get</tt>,
275  
-      #   <tt>:delete</tt> and <tt>:put</tt>. By default it will be <tt>:post</tt>.
  275
+      #   <tt>:delete</tt>, <tt>:patch</tt>, and <tt>:put</tt>. By default it will be <tt>:post</tt>.
276 276
       # * <tt>:disabled</tt> - If set to true, it will generate a disabled button.
277 277
       # * <tt>:confirm</tt> - This will use the unobtrusive JavaScript driver to
278 278
       #   prompt with the question specified. If the user accepts, the link is
@@ -329,7 +329,7 @@ def button_to(name, options = {}, html_options = {})
329 329
         remote = html_options.delete('remote')
330 330
 
331 331
         method     = html_options.delete('method').to_s
332  
-        method_tag = %w{put delete}.include?(method) ? method_tag(method) : ""
  332
+        method_tag = %w{put patch delete}.include?(method) ? method_tag(method) : ""
333 333
 
334 334
         form_method  = method == 'get' ? 'get' : 'post'
335 335
         form_options = html_options.delete('form') || {}
1  actionpack/lib/action_view/railtie.rb
@@ -33,6 +33,7 @@ class Railtie < Rails::Railtie
33 33
     end
34 34
 
35 35
     initializer "action_view.set_configs" do |app|
  36
+      ActionView::Base.default_method_for_update = app.config.default_method_for_update
36 37
       ActiveSupport.on_load(:action_view) do
37 38
         app.config.action_view.each do |k,v|
38 39
           send "#{k}=", v
2  actionpack/test/controller/caching_test.rb
@@ -180,7 +180,7 @@ def test_should_cache_ok_at_custom_path
180 180
   end
181 181
 
182 182
   [:ok, :no_content, :found, :not_found].each do |status|
183  
-    [:get, :post, :put, :delete].each do |method|
  183
+    [:get, :post, :patch, :put, :delete].each do |method|
184 184
       unless method == :get && status == :ok
185 185
         define_method "test_shouldnt_cache_#{method}_with_#{status}_status" do
186 186
           send(method, status)
24  actionpack/test/controller/integration_test.rb
@@ -63,6 +63,12 @@ def test_post_via_redirect
63 63
     @session.post_via_redirect(path, args, headers)
64 64
   end
65 65
 
  66
+  def test_patch_via_redirect
  67
+    path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" }
  68
+    @session.expects(:request_via_redirect).with(:patch, path, args, headers)
  69
+    @session.patch_via_redirect(path, args, headers)
  70
+  end
  71
+
66 72
   def test_put_via_redirect
67 73
     path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" }
68 74
     @session.expects(:request_via_redirect).with(:put, path, args, headers)
@@ -87,6 +93,12 @@ def test_post
87 93
     @session.post(path,params,headers)
88 94
   end
89 95
 
  96
+  def test_patch
  97
+    path = "/index"; params = "blah"; headers = {:location => 'blah'}
  98
+    @session.expects(:process).with(:patch,path,params,headers)
  99
+    @session.patch(path,params,headers)
  100
+  end
  101
+
90 102
   def test_put
91 103
     path = "/index"; params = "blah"; headers = {:location => 'blah'}
92 104
     @session.expects(:process).with(:put,path,params,headers)
@@ -131,6 +143,16 @@ def test_xml_http_request_post
131 143
     @session.xml_http_request(:post,path,params,headers)
132 144
   end
133 145
 
  146
+  def test_xml_http_request_patch
  147
+    path = "/index"; params = "blah"; headers = {:location => 'blah'}
  148
+    headers_after_xhr = headers.merge(
  149
+      "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
  150
+      "HTTP_ACCEPT"           => "text/javascript, text/html, application/xml, text/xml, */*"
  151
+    )
  152
+    @session.expects(:process).with(:patch,path,params,headers_after_xhr)
  153
+    @session.xml_http_request(:patch,path,params,headers)
  154
+  end
  155
+
134 156
   def test_xml_http_request_put
135 157
     path = "/index"; params = "blah"; headers = {:location => 'blah'}
136 158
     headers_after_xhr = headers.merge(
@@ -228,7 +250,7 @@ def test_integration_methods_called
228 250
     @integration_session.stubs(:generic_url_rewriter)
229 251
     @integration_session.stubs(:process)
230 252
 
231  
-    %w( get post head put delete options ).each do |verb|
  253
+    %w( get post head patch put delete options ).each do |verb|
232 254
       assert_nothing_raised("'#{verb}' should use integration test methods") { __send__(verb, '/') }
233 255
     end
234 256
   end
35  actionpack/test/controller/mime_responds_test.rb
@@ -770,6 +770,41 @@ def test_using_resource_for_post_with_json_yields_unprocessable_entity_on_failur
770 770
     end
771 771
   end
772 772
 
  773
+  def test_using_resource_for_patch_with_html_redirects_on_success
  774
+    with_test_route_set do
  775
+      patch :using_resource
  776
+      assert_equal "text/html", @response.content_type
  777
+      assert_equal 302, @response.status
  778
+      assert_equal "http://www.example.com/customers/13", @response.location
  779
+      assert @response.redirect?
  780
+    end
  781
+  end
  782
+
  783
+  def test_using_resource_for_patch_with_html_rerender_on_failure
  784
+    with_test_route_set do
  785
+      errors = { :name => :invalid }
  786
+      Customer.any_instance.stubs(:errors).returns(errors)
  787
+      patch :using_resource
  788
+      assert_equal "text/html", @response.content_type
  789
+      assert_equal 200, @response.status
  790
+      assert_equal "Edit world!\n", @response.body
  791
+      assert_nil @response.location
  792
+    end
  793
+  end
  794
+
  795
+  def test_using_resource_for_patch_with_html_rerender_on_failure_even_on_method_override
  796
+    with_test_route_set do
  797
+      errors = { :name => :invalid }
  798
+      Customer.any_instance.stubs(:errors).returns(errors)
  799
+      @request.env["rack.methodoverride.original_method"] = "POST"
  800
+      patch :using_resource
  801
+      assert_equal "text/html", @response.content_type
  802
+      assert_equal 200, @response.status
  803
+      assert_equal "Edit world!\n", @response.body
  804
+      assert_nil @response.location
  805
+    end
  806
+  end
  807
+
773 808
   def test_using_resource_for_put_with_html_redirects_on_success
774 809
     with_test_route_set do
775 810
       put :using_resource
15  actionpack/test/controller/request_forgery_protection_test.rb
@@ -114,6 +114,10 @@ def test_should_not_allow_post_without_token_irrespective_of_format
114 114
     assert_blocked { post :index, :format=>'xml' }
115 115
   end
116 116
 
  117
+  def test_should_not_allow_patch_without_token
  118
+    assert_blocked { patch :index }
  119
+  end
  120
+
117 121
   def test_should_not_allow_put_without_token
118 122
     assert_blocked { put :index }
119 123
   end
@@ -130,6 +134,10 @@ def test_should_allow_post_with_token
130 134
     assert_not_blocked { post :index, :custom_authenticity_token => @token }
131 135
   end
132 136
 
  137
+  def test_should_allow_patch_with_token
  138
+    assert_not_blocked { patch :index, :custom_authenticity_token => @token }
  139
+  end
  140
+
133 141
   def test_should_allow_put_with_token
134 142
     assert_not_blocked { put :index, :custom_authenticity_token => @token }
135 143
   end
@@ -148,6 +156,11 @@ def test_should_allow_delete_with_token_in_header
148 156
     assert_not_blocked { delete :index }
149 157
   end
150 158
 
  159
+  def test_should_allow_patch_with_token_in_header
  160
+    @request.env['HTTP_X_CSRF_TOKEN'] = @token
  161
+    assert_not_blocked { patch :index }
  162
+  end
  163
+
151 164
   def test_should_allow_put_with_token_in_header
152 165
     @request.env['HTTP_X_CSRF_TOKEN'] = @token
153 166
     assert_not_blocked { put :index }
@@ -232,7 +245,7 @@ def test_should_not_render_button_to_with_token_tag
232 245
   end
233 246
 
234 247
   def test_should_allow_all_methods_without_token
235  
-    [:post, :put, :delete].each do |method|
  248
+    [:post, :patch, :put, :delete].each do |method|
236 249
       assert_nothing_raised { send(method, :index)}
237 250
     end
238 251
   end
25  actionpack/test/controller/resources_test.rb
@@ -158,7 +158,7 @@ def test_with_name_prefix
158 158
   end
159 159
 
160 160
   def test_with_collection_actions
161  
-    actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete }
  161
+    actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete, 'e' => :patch }
162 162
 
163 163
     with_routing do |set|
164 164
       set.draw do
@@ -167,6 +167,7 @@ def test_with_collection_actions
167 167
           put    :b, :on => :collection
168 168
           post   :c, :on => :collection
169 169
           delete :d, :on => :collection
  170
+          patch  :e, :on => :collection
170 171
         end
171 172
       end
172 173
 
@@ -185,7 +186,7 @@ def test_with_collection_actions
185 186
   end
186 187
 
187 188
   def test_with_collection_actions_and_name_prefix
188  
-    actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete }
  189
+    actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete, 'e' => :patch }
189 190
 
190 191
     with_routing do |set|
191 192
       set.draw do
@@ -195,6 +196,7 @@ def test_with_collection_actions_and_name_prefix
195 196
             put    :b, :on => :collection
196 197
             post   :c, :on => :collection
197 198
             delete :d, :on => :collection
  199
+            patch  :e, :on => :collection
198 200
           end
199 201
         end
200 202
       end
@@ -241,7 +243,7 @@ def test_with_collection_actions_and_name_prefix_and_member_action_with_same_nam
241 243
   end
242 244
 
243 245
   def test_with_collection_action_and_name_prefix_and_formatted
244  
-    actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete }
  246
+    actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete, 'e' => :patch }
245 247
 
246 248
     with_routing do |set|
247 249
       set.draw do
@@ -251,6 +253,7 @@ def test_with_collection_action_and_name_prefix_and_formatted
251 253
             put    :b, :on => :collection
252 254
             post   :c, :on => :collection
253 255
             delete :d, :on => :collection
  256
+            patch  :e, :on => :collection
254 257
           end
255 258
         end
256 259
       end
@@ -270,7 +273,7 @@ def test_with_collection_action_and_name_prefix_and_formatted
270 273
   end
271 274
 
272 275
   def test_with_member_action
273  
-    [:put, :post].each do |method|
  276
+    [:patch, :put, :post].each do |method|
274 277
       with_restful_routing :messages, :member => { :mark => method } do
275 278
         mark_options = {:action => 'mark', :id => '1'}
276 279
         mark_path    = "/messages/1/mark"
@@ -294,7 +297,7 @@ def test_with_member_action_and_requirement
294 297
   end
295 298
 
296 299
   def test_member_when_override_paths_for_default_restful_actions_with
297  
-    [:put, :post].each do |method|
  300
+    [:patch, :put, :post].each do |method|
298 301
       with_restful_routing :messages, :member => { :mark => method }, :path_names => {:new => 'nuevo'} do
299 302
         mark_options = {:action => 'mark', :id => '1', :controller => "messages"}
300 303
         mark_path    = "/messages/1/mark"
@@ -311,7 +314,7 @@ def test_member_when_override_paths_for_default_restful_actions_with
311 314
   end
312 315
 
313 316
   def test_with_two_member_actions_with_same_method
314  
-    [:put, :post].each do |method|
  317
+    [:patch, :put, :post].each do |method|
315 318
       with_routing do |set|
316 319
         set.draw do
317 320
           resources :messages do
@@ -564,7 +567,7 @@ def test_should_create_nested_singleton_resource_routes
564 567
   end
565 568
 
566 569
   def test_singleton_resource_with_member_action
567  
-    [:put, :post].each do |method|
  570
+    [:patch, :put, :post].each do |method|
568 571
       with_routing do |set|
569 572
         set.draw do
570 573
           resource :account do
@@ -586,7 +589,7 @@ def test_singleton_resource_with_member_action
586 589
   end
587 590
 
588 591
   def test_singleton_resource_with_two_member_actions_with_same_method
589  
-    [:put, :post].each do |method|
  592
+    [:patch, :put, :post].each do |method|
590 593
       with_routing do |set|
591 594
         set.draw do
592 595
           resource :account do
@@ -651,13 +654,17 @@ def test_should_nest_singleton_resource_in_resources
651 654
     end
652 655
   end
653 656
 
654  
-  def test_should_not_allow_delete_or_put_on_collection_path
  657
+  def test_should_not_allow_delete_or_patch_or_put_on_collection_path
655 658
     controller_name = :messages
656 659
     with_restful_routing controller_name do
657 660
       options = { :controller => controller_name.to_s }
658 661
       collection_path = "/#{controller_name}"
659 662
 
660 663
       assert_raise(ActionController::RoutingError) do
  664
+        assert_recognizes(options.merge(:action => 'update'), :path => collection_path, :method => :patch)
  665
+      end
  666
+
  667
+      assert_raise(ActionController::RoutingError) do
661 668
         assert_recognizes(options.merge(:action => 'update'), :path => collection_path, :method => :put)
662 669
       end
663 670
 
15  actionpack/test/controller/routing_test.rb
@@ -648,11 +648,12 @@ def setup_request_method_routes_for(method)
648 648
       match '/match' => 'books#get', :via => :get
649 649
       match '/match' => 'books#post', :via => :post
650 650
       match '/match' => 'books#put', :via => :put
  651
+      match '/match' => 'books#patch', :via => :patch
651 652
       match '/match' => 'books#delete', :via => :delete
652 653
     end
653 654
   end
654 655
 
655  
-  %w(GET POST PUT DELETE).each do |request_method|
  656
+  %w(GET PATCH POST PUT DELETE).each do |request_method|
656 657
     define_method("test_request_method_recognized_with_#{request_method}") do
657 658
       setup_request_method_routes_for(request_method)
658 659
       params = rs.recognize_path("/match", :method => request_method)
@@ -1035,6 +1036,7 @@ def test_recognize_with_http_methods
1035 1036
       post   "/people"     => "people#create"
1036 1037
       get    "/people/:id" => "people#show",  :as => "person"
1037 1038
       put    "/people/:id" => "people#update"
  1039
+      patch  "/people/:id" => "people#update"
1038 1040
       delete "/people/:id" => "people#destroy"
1039 1041
     end
1040 1042
 
@@ -1047,6 +1049,9 @@ def test_recognize_with_http_methods
1047 1049
     params = set.recognize_path("/people/5", :method => :put)
1048 1050
     assert_equal("update", params[:action])
1049 1051
 
  1052
+    params = set.recognize_path("/people/5", :method => :patch)
  1053
+    assert_equal("update", params[:action])
  1054
+
1050 1055
     assert_raise(ActionController::UnknownHttpMethod) {
1051 1056
       set.recognize_path("/people", :method => :bacon)
1052 1057
     }
@@ -1059,6 +1064,10 @@ def test_recognize_with_http_methods
1059 1064
     assert_equal("update", params[:action])
1060 1065
     assert_equal("5", params[:id])
1061 1066
 
  1067
+    params = set.recognize_path("/people/5", :method => :patch)
  1068
+    assert_equal("update", params[:action])
  1069
+    assert_equal("5", params[:id])
  1070
+
1062 1071
     params = set.recognize_path("/people/5", :method => :delete)
1063 1072
     assert_equal("destroy", params[:action])
1064 1073
     assert_equal("5", params[:id])
@@ -1112,6 +1121,7 @@ def test_recognize_with_conditions_and_format
1112 1121
     set.draw do
1113 1122
       get "people/:id" => "people#show", :as => "person"
1114 1123
       put "people/:id" => "people#update"
  1124
+      patch "people/:id" => "people#update"
1115 1125
       get "people/:id(.:format)" => "people#show"
1116 1126
     end
1117 1127
 
@@ -1122,6 +1132,9 @@ def test_recognize_with_conditions_and_format
1122 1132
     params = set.recognize_path("/people/5", :method => :put)
1123 1133
     assert_equal("update", params[:action])
1124 1134
 
  1135
+    params = set.recognize_path("/people/5", :method => :patch)
  1136
+    assert_equal("update", params[:action])
  1137
+
1125 1138
     params = set.recognize_path("/people/5.png", :method => :get)
1126 1139
     assert_equal("show", params[:action])
1127 1140
     assert_equal("5", params[:id])
15  actionpack/test/dispatch/request_test.rb
@@ -325,14 +325,14 @@ def url_for(options = {})
325 325
   end
326 326
 
327 327
   test "String request methods" do
328  
-    [:get, :post, :put, :delete].each do |method|
  328
+    [:get, :post, :patch, :put, :delete].each do |method|
329 329
       request = stub_request 'REQUEST_METHOD' => method.to_s.upcase
330 330
       assert_equal method.to_s.upcase, request.method
331 331
     end
332 332
   end
333 333
 
334 334
   test "Symbol forms of request methods via method_symbol" do
335  
-    [:get, :post, :put, :delete].each do |method|
  335
+    [:get, :post, :patch, :put, :delete].each do |method|
336 336
       request = stub_request 'REQUEST_METHOD' => method.to_s.upcase
337 337
       assert_equal method, request.method_symbol
338 338
     end
@@ -346,7 +346,7 @@ def url_for(options = {})
346 346
   end
347 347
 
348 348
   test "allow method hacking on post" do
349  
-    %w(GET OPTIONS PUT POST DELETE).each do |method|
  349
+    %w(GET OPTIONS PATCH PUT POST DELETE).each do |method|
350 350
       request = stub_request "REQUEST_METHOD" => method.to_s.upcase
351 351
       assert_equal(method == "HEAD" ? "GET" : method, request.method)
352 352
     end
@@ -360,7 +360,7 @@ def url_for(options = {})
360 360
   end
361 361
 
362 362
   test "restrict method hacking" do
363  
-    [:get, :put, :delete].each do |method|
  363
+    [:get, :patch, :put, :delete].each do |method|
364 364
       request = stub_request 'REQUEST_METHOD' => method.to_s.upcase,
365 365
         'action_dispatch.request.request_parameters' => { :_method => 'put' }
366 366
       assert_equal method.to_s.upcase, request.method
@@ -375,6 +375,13 @@ def url_for(options = {})
375 375
     assert request.head?
376 376
   end
377 377
 
  378
+  test "post masquerading as patch" do
  379
+    request = stub_request 'REQUEST_METHOD' => 'PATCH', "rack.methodoverride.original_method" => "POST"
  380
+    assert_equal "POST", request.method
  381
+    assert_equal "PATCH",  request.request_method
  382
+    assert request.patch?
  383
+  end
  384
+
378 385
   test "post masquerading as put" do
379 386
     request = stub_request 'REQUEST_METHOD' => 'PUT', "rack.methodoverride.original_method" => "POST"
380 387
     assert_equal "POST", request.method
9  actionpack/test/template/form_helper_test.rb
@@ -2195,6 +2195,15 @@ def test_form_for_with_existing_object_and_custom_url
2195 2195
     assert_equal expected, output_buffer
2196 2196
   end
2197 2197
 
  2198
+  def test_form_for_with_default_method_as_patch
  2199
+    ActionView::Base.default_method_for_update = :patch
  2200
+    form_for(@post) {}
  2201
+    expected = whole_form("/posts/123", "edit_post_123", "edit_post", "patch")
  2202
+    assert_dom_equal expected, output_buffer
  2203
+  ensure
  2204
+    ActionView::Base.default_method_for_update = :put
  2205
+  end
  2206
+
2198 2207
   def test_fields_for_returns_block_result
2199 2208
     output = fields_for(Post.new) { |f| "fields" }
2200 2209
     assert_equal "fields", output
6  actionpack/test/template/form_tag_helper_test.rb
@@ -78,6 +78,12 @@ def test_form_tag_multipart
78 78
     assert_dom_equal expected, actual
79 79
   end
80 80
 
  81
+  def test_form_tag_with_method_patch
  82
+    actual = form_tag({}, { :method => :patch })
  83
+    expected = whole_form("http://www.example.com", :method => :patch)
  84
+    assert_dom_equal expected, actual
  85
+  end
  86
+
81 87
   def test_form_tag_with_method_put
82 88
     actual = form_tag({}, { :method => :put })
83 89
     expected = whole_form("http://www.example.com", :method => :put)
6  activemodel/lib/active_model/lint.rb
@@ -62,9 +62,9 @@ def test_to_partial_path
62 62
       #
63 63
       # Returns a boolean that specifies whether the object has been persisted yet.
64 64
       # This is used when calculating the URL for an object. If the object is
65  
-      # not persisted, a form for that object, for instance, will be POSTed to the
66  
-      # collection. If it is persisted, a form for the object will be PUT to the
67  
-      # URL for the object.
  65
+      # not persisted, a form for that object, for instance, will route to the
  66
+      # create action. If it is persisted, a form for the object will routes to
  67
+      # the update action.
68 68
       def test_persisted?
69 69
         assert model.respond_to?(:persisted?), "The model should respond to persisted?"
70 70
         assert_boolean model.persisted?, "persisted?"
7  activeresource/lib/active_resource/connection.rb
@@ -15,6 +15,7 @@ class Connection
15 15
     HTTP_FORMAT_HEADER_NAMES = {  :get => 'Accept',
16 16
       :put => 'Content-Type',
17 17
       :post => 'Content-Type',
  18
+      :patch => 'Content-Type',
18 19
       :delete => 'Accept',
19 20
       :head => 'Accept'
20 21
     }
@@ -86,6 +87,12 @@ def delete(path, headers = {})
86 87
       with_auth { request(:delete, path, build_request_headers(headers, :delete, self.site.merge(path))) }
87 88
     end
88 89
 
  90
+    # Executes a PATCH request (see HTTP protocol documentation if unfamiliar).
  91
+    # Used to update resources.
  92
+    def patch(path, body = '', headers = {})
  93
+      with_auth { request(:patch, path, body.to_s, build_request_headers(headers, :patch, self.site.merge(path))) }
  94
+    end
  95
+
89 96
     # Executes a PUT request (see HTTP protocol documentation if unfamiliar).
90 97
     # Used to update resources.
91 98
     def put(path, body = '', headers = {})
16  activeresource/lib/active_resource/custom_methods.rb
@@ -11,10 +11,10 @@ module ActiveResource
11 11
   #
12 12
   #  This route set creates routes for the following HTTP requests:
13 13
   #
14  
-  #    POST    /people/new/register.json # PeopleController.register
15  
-  #    PUT     /people/1/promote.json    # PeopleController.promote with :id => 1
16  
-  #    DELETE  /people/1/deactivate.json # PeopleController.deactivate with :id => 1
17  
-  #    GET     /people/active.json       # PeopleController.active
  14
+  #    POST      /people/new/register.json # PeopleController.register
  15
+  #    PUT/PATCH /people/1/promote.json    # PeopleController.promote with :id => 1
  16
+  #    DELETE    /people/1/deactivate.json # PeopleController.deactivate with :id => 1
  17
+  #    GET       /people/active.json       # PeopleController.active
18 18
   #
19 19
   # Using this module, Active Resource can use these custom REST methods just like the
20 20
   # standard methods.
@@ -63,6 +63,10 @@ def post(custom_method_name, options = {}, body = '')
63 63
           connection.post(custom_method_collection_url(custom_method_name, options), body, headers)
64 64
         end
65 65
 
  66
+        def patch(custom_method_name, options = {}, body = '')
  67
+          connection.patch(custom_method_collection_url(custom_method_name, options), body, headers)
  68
+        end
  69
+
66 70
         def put(custom_method_name, options = {}, body = '')