Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

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
David Lee dlee authored
Showing with 402 additions and 151 deletions.
  1. +1 −0  Gemfile
  2. +2 −2 actionpack/lib/action_controller/metal/http_authentication.rb
  3. +7 −6 actionpack/lib/action_controller/metal/responder.rb
  4. +6 −1 actionpack/lib/action_controller/test_case.rb
  5. +6 −0 actionpack/lib/action_dispatch/http/request.rb
  6. +1 −0  actionpack/lib/action_dispatch/railtie.rb
  7. +9 −6 actionpack/lib/action_dispatch/routing.rb
  8. +71 −54 actionpack/lib/action_dispatch/routing/mapper.rb
  9. +19 −7 actionpack/lib/action_dispatch/testing/integration.rb
  10. +2 −0  actionpack/lib/action_view/base.rb
  11. +2 −2 actionpack/lib/action_view/helpers/form_helper.rb
  12. +4 −4 actionpack/lib/action_view/helpers/url_helper.rb
  13. +1 −0  actionpack/lib/action_view/railtie.rb
  14. +1 −1  actionpack/test/controller/caching_test.rb
  15. +23 −1 actionpack/test/controller/integration_test.rb
  16. +35 −0 actionpack/test/controller/mime_responds_test.rb
  17. +14 −1 actionpack/test/controller/request_forgery_protection_test.rb
  18. +16 −9 actionpack/test/controller/resources_test.rb
  19. +14 −1 actionpack/test/controller/routing_test.rb
  20. +11 −4 actionpack/test/dispatch/request_test.rb
  21. +9 −0 actionpack/test/template/form_helper_test.rb
  22. +6 −0 actionpack/test/template/form_tag_helper_test.rb
  23. +3 −3 activemodel/lib/active_model/lint.rb
  24. +7 −0 activeresource/lib/active_resource/connection.rb
  25. +12 −4 activeresource/lib/active_resource/custom_methods.rb
  26. +3 −3 activeresource/lib/active_resource/http_mock.rb
  27. +8 −4 activeresource/test/cases/format_test.rb
  28. +2 −2 activeresource/test/cases/http_mock_test.rb
  29. +1 −1  railties/guides/source/action_controller_overview.textile
  30. +2 −1  railties/guides/source/ajax_on_rails.textile
  31. +5 −2 railties/guides/source/configuring.textile
  32. +10 −5 railties/guides/source/form_helpers.textile
  33. +25 −17 railties/guides/source/routing.textile
  34. +4 −2 railties/guides/source/testing.textile
  35. +2 −1  railties/lib/rails/application/configuration.rb
  36. +3 −3 railties/lib/rails/generators/active_model.rb
  37. +3 −0  railties/lib/rails/generators/rails/app/templates/config/application.rb
  38. +2 −2 railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
  39. +45 −2 railties/test/application/configuration_test.rb
  40. +5 −0 railties/test/generators/app_generator_test.rb
1  Gemfile
View
@@ -8,6 +8,7 @@ else
gem 'arel'
end
+gem 'rack-test', :git => "https://github.com/brynary/rack-test.git"
gem 'bcrypt-ruby', '~> 3.0.0'
gem 'jquery-rails'
4 actionpack/lib/action_controller/metal/http_authentication.rb
View
@@ -279,7 +279,7 @@ def secret_token(request)
#
# An implementation might choose not to accept a previously used nonce or a previously used digest, in order to
# protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for
- # POST or PUT requests and a time-stamp for GET requests. For more details on the issues involved see Section 4
+ # POST, PUT, or PATCH requests and a time-stamp for GET requests. For more details on the issues involved see Section 4
# of this document.
#
# The nonce is opaque to the client. Composed of Time, and hash of Time with secret
@@ -293,7 +293,7 @@ def nonce(secret_key, time = Time.now)
end
# Might want a shorter timeout depending on whether the request
- # is a PUT or POST, and if client is browser or web service.
+ # is a PATCH, PUT, or POST, and if client is browser or web service.
# Can be much shorter if the Stale directive is implemented. This would
# allow a user to use new nonce without prompting user again for their
# username and password.
13 actionpack/lib/action_controller/metal/responder.rb
View
@@ -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 @@ def initialize(controller, resources, options={})
end
delegate :head, :render, :redirect_to, :to => :controller
- delegate :get?, :post?, :put?, :delete?, :to => :request
+ delegate :get?, :post?, :patch?, :put?, :delete?, :to => :request
# Undefine :to_json and :to_yaml since it's defined on Object
undef_method(:to_json) if method_defined?(:to_json)
@@ -259,11 +260,11 @@ def has_errors?
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
7 actionpack/lib/action_controller/test_case.rb
View
@@ -225,7 +225,7 @@ def exists?
# == Basic example
#
# Functional tests are written as follows:
- # 1. First, one uses the +get+, +post+, +put+, +delete+ or +head+ method to simulate
+ # 1. First, one uses the +get+, +post+, +patch+, +put+, +delete+ or +head+ method to simulate
# an HTTP request.
# 2. Then, one asserts whether the current state is as expected. "State" can be anything:
# the controller's HTTP response, the database contents, etc.
@@ -392,6 +392,11 @@ def post(action, *args)
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)
6 actionpack/lib/action_dispatch/http/request.rb
View
@@ -97,6 +97,12 @@ def post?
HTTP_METHOD_LOOKUP[request_method] == :post
end
+ # Is this a PATCH request?
+ # Equivalent to <tt>request.request_method == :patch</tt>.
+ def patch?
+ HTTP_METHOD_LOOKUP[request_method] == :patch
+ end
+
# Is this a PUT request?
# Equivalent to <tt>request.request_method_symbol == :put</tt>.
def put?
1  actionpack/lib/action_dispatch/railtie.rb
View
@@ -23,6 +23,7 @@ class Railtie < Rails::Railtie
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)
15 actionpack/lib/action_dispatch/routing.rb
View
@@ -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 Routing
autoload :PolymorphicRoutes, 'action_dispatch/routing/polymorphic_routes'
SEPARATORS = %w( / . ? ) #:nodoc:
- HTTP_METHODS = [:get, :head, :post, :put, :delete, :options] #:nodoc:
+ HTTP_METHODS = [:get, :head, :post, :patch, :put, :delete, :options] #:nodoc:
end
end
125 actionpack/lib/action_dispatch/routing/mapper.rb
View
@@ -7,6 +7,8 @@
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 HttpHelpers
#
# 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 @@ def get(*args, &block)
#
# Example:
#
- # post 'bacon', :to => 'food#bacon'
+ # post 'bacon', :to => 'food#bacon'
def post(*args, &block)
map_method(:post, args, &block)
end
+ # Define a route that only recognizes HTTP PATCH.
+ # For supported arguments, see <tt>Base#match</tt>.
+ #
+ # Example:
+ #
+ # patch 'bacon', :to => 'food#bacon'
+ def patch(*args, &block)
+ map_method(:patch, args, &block)
+ end
+
# Define a route that only recognizes HTTP PUT.
# For supported arguments, see <tt>Base#match</tt>.
#
# Example:
#
- # put 'bacon', :to => 'food#bacon'
+ # put 'bacon', :to => 'food#bacon'
def put(*args, &block)
map_method(:put, args, &block)
end
@@ -495,7 +507,7 @@ def put(*args, &block)
#
# 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 @@ def map_method(method, args, &block)
# 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 @@ def map_method(method, args, &block)
# 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 @@ def controller(controller, options={})
#
# 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 @@ def resources_path_names(options)
# 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 @@ def resource(*resources, &block)
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 @@ def resource(*resources, &block)
# 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 @@ def resource(*resources, &block)
#
# 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 @@ def resource(*resources, &block)
#
# 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 @@ def resources(*resources, &block)
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
26 actionpack/lib/action_dispatch/testing/integration.rb
View
@@ -26,8 +26,8 @@ module RequestHelpers
# object's <tt>@response</tt> instance variable will point to the same
# response object.
#
- # You can also perform POST, PUT, DELETE, and HEAD requests with +#post+,
- # +#put+, +#delete+, and +#head+.
+ # You can also perform POST, PATCH, PUT, DELETE, and HEAD requests with
+ # +#post+, +#patch+, +#put+, +#delete+, and +#head+.
def get(path, parameters = nil, headers = nil)
process :get, path, parameters, headers
end
@@ -38,6 +38,12 @@ def post(path, parameters = nil, headers = nil)
process :post, path, parameters, headers
end
+ # Performs a PATCH request with the given parameters. See +#get+ for more
+ # details.
+ def patch(path, parameters = nil, headers = nil)
+ process :patch, path, parameters, headers
+ end
+
# Performs a PUT request with the given parameters. See +#get+ for more
# details.
def put(path, parameters = nil, headers = nil)
@@ -65,10 +71,10 @@ def options(path, parameters = nil, headers = nil)
# Performs an XMLHttpRequest request with the given parameters, mirroring
# a request from the Prototype library.
#
- # The request_method is +:get+, +:post+, +:put+, +:delete+ or +:head+; the
- # parameters are +nil+, a hash, or a url-encoded or multipart string;
- # the headers are a hash. Keys are automatically upcased and prefixed
- # with 'HTTP_' if not already.
+ # The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or
+ # +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart
+ # string; the headers are a hash. Keys are automatically upcased and
+ # prefixed with 'HTTP_' if not already.
def xml_http_request(request_method, path, parameters = nil, headers = nil)
headers ||= {}
headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
@@ -108,6 +114,12 @@ def post_via_redirect(path, parameters = nil, headers = nil)
request_via_redirect(:post, path, parameters, headers)
end
+ # Performs a PATCH request, following any subsequent redirect.
+ # See +request_via_redirect+ for more information.
+ def patch_via_redirect(path, parameters = nil, headers = nil)
+ request_via_redirect(:patch, path, parameters, headers)
+ end
+
# Performs a PUT request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
def put_via_redirect(path, parameters = nil, headers = nil)
@@ -318,7 +330,7 @@ def reset!
@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
2  actionpack/lib/action_view/base.rb
View
@@ -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 }
4 actionpack/lib/action_view/helpers/form_helper.rb
View
@@ -250,7 +250,7 @@ def convert_to_model(object)
#
# You can force the form to use the full array of HTTP verbs by setting
#
- # :method => (:get|:post|:put|:delete)
+ # :method => (:get|:post|:patch|:put|:delete)
#
# in the options hash. If the verb is not GET or POST, which are natively supported by HTML forms, the
# form will be set to POST and a hidden input called _method will carry the intended verb for the server
@@ -385,7 +385,7 @@ def apply_form_for_options!(record, object, options) #:nodoc:
object = convert_to_model(object)
as = options[:as]
- action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :put] : [:new, :post]
+ action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, ActionView::Base.default_method_for_update] : [:new, :post]
options[:html].reverse_merge!(
:class => as ? "#{action}_#{as}" : dom_class(object, action),
:id => as ? "#{action}_#{as}" : [options[:namespace], dom_id(object, action)].compact.join("_").presence,
8 actionpack/lib/action_view/helpers/url_helper.rb
View
@@ -146,12 +146,12 @@ def url_for(options = nil)
# create an HTML form and immediately submit the form for processing using
# the HTTP verb specified. Useful for having links perform a POST operation
# in dangerous actions like deleting a record (which search bots can follow
- # while spidering your site). Supported verbs are <tt>:post</tt>, <tt>:delete</tt> and <tt>:put</tt>.
+ # while spidering your site). Supported verbs are <tt>:post</tt>, <tt>:delete</tt>, <tt>:patch</tt>, and <tt>:put</tt>.
# Note that if the user has JavaScript disabled, the request will fall back
# to using GET. If <tt>:href => '#'</tt> is used and the user has JavaScript
# disabled clicking the link will have no effect. If you are relying on the
# POST behavior, you should check for it in your controller's action by using
- # the request object's methods for <tt>post?</tt>, <tt>delete?</tt> or <tt>put?</tt>.
+ # the request object's methods for <tt>post?</tt>, <tt>delete?</tt>, <tt>:patch</tt>, or <tt>put?</tt>.
# * <tt>:remote => true</tt> - This will allow the unobtrusive JavaScript
# driver to make an Ajax request to the URL in question instead of following
# the link. The drivers each provide mechanisms for listening for the
@@ -272,7 +272,7 @@ def link_to(*args, &block)
#
# There are a few special +html_options+:
# * <tt>:method</tt> - Symbol of HTTP verb. Supported verbs are <tt>:post</tt>, <tt>:get</tt>,
- # <tt>:delete</tt> and <tt>:put</tt>. By default it will be <tt>:post</tt>.
+ # <tt>:delete</tt>, <tt>:patch</tt>, and <tt>:put</tt>. By default it will be <tt>:post</tt>.
# * <tt>:disabled</tt> - If set to true, it will generate a disabled button.
# * <tt>:confirm</tt> - This will use the unobtrusive JavaScript driver to
# prompt with the question specified. If the user accepts, the link is
@@ -329,7 +329,7 @@ def button_to(name, options = {}, html_options = {})
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') || {}
1  actionpack/lib/action_view/railtie.rb
View
@@ -33,6 +33,7 @@ class Railtie < Rails::Railtie
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
2  actionpack/test/controller/caching_test.rb
View
@@ -180,7 +180,7 @@ def test_should_cache_ok_at_custom_path
end
[:ok, :no_content, :found, :not_found].each do |status|
- [:get, :post, :put, :delete].each do |method|
+ [:get, :post, :patch, :put, :delete].each do |method|
unless method == :get && status == :ok
define_method "test_shouldnt_cache_#{method}_with_#{status}_status" do
send(method, status)
24 actionpack/test/controller/integration_test.rb
View
@@ -63,6 +63,12 @@ def test_post_via_redirect
@session.post_via_redirect(path, args, headers)
end
+ def test_patch_via_redirect
+ path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" }
+ @session.expects(:request_via_redirect).with(:patch, path, args, headers)
+ @session.patch_via_redirect(path, args, headers)
+ end
+
def test_put_via_redirect
path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" }
@session.expects(:request_via_redirect).with(:put, path, args, headers)
@@ -87,6 +93,12 @@ def test_post
@session.post(path,params,headers)
end
+ def test_patch
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ @session.expects(:process).with(:patch,path,params,headers)
+ @session.patch(path,params,headers)
+ end
+
def test_put
path = "/index"; params = "blah"; headers = {:location => 'blah'}
@session.expects(:process).with(:put,path,params,headers)
@@ -131,6 +143,16 @@ def test_xml_http_request_post
@session.xml_http_request(:post,path,params,headers)
end
+ def test_xml_http_request_patch
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ headers_after_xhr = headers.merge(
+ "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
+ "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*"
+ )
+ @session.expects(:process).with(:patch,path,params,headers_after_xhr)
+ @session.xml_http_request(:patch,path,params,headers)
+ end
+
def test_xml_http_request_put
path = "/index"; params = "blah"; headers = {:location => 'blah'}
headers_after_xhr = headers.merge(
@@ -228,7 +250,7 @@ def test_integration_methods_called
@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
35 actionpack/test/controller/mime_responds_test.rb
View
@@ -770,6 +770,41 @@ def test_using_resource_for_post_with_json_yields_unprocessable_entity_on_failur
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
15 actionpack/test/controller/request_forgery_protection_test.rb
View
@@ -114,6 +114,10 @@ def test_should_not_allow_post_without_token_irrespective_of_format
assert_blocked { post :index, :format=>'xml' }
end
+ def test_should_not_allow_patch_without_token
+ assert_blocked { patch :index }
+ end
+
def test_should_not_allow_put_without_token
assert_blocked { put :index }
end
@@ -130,6 +134,10 @@ def test_should_allow_post_with_token
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 @@ def test_should_allow_delete_with_token_in_header
assert_not_blocked { delete :index }
end
+ def test_should_allow_patch_with_token_in_header
+ @request.env['HTTP_X_CSRF_TOKEN'] = @token
+ assert_not_blocked { patch :index }
+ end
+
def test_should_allow_put_with_token_in_header
@request.env['HTTP_X_CSRF_TOKEN'] = @token
assert_not_blocked { put :index }
@@ -232,7 +245,7 @@ def test_should_not_render_button_to_with_token_tag
end
def test_should_allow_all_methods_without_token
- [:post, :put, :delete].each do |method|
+ [:post, :patch, :put, :delete].each do |method|
assert_nothing_raised { send(method, :index)}
end
end
25 actionpack/test/controller/resources_test.rb
View
@@ -158,7 +158,7 @@ def test_with_name_prefix
end
def test_with_collection_actions
- actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete }
+ actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete, 'e' => :patch }
with_routing do |set|
set.draw do
@@ -167,6 +167,7 @@ def test_with_collection_actions
put :b, :on => :collection
post :c, :on => :collection
delete :d, :on => :collection
+ patch :e, :on => :collection
end
end
@@ -185,7 +186,7 @@ def test_with_collection_actions
end
def test_with_collection_actions_and_name_prefix
- actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete }
+ actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete, 'e' => :patch }
with_routing do |set|
set.draw do
@@ -195,6 +196,7 @@ def test_with_collection_actions_and_name_prefix
put :b, :on => :collection
post :c, :on => :collection
delete :d, :on => :collection
+ patch :e, :on => :collection
end
end
end
@@ -241,7 +243,7 @@ def test_with_collection_actions_and_name_prefix_and_member_action_with_same_nam
end
def test_with_collection_action_and_name_prefix_and_formatted
- actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete }
+ actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete, 'e' => :patch }
with_routing do |set|
set.draw do
@@ -251,6 +253,7 @@ def test_with_collection_action_and_name_prefix_and_formatted
put :b, :on => :collection
post :c, :on => :collection
delete :d, :on => :collection
+ patch :e, :on => :collection
end
end
end
@@ -270,7 +273,7 @@ def test_with_collection_action_and_name_prefix_and_formatted
end
def test_with_member_action
- [:put, :post].each do |method|
+ [:patch, :put, :post].each do |method|
with_restful_routing :messages, :member => { :mark => method } do
mark_options = {:action => 'mark', :id => '1'}
mark_path = "/messages/1/mark"
@@ -294,7 +297,7 @@ def test_with_member_action_and_requirement
end
def test_member_when_override_paths_for_default_restful_actions_with
- [:put, :post].each do |method|
+ [:patch, :put, :post].each do |method|
with_restful_routing :messages, :member => { :mark => method }, :path_names => {:new => 'nuevo'} do
mark_options = {:action => 'mark', :id => '1', :controller => "messages"}
mark_path = "/messages/1/mark"
@@ -311,7 +314,7 @@ def test_member_when_override_paths_for_default_restful_actions_with
end
def test_with_two_member_actions_with_same_method
- [:put, :post].each do |method|
+ [:patch, :put, :post].each do |method|
with_routing do |set|
set.draw do
resources :messages do
@@ -564,7 +567,7 @@ def test_should_create_nested_singleton_resource_routes
end
def test_singleton_resource_with_member_action
- [:put, :post].each do |method|
+ [:patch, :put, :post].each do |method|
with_routing do |set|
set.draw do
resource :account do
@@ -586,7 +589,7 @@ def test_singleton_resource_with_member_action
end
def test_singleton_resource_with_two_member_actions_with_same_method
- [:put, :post].each do |method|
+ [:patch, :put, :post].each do |method|
with_routing do |set|
set.draw do
resource :account do
@@ -651,13 +654,17 @@ def test_should_nest_singleton_resource_in_resources
end
end
- def test_should_not_allow_delete_or_put_on_collection_path
+ def test_should_not_allow_delete_or_patch_or_put_on_collection_path
controller_name = :messages
with_restful_routing controller_name do
options = { :controller => controller_name.to_s }
collection_path = "/#{controller_name}"
assert_raise(ActionController::RoutingError) do
+ assert_recognizes(options.merge(:action => 'update'), :path => collection_path, :method => :patch)
+ end
+
+ assert_raise(ActionController::RoutingError) do
assert_recognizes(options.merge(:action => 'update'), :path => collection_path, :method => :put)
end
15 actionpack/test/controller/routing_test.rb
View
@@ -648,11 +648,12 @@ def setup_request_method_routes_for(method)
match '/match' => 'books#get', :via => :get
match '/match' => 'books#post', :via => :post
match '/match' => 'books#put', :via => :put
+ match '/match' => 'books#patch', :via => :patch
match '/match' => 'books#delete', :via => :delete
end
end
- %w(GET POST PUT DELETE).each do |request_method|
+ %w(GET PATCH POST PUT DELETE).each do |request_method|
define_method("test_request_method_recognized_with_#{request_method}") do
setup_request_method_routes_for(request_method)
params = rs.recognize_path("/match", :method => request_method)
@@ -1035,6 +1036,7 @@ def test_recognize_with_http_methods
post "/people" => "people#create"
get "/people/:id" => "people#show", :as => "person"
put "/people/:id" => "people#update"
+ patch "/people/:id" => "people#update"
delete "/people/:id" => "people#destroy"
end
@@ -1047,6 +1049,9 @@ def test_recognize_with_http_methods
params = set.recognize_path("/people/5", :method => :put)
assert_equal("update", params[:action])
+ params = set.recognize_path("/people/5", :method => :patch)
+ assert_equal("update", params[:action])
+
assert_raise(ActionController::UnknownHttpMethod) {
set.recognize_path("/people", :method => :bacon)
}
@@ -1059,6 +1064,10 @@ def test_recognize_with_http_methods
assert_equal("update", params[:action])
assert_equal("5", params[:id])
+ params = set.recognize_path("/people/5", :method => :patch)
+ assert_equal("update", params[:action])
+ assert_equal("5", params[:id])
+
params = set.recognize_path("/people/5", :method => :delete)
assert_equal("destroy", params[:action])
assert_equal("5", params[:id])
@@ -1112,6 +1121,7 @@ def test_recognize_with_conditions_and_format
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 @@ def test_recognize_with_conditions_and_format
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])
15 actionpack/test/dispatch/request_test.rb
View
@@ -325,14 +325,14 @@ def url_for(options = {})
end
test "String request methods" do
- [:get, :post, :put, :delete].each do |method|
+ [:get, :post, :patch, :put, :delete].each do |method|
request = stub_request 'REQUEST_METHOD' => method.to_s.upcase
assert_equal method.to_s.upcase, request.method
end
end
test "Symbol forms of request methods via method_symbol" do
- [:get, :post, :put, :delete].each do |method|
+ [:get, :post, :patch, :put, :delete].each do |method|
request = stub_request 'REQUEST_METHOD' => method.to_s.upcase
assert_equal method, request.method_symbol
end
@@ -346,7 +346,7 @@ def url_for(options = {})
end
test "allow method hacking on post" do
- %w(GET OPTIONS PUT POST DELETE).each do |method|
+ %w(GET OPTIONS PATCH PUT POST DELETE).each do |method|
request = stub_request "REQUEST_METHOD" => method.to_s.upcase
assert_equal(method == "HEAD" ? "GET" : method, request.method)
end
@@ -360,7 +360,7 @@ def url_for(options = {})
end
test "restrict method hacking" do
- [:get, :put, :delete].each do |method|
+ [:get, :patch, :put, :delete].each do |method|
request = stub_request 'REQUEST_METHOD' => method.to_s.upcase,
'action_dispatch.request.request_parameters' => { :_method => 'put' }
assert_equal method.to_s.upcase, request.method
@@ -375,6 +375,13 @@ def url_for(options = {})
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
9 actionpack/test/template/form_helper_test.rb
View
@@ -2195,6 +2195,15 @@ def test_form_for_with_existing_object_and_custom_url
assert_equal expected, output_buffer
end
+ def test_form_for_with_default_method_as_patch
+ ActionView::Base.default_method_for_update = :patch
+ form_for(@post) {}
+ expected = whole_form("/posts/123", "edit_post_123", "edit_post", "patch")
+ assert_dom_equal expected, output_buffer
+ ensure
+ ActionView::Base.default_method_for_update = :put
+ end
+
def test_fields_for_returns_block_result
output = fields_for(Post.new) { |f| "fields" }
assert_equal "fields", output
6 actionpack/test/template/form_tag_helper_test.rb
View
@@ -78,6 +78,12 @@ def test_form_tag_multipart
assert_dom_equal expected, actual
end
+ def test_form_tag_with_method_patch
+ actual = form_tag({}, { :method => :patch })
+ expected = whole_form("http://www.example.com", :method => :patch)
+ assert_dom_equal expected, actual
+ end
+
def test_form_tag_with_method_put
actual = form_tag({}, { :method => :put })
expected = whole_form("http://www.example.com", :method => :put)
6 activemodel/lib/active_model/lint.rb
View
@@ -62,9 +62,9 @@ def test_to_partial_path
#
# Returns a boolean that specifies whether the object has been persisted yet.
# This is used when calculating the URL for an object. If the object is
- # not persisted, a form for that object, for instance, will be POSTed to the
- # collection. If it is persisted, a form for the object will be PUT to the
- # URL for the object.
+ # not persisted, a form for that object, for instance, will route to the
+ # create action. If it is persisted, a form for the object will routes to
+ # the update action.
def test_persisted?
assert model.respond_to?(:persisted?), "The model should respond to persisted?"
assert_boolean model.persisted?, "persisted?"
7 activeresource/lib/active_resource/connection.rb
View
@@ -15,6 +15,7 @@ class Connection
HTTP_FORMAT_HEADER_NAMES = { :get => 'Accept',
:put => 'Content-Type',
:post => 'Content-Type',
+ :patch => 'Content-Type',
:delete => 'Accept',
:head => 'Accept'
}
@@ -86,6 +87,12 @@ def delete(path, headers = {})
with_auth { request(:delete, path, build_request_headers(headers, :delete, self.site.merge(path))) }
end
+ # Executes a PATCH request (see HTTP protocol documentation if unfamiliar).
+ # Used to update resources.
+ def patch(path, body = '', headers = {})
+ with_auth { request(:patch, path, body.to_s, build_request_headers(headers, :patch, self.site.merge(path))) }
+ end
+
# Executes a PUT request (see HTTP protocol documentation if unfamiliar).
# Used to update resources.
def put(path, body = '', headers = {})
16 activeresource/lib/active_resource/custom_methods.rb
View
@@ -11,10 +11,10 @@ module ActiveResource
#
# This route set creates routes for the following HTTP requests:
#
- # POST /people/new/register.json # PeopleController.register
- # PUT /people/1/promote.json # PeopleController.promote with :id => 1
- # DELETE /people/1/deactivate.json # PeopleController.deactivate with :id => 1
- # GET /people/active.json # PeopleController.active
+ # POST /people/new/register.json # PeopleController.register
+ # PUT/PATCH /people/1/promote.json # PeopleController.promote with :id => 1
+ # DELETE /people/1/deactivate.json # PeopleController.deactivate with :id => 1
+ # GET /people/active.json # PeopleController.active
#
# Using this module, Active Resource can use these custom REST methods just like the
# standard methods.
@@ -63,6 +63,10 @@ def post(custom_method_name, options = {}, body = '')
connection.post(custom_method_collection_url(custom_method_name, options), body, headers)
end
+ def patch(custom_method_name, options = {}, body = '')
+ connection.patch(custom_method_collection_url(custom_method_name, options), body, headers)
+ end
+
def put(custom_method_name, options = {}, body = '')
connection.put(custom_method_collection_url(custom_method_name, options), body, headers)
end
@@ -98,6 +102,10 @@ def post(method_name, options = {}, body = nil)
end
end
+ def patch(method_name, options = {}, body = '')
+ connection.patch(custom_method_element_url(method_name, options), body, self.class.headers)
+ end
+
def put(method_name, options = {}, body = '')
connection.put(custom_method_element_url(method_name, options), body, self.class.headers)
end
6 activeresource/lib/active_resource/http_mock.rb
View
@@ -15,7 +15,7 @@ class InvalidRequestError < StandardError; end #:nodoc:
#
# mock.http_method(path, request_headers = {}, body = nil, status = 200, response_headers = {})
#
- # * <tt>http_method</tt> - The HTTP method to listen for. This can be +get+, +post+, +put+, +delete+ or
+ # * <tt>http_method</tt> - The HTTP method to listen for. This can be +get+, +post+, +patch+, +put+, +delete+ or
# +head+.
# * <tt>path</tt> - A string, starting with a "/", defining the URI that is expected to be
# called.
@@ -55,7 +55,7 @@ def initialize(responses)
@responses = responses
end
- [ :post, :put, :get, :delete, :head ].each do |method|
+ [ :post, :put, :patch, :get, :delete, :head ].each do |method|
# def post(path, request_headers = {}, body = nil, status = 200, response_headers = {})
# @responses[Request.new(:post, path, nil, request_headers)] = Response.new(body || "", status, response_headers)
# end
@@ -217,7 +217,7 @@ def reset!
end
# body? methods
- { true => %w(post put),
+ { true => %w(post patch put),
false => %w(get delete head) }.each do |has_body, methods|
methods.each do |method|
# def post(path, body, headers)
12 activeresource/test/cases/format_test.rb
View
@@ -11,11 +11,15 @@ def setup
end
def test_http_format_header_name
- header_name = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[:get]
- assert_equal 'Accept', header_name
+ [:get, :head].each do |verb|
+ header_name = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[verb]
+ assert_equal 'Accept', header_name
+ end
- headers_names = [ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[:put], ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[:post]]
- headers_names.each{ |name| assert_equal 'Content-Type', name }
+ [:patch, :put, :post].each do |verb|
+ header_name = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[verb]
+ assert_equal 'Content-Type', header_name
+ end
end
def test_formats_on_single_element
4 activeresource/test/cases/http_mock_test.rb
View
@@ -8,7 +8,7 @@ class HttpMockTest < ActiveSupport::TestCase
FORMAT_HEADER = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES
- [:post, :put, :get, :delete, :head].each do |method|
+ [:post, :patch, :put, :get, :delete, :head].each do |method|
test "responds to simple #{method} request" do
ActiveResource::HttpMock.respond_to do |mock|
mock.send(method, "/people/1", { FORMAT_HEADER[method] => "application/json" }, "Response")
@@ -193,7 +193,7 @@ class HttpMockTest < ActiveSupport::TestCase
end
def request(method, path, headers = {}, body = nil)
- if method.in?([:put, :post])
+ if method.in?([:patch, :put, :post])
@http.send(method, path, body, headers)
else
@http.send(method, path, headers)
2  railties/guides/source/action_controller_overview.textile
View
@@ -563,7 +563,7 @@ The request object contains a lot of useful information about the request coming
|domain(n=2)|The hostname's first +n+ segments, starting from the right (the TLD).|
|format|The content type requested by the client.|
|method|The HTTP method used for the request.|
-|get?, post?, put?, delete?, head?|Returns true if the HTTP method is GET/POST/PUT/DELETE/HEAD.|
+|get?, post?, patch?, put?, delete?, head?|Returns true if the HTTP method is GET/POST/PATCH/PUT/DELETE/HEAD.|
|headers|Returns a hash containing the headers associated with the request.|
|port|The port number (integer) used for the request.|
|protocol|Returns a string containing the protocol used plus "://", for example "http://".|
3  railties/guides/source/ajax_on_rails.textile
View
@@ -146,7 +146,8 @@ link_to_remote "Add new item",
:position => :bottom
</ruby>
-** *:method* Most typically you want to use a POST request when adding a remote link to your view so this is the default behavior. However, sometimes you'll want to update (PUT) or delete/destroy (DELETE) something and you can specify this with the +:method+ option. Let's see an example for a typical AJAX link for deleting an item from a list:
+** *:method* Most typically you want to use a POST request when adding a remote
+link to your view so this is the default behavior. However, sometimes you'll want to update (PUT/PATCH) or delete/destroy (DELETE) something and you can specify this with the +:method+ option. Let's see an example for a typical AJAX link for deleting an item from a list:
<ruby>
link_to_remote "Delete the item",
7 railties/guides/source/configuring.textile
View
@@ -76,6 +76,8 @@ NOTE. The +config.asset_path+ configuration is ignored if the asset pipeline is
* +config.consider_all_requests_local+ is a flag. If true then any error will cause detailed debugging information to be dumped in the HTTP response, and the +Rails::Info+ controller will show the application runtime context in +/rails/info/properties+. True by default in development and test environments, and false in production mode. For finer-grained control, set this to false and implement +local_request?+ in controllers to specify which requests should provide debugging information on errors.
+* +config.default_method_for_update+ tells Rails which HTTP method to use for update actions by default. Set this to +:patch+ for proper HTTP update semantics. The default is +:put+ for backwards compatibility.
+
* +config.dependency_loading+ is a flag that allows you to disable constant autoloading setting it to false. It only has effect if +config.cache_classes+ is true, which it is by default in production mode. This flag is set to false by +config.threadsafe!+.
* +config.eager_load_paths+ accepts an array of paths from which Rails will eager load on boot if cache classes is enabled. Defaults to every folder in the +app+ directory of the application.
@@ -214,7 +216,7 @@ Every Rails application comes with a standard set of middleware which it uses in
* +ActionDispatch::Session::CookieStore+ is responsible for storing the session in cookies. An alternate middleware can be used for this by changing the +config.action_controller.session_store+ to an alternate value. Additionally, options passed to this can be configured by using +config.action_controller.session_options+.
* +ActionDispatch::Flash+ sets up the +flash+ keys. Only available if +config.action_controller.session_store+ is set to a value.
* +ActionDispatch::ParamsParser+ parses out parameters from the request into +params+.
-* +Rack::MethodOverride+ allows the method to be overridden if +params[:_method]+ is set. This is the middleware which supports the PUT and DELETE HTTP method types.
+* +Rack::MethodOverride+ allows the method to be overridden if +params[:_method]+ is set. This is the middleware which supports the PUT, PATCH, and DELETE HTTP method types.
* +ActionDispatch::Head+ converts HEAD requests to GET requests and serves them as so.
* +ActionDispatch::BestStandardsSupport+ enables "best standards support" so that IE8 renders some elements correctly.
@@ -346,7 +348,8 @@ h4. Configuring Action Dispatch
h4. Configuring Action View
-There are only a few configuration options for Action View, starting with four on +ActionView::Base+:
+There are only a few configuration options for Action View, starting with six on +ActionView::Base+:
+
* +config.action_view.field_error_proc+ provides an HTML generator for displaying errors that come from Active Record. The default is
15 railties/guides/source/form_helpers.textile
View
@@ -303,6 +303,11 @@ Rails will also automatically set the +class+ and +id+ of the form appropriately
WARNING: When you're using STI (single-table inheritance) with your models, you can't rely on record identification on a subclass if only their parent class is declared a resource. You will have to specify the model name, +:url+, and +:method+ explicitly.
+NOTE: Rails can use the +PATCH+ method instead of +PUT+ for update forms if you set the following option in your +config/application.rb+:
+<ruby>
+config.default_method_for_update = :patch
+</ruby>
+
h5. Dealing with Namespaces
If you have created namespaced routes, +form_for+ has a nifty shorthand for that too. If your application has an admin namespace then
@@ -320,14 +325,14 @@ form_for [:admin, :management, @article]
For more information on Rails' routing system and the associated conventions, please see the "routing guide":routing.html.
-h4. How do forms with PUT or DELETE methods work?
+h4. How do forms with PATCH, PUT, or DELETE methods work?
-The Rails framework encourages RESTful design of your applications, which means you'll be making a lot of "PUT" and "DELETE" requests (besides "GET" and "POST"). However, most browsers _don't support_ methods other than "GET" and "POST" when it comes to submitting forms.
+The Rails framework encourages RESTful design of your applications, which means you'll be making a lot of "PATCH" and "DELETE" requests (besides "GET" and "POST"). However, most browsers _don't support_ methods other than "GET" and "POST" when it comes to submitting forms.
Rails works around this issue by emulating other methods over POST with a hidden input named +"_method"+, which is set to reflect the desired method:
<ruby>
-form_tag(search_path, :method => "put")
+form_tag(search_path, :method => "patch")
</ruby>
output:
@@ -335,14 +340,14 @@ output:
<html>
<form accept-charset="UTF-8" action="/search" method="post">
<div style="margin:0;padding:0">
- <input name="_method" type="hidden" value="put" />
+ <input name="_method" type="hidden" value="patch" />
<input name="utf8" type="hidden" value="&#x2713;" />
<input name="authenticity_token" type="hidden" value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71" />
</div>
...
</html>
-When parsing POSTed data, Rails will take into account the special +_method+ parameter and acts as if the HTTP method was the one specified inside it ("PUT" in this example).
+When parsing POSTed data, Rails will take into account the special +_method+ parameter and acts as if the HTTP method was the one specified inside it ("PATCH" in this example).
h3. Making Select Boxes with Ease
42 railties/guides/source/routing.textile
View
@@ -50,7 +50,7 @@ Resource routing allows you to quickly declare all of the common routes for a gi
h4. Resources on the Web
-Browsers request pages from Rails by making a request for a URL using a specific HTTP method, such as +GET+, +POST+, +PUT+ and +DELETE+. Each method is a request to perform an operation on the resource. A resource route maps a number of related requests to actions in a single controller.
+Browsers request pages from Rails by making a request for a URL using a specific HTTP method, such as +GET+, +POST+, +PATCH+, +PUT+ and +DELETE+. Each method is a request to perform an operation on the resource. A resource route maps a number of related requests to actions in a single controller.
When your Rails application receives an incoming request for
@@ -82,11 +82,10 @@ creates seven different routes in your application, all mapping to the +Photos+
|POST |/photos |create |create a new photo |
|GET |/photos/:id |show |display a specific photo |
|GET |/photos/:id/edit |edit |return an HTML form for editing a photo |
-|PUT |/photos/:id |update |update a specific photo |
+|PUT/PATCH |/photos/:id |update |update a specific photo |
|DELETE |/photos/:id |destroy |delete a specific photo |
-
-NOTE: Rails routes are matched in the order they are specified, so if you have a +resources :photos+ above a +get 'photos/poll'+ the +show+ action's route for the +resources+ line will be matched before the +get+ line. To fix this, move the +get+ line *above* the +resources+ line so that it is matched first.
+NOTE: The HTTP Verb for the +update+ action defaults to +PUT+ for backwards compatibility. Set +config.default_method_for_update+ to +:patch+ to use +PATCH+. Rails routes are matched in the order they are specified, so if you have a +resources :photos+ above a +get 'photos/poll'+ the +show+ action's route for the +resources+ line will be matched before the +get+ line. To fix this, move the +get+ line *above* the +resources+ line so that it is matched first.
h4. Paths and URLs
@@ -138,10 +137,10 @@ creates six different routes in your application, all mapping to the +Geocoders+
|POST |/geocoder |create |create the new geocoder |
|GET |/geocoder |show |display the one and only geocoder resource |
|GET |/geocoder/edit |edit |return an HTML form for editing the geocoder |
-|PUT |/geocoder |update |update the one and only geocoder resource |
+|PUT/PATCH |/geocoder |update |update the one and only geocoder resource |
|DELETE |/geocoder |destroy |delete the geocoder resource |
-NOTE: Because you might want to use the same controller for a singular route (+/account+) and a plural route (+/accounts/45+), singular resources map to plural controllers.
+NOTE: The HTTP Verb for the +update+ action defaults to +PUT+ for backwards compatibility. Set +config.default_method_for_update+ to +:patch+ to use +PATCH+. Because you might want to use the same controller for a singular route (+/account+) and a plural route (+/accounts/45+), singular resources map to plural controllers.
A singular resourceful route generates these helpers:
@@ -169,9 +168,11 @@ This will create a number of routes for each of the +posts+ and +comments+ contr
|POST |/admin/posts |create | admin_posts_path |
|GET |/admin/posts/:id |show | admin_post_path(:id) |
|GET |/admin/posts/:id/edit |edit | edit_admin_post_path(:id) |
-|PUT |/admin/posts/:id |update | admin_post_path(:id) |
+|PUT/PATCH |/admin/posts/:id |update | admin_post_path(:id) |
|DELETE |/admin/posts/:id |destroy | admin_post_path(:id) |
+NOTE: The HTTP Verb for the +update+ action defaults to +PUT+ for backwards compatibility. Set +config.default_method_for_update+ to +:patch+ to use +PATCH+.
+
If you want to route +/posts+ (without the prefix +/admin+) to +Admin::PostsController+, you could use
<ruby>
@@ -208,9 +209,11 @@ In each of these cases, the named routes remain the same as if you did not use +
|POST |/admin/posts |create | posts_path |
|GET |/admin/posts/:id |show | post_path(:id) |
|GET |/admin/posts/:id/edit|edit | edit_post_path(:id)|
-|PUT |/admin/posts/:id |update | post_path(:id) |
+|PUT/PATCH |/admin/posts/:id |update | post_path(:id) |
|DELETE |/admin/posts/:id |destroy | post_path(:id) |
+NOTE: The HTTP Verb for the +update+ action defaults to +PUT+ for backwards compatibility. Set +config.default_method_for_update+ to +:patch+ to use +PATCH+.
+
h4. Nested Resources
It's common to have resources that are logically children of other resources. For example, suppose your application includes these models:
@@ -235,15 +238,16 @@ end
In addition to the routes for magazines, this declaration will also route ads to an +AdsController+. The ad URLs require a magazine:
-|_.HTTP Verb |_.Path |_.action |_.used for |
-|GET |/magazines/:id/ads |index |display a list of all ads for a specific magazine |
-|GET |/magazines/:id/ads/new |new |return an HTML form for creating a new ad belonging to a specific magazine |
-|POST |/magazines/:id/ads |create |create a new ad belonging to a specific magazine |
+|_.HTTP Verb |_.Path |_.action |_.used for |
+|GET |/magazines/:id/ads |index |display a list of all ads for a specific magazine |
+|GET |/magazines/:id/ads/new |new |return an HTML form for creating a new ad belonging to a specific magazine |
+|POST |/magazines/:id/ads |create |create a new ad belonging to a specific magazine |
|GET |/magazines/:id/ads/:id |show |display a specific ad belonging to a specific magazine |
|GET |/magazines/:id/ads/:id/edit |edit |return an HTML form for editing an ad belonging to a specific magazine |
-|PUT |/magazines/:id/ads/:id |update |update a specific ad belonging to a specific magazine |
+|PUT/PATCH |/magazines/:id/ads/:id |update |update a specific ad belonging to a specific magazine |
|DELETE |/magazines/:id/ads/:id |destroy |delete a specific ad belonging to a specific magazine |
+NOTE: The HTTP Verb for the +update+ action defaults to +PUT+ for backwards compatibility. Set +config.default_method_for_update+ to +:patch+ to use +PATCH+.
This will also create routing helpers such as +magazine_ads_url+ and +edit_magazine_ad_path+. These helpers take an instance of Magazine as the first parameter (+magazine_ads_url(@magazine)+).
@@ -323,7 +327,7 @@ end
This will recognize +/photos/1/preview+ with GET, and route to the +preview+ action of +PhotosController+. It will also create the +preview_photo_url+ and +preview_photo_path+ helpers.
-Within the block of member routes, each route name specifies the HTTP verb that it will recognize. You can use +get+, +put+, +post+, or +delete+ here. If you don't have multiple +member+ routes, you can also pass +:on+ to a route, eliminating the block:
+Within the block of member routes, each route name specifies the HTTP verb that it will recognize. You can use +get+, +patch+, +put+, +post+, or +delete+ here. If you don't have multiple +member+ routes, you can also pass +:on+ to a route, eliminating the block:
<ruby>
resources :photos do
@@ -642,7 +646,7 @@ will recognize incoming paths beginning with +/photos+ but route to the +Images+
|POST |/photos |create | photos_path |
|GET |/photos/:id |show | photo_path(:id) |
|GET |/photos/:id/edit |edit | edit_photo_path(:id) |
-|PUT |/photos/:id |update | photo_path(:id) |
+|PUT/PATCH |/photos/:id |update | photo_path(:id) |
|DELETE |/photos/:id |destroy | photo_path(:id) |
NOTE: Use +photos_path+, +new_photo_path+, etc. to generate paths for this resource.
@@ -686,9 +690,11 @@ will recognize incoming paths beginning with +/photos+ and route the requests to
|POST |/photos |create | images_path |
|GET |/photos/:id |show | image_path(:id) |
|GET |/photos/:id/edit |edit | edit_image_path(:id) |
-|PUT |/photos/:id |update | image_path(:id) |
+|PUT/PATCH |/photos/:id |update | image_path(:id) |
|DELETE |/photos/:id |destroy | image_path(:id) |
+NOTE: The HTTP Verb for the +update+ action defaults to +PUT+ for backwards compatibility. Set +config.default_method_for_update+ to +:patch+ to use +PATCH+.
+
h4. Overriding the +new+ and +edit+ Segments
The +:path_names+ option lets you override the automatically-generated "new" and "edit" segments in paths:
@@ -790,9 +796,11 @@ Rails now creates routes to the +CategoriesController+.
|POST |/kategorien |create | categories_path |
|GET |/kategorien/:id |show | category_path(:id) |
|GET |/kategorien/:id/bearbeiten |edit | edit_category_path(:id) |
-|PUT |/kategorien/:id |update | category_path(:id) |
+|PUT/PATCH |/kategorien/:id |update | category_path(:id) |
|DELETE |/kategorien/:id |destroy | category_path(:id) |
+NOTE: The HTTP Verb for the +update+ action defaults to +PUT+ for backwards compatibility. Set +config.default_method_for_update+ to +:patch+ to use +PATCH+.
+
h4. Overriding the Singular Form
If you want to define the singular form of a resource, you should add additional rules to the +Inflector+.
6 railties/guides/source/testing.textile
View
@@ -483,10 +483,11 @@ Now you can try running all the tests and they should pass.
h4. Available Request Types for Functional Tests
-If you're familiar with the HTTP protocol, you'll know that +get+ is a type of request. There are 5 request types supported in Rails functional tests:
+If you're familiar with the HTTP protocol, you'll know that +get+ is a type of request. There are 6 request types supported in Rails functional tests:
* +get+
* +post+
+* +patch+
* +put+
* +head+
* +delete+
@@ -638,6 +639,7 @@ In addition to the standard testing helpers, there are some additional helpers a
|+request_via_redirect(http_method, path, [parameters], [headers])+ |Allows you to make an HTTP request and follow any subsequent redirects.|
|+post_via_redirect(path, [parameters], [headers])+ |Allows you to make an HTTP POST request and follow any subsequent redirects.|
|+get_via_redirect(path, [parameters], [headers])+ |Allows you to make an HTTP GET request and follow any subsequent redirects.|
+|+patch_via_redirect(path, [parameters], [headers])+ |Allows you to make an HTTP PATCH request and follow any subsequent redirects.|
|+put_via_redirect(path, [parameters], [headers])+ |Allows you to make an HTTP PUT request and follow any subsequent redirects.|
|+delete_via_redirect(path, [parameters], [headers])+ |Allows you to make an HTTP DELETE request and follow any subsequent redirects.|
|+open_session+ |Opens a new session instance.|
@@ -810,7 +812,7 @@ class PostsControllerTest < ActionController::TestCase
end
test "should update post" do
- put :update, :id => @post.id, :post => { }
+ patch :update, :id => @post.id, :post => { }
assert_redirected_to post_path(assigns(:post))
end
3  railties/lib/rails/application/configuration.rb
View
@@ -11,7 +11,7 @@ class Configuration < ::Rails::Engine::Configuration
:force_ssl, :helpers_paths, :logger, :log_tags, :preload_frameworks,
:railties_order, :relative_url_root, :secret_token,
:serve_static_assets, :ssl_options, :static_cache_control, :session_options,
- :time_zone, :reload_classes_only_on_change
+ :time_zone, :reload_classes_only_on_change, :default_method_for_update
attr_writer :log_level
attr_reader :encoding
@@ -40,6 +40,7 @@ def initialize(*)
@reload_classes_only_on_change = true
@file_watcher = ActiveSupport::FileUpdateChecker
@exceptions_app = nil
+ @default_method_for_update = :put
@assets = ActiveSupport::OrderedOptions.new
@assets.enabled = false
6 railties/lib/rails/generators/active_model.rb
View
@@ -37,7 +37,7 @@ def self.all(klass)
# GET show
# GET edit
- # PUT update
+ # PUT/PATCH update
# DELETE destroy
def self.find(klass, params=nil)
"#{klass}.find(#{params})"
@@ -58,13 +58,13 @@ def save
"#{name}.save"
end
- # PUT update
+ # PUT/PATCH update
def update_attributes(params=nil)
"#{name}.update_attributes(#{params})"
end
# POST create
- # PUT update
+ # PUT/PATCH update
def errors
"#{name}.errors"
end
3  railties/lib/rails/generators/rails/app/templates/config/application.rb
View
@@ -31,6 +31,9 @@ class Application < Rails::Application
# Activate observers that should always be running.
# config.active_record.observers = :cacher, :garbage_collector, :forum_observer
+ # Use PATCH as default method for update actions
+ # config.default_method_for_update = :patch
+
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
# config.time_zone = 'Central Time (US & Canada)'
4 railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
View
@@ -54,8 +54,8 @@ def create
end
end
- # PUT <%= route_url %>/1
- # PUT <%= route_url %>/1.json
+ # PUT/PATCH <%= route_url %>/1
+ # PUT/PATCH <%= route_url %>/1.json
def update
@<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
47 railties/test/application/configuration_test.rb
View
@@ -146,7 +146,7 @@ def teardown
test "frameworks are not preloaded by default" do
require "#{app_path}/config/environment"
- assert ActionController.autoload?(:RecordIdentifier)
+ assert ActionController.autoload?(:Caching)
end
test "frameworks are preloaded with config.preload_frameworks is set" do
@@ -156,7 +156,7 @@ def teardown
require "#{app_path}/config/environment"
- assert !ActionController.autoload?(:RecordIdentifier)
+ assert !ActionController.autoload?(:Caching)
end
test "filter_parameters should be able to set via config.filter_parameters" do
@@ -246,6 +246,49 @@ def index
assert last_response.body =~ /csrf\-param/
end
+ test "default method for update can be changed" do
+ app_file 'app/models/post.rb', <<-RUBY
+ class Post
+ extend ActiveModel::Naming
+ def to_key; [1]; end
+ def persisted?; true; end
+ end
+ RUBY
+
+ app_file 'app/controllers/posts_controller.rb', <<-RUBY
+ class PostsController < ApplicationController
+ def show
+ render :inline => "<%= begin; form_for(Post.new) {}; rescue => e; e.to_s; end %>"
+ end
+
+ def update
+ render :text => "update"
+ end
+ end
+ RUBY
+
+ add_to_config <<-RUBY
+ config.default_method_for_update = :patch
+ routes.prepend do
+ resources :posts
+ end
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ assert_equal ActionView::Base.default_method_for_update, :patch
+ assert_equal ActionDispatch::Routing::Mapper.default_method_for_update, :patch
+
+ get "/posts/1"
+ assert_match /patch/, last_response.body
+
+ patch "/posts/1"
+ assert_match /update/, last_response.body
+
+ put "/posts/1"
+ assert_equal 404, last_response.status
+ end
+
test "request forgery token param can be changed" do
make_basic_app do
app.config.action_controller.request_forgery_protection_token = '_xsrf_token_here'
5 railties/test/generators/app_generator_test.rb
View
@@ -325,6 +325,11 @@ def test_no_active_record_or_test_unit_if_skips_given
assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/
end
+ def test_default_method_for_update_is_not_patch
+ run_generator [destination_root, "--skip-test-unit", "--skip-active-record"]
+ assert_file "config/application.rb", /#\s+config\.default_method_for_update\s+=\s+:patch/
+ end
+
def test_new_hash_style
run_generator [destination_root]
assert_file "config/initializers/session_store.rb" do |file|

3 comments on commit 002713c

Rinaldi Fonseca

isn't "HTTP" verb?

Oskar Boethius Lissheim

Great patch, no pun intended.

Please sign in to comment.
Something went wrong with that request. Please try again.