1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Merge pull request #5130 from dlee/revised_patch_verb

Add config.default_method_for_update to support PATCH
This commit is contained in:
Xavier Noria 2012-02-22 09:00:53 -08:00
commit 7f2548e34d
40 changed files with 402 additions and 151 deletions

View file

@ -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'

View file

@ -279,7 +279,7 @@ module ActionController
#
# An implementation might choose not to accept a previously used nonce or a previously used digest, in order to
# protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for
# POST or PUT requests and a time-stamp for GET requests. For more details on the issues involved see Section 4
# POST, PUT, or PATCH requests and a time-stamp for GET requests. For more details on the issues involved see Section 4
# of this document.
#
# The nonce is opaque to the client. Composed of Time, and hash of Time with secret
@ -293,7 +293,7 @@ module ActionController
end
# Might want a shorter timeout depending on whether the request
# is a PUT or POST, and if client is browser or web service.
# is a PATCH, PUT, or POST, and if client is browser or web service.
# Can be much shorter if the Stale directive is implemented. This would
# allow a user to use new nonce without prompting user again for their
# username and password.

View file

@ -53,7 +53,7 @@ module ActionController #:nodoc:
# end
# end
#
# The same happens for PUT and DELETE requests.
# The same happens for PATCH/PUT and DELETE requests.
#
# === Nested resources
#
@ -116,8 +116,9 @@ module ActionController #:nodoc:
class Responder
attr_reader :controller, :request, :format, :resource, :resources, :options
ACTIONS_FOR_VERBS = {
DEFAULT_ACTIONS_FOR_VERBS = {
:post => :new,
:patch => :edit,
:put => :edit
}
@ -132,7 +133,7 @@ module ActionController #:nodoc:
end
delegate :head, :render, :redirect_to, :to => :controller
delegate :get?, :post?, :put?, :delete?, :to => :request
delegate :get?, :post?, :patch?, :put?, :delete?, :to => :request
# Undefine :to_json and :to_yaml since it's defined on Object
undef_method(:to_json) if method_defined?(:to_json)
@ -259,11 +260,11 @@ module ActionController #:nodoc:
resource.respond_to?(:errors) && !resource.errors.empty?
end
# By default, render the <code>:edit</code> action for HTML requests with failure, unless
# the verb is POST.
# By default, render the <code>:edit</code> action for HTML requests with errors, unless
# the verb was POST.
#
def default_action
@action ||= ACTIONS_FOR_VERBS[request.request_method_symbol]
@action ||= DEFAULT_ACTIONS_FOR_VERBS[request.request_method_symbol]
end
def resource_errors

View file

@ -225,7 +225,7 @@ module ActionController
# == Basic example
#
# Functional tests are written as follows:
# 1. First, one uses the +get+, +post+, +put+, +delete+ or +head+ method to simulate
# 1. First, one uses the +get+, +post+, +patch+, +put+, +delete+ or +head+ method to simulate
# an HTTP request.
# 2. Then, one asserts whether the current state is as expected. "State" can be anything:
# the controller's HTTP response, the database contents, etc.
@ -392,6 +392,11 @@ module ActionController
process(action, "POST", *args)
end
# Executes a request simulating PATCH HTTP method and set/volley the response
def patch(action, *args)
process(action, "PATCH", *args)
end
# Executes a request simulating PUT HTTP method and set/volley the response
def put(action, *args)
process(action, "PUT", *args)

View file

@ -97,6 +97,12 @@ module ActionDispatch
HTTP_METHOD_LOOKUP[request_method] == :post
end
# Is this a PATCH request?
# Equivalent to <tt>request.request_method == :patch</tt>.
def patch?
HTTP_METHOD_LOOKUP[request_method] == :patch
end
# Is this a PUT request?
# Equivalent to <tt>request.request_method_symbol == :put</tt>.
def put?

View file

@ -23,6 +23,7 @@ module ActionDispatch
ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length
ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header
ActionDispatch::Response.default_charset = app.config.action_dispatch.default_charset || app.config.encoding
ActionDispatch::Routing::Mapper.default_method_for_update = app.config.default_method_for_update
ActionDispatch::ExceptionWrapper.rescue_responses.merge!(config.action_dispatch.rescue_responses)
ActionDispatch::ExceptionWrapper.rescue_templates.merge!(config.action_dispatch.rescue_templates)

View file

@ -182,10 +182,13 @@ module ActionDispatch
#
# == HTTP Methods
#
# Using the <tt>:via</tt> option when specifying a route allows you to restrict it to a specific HTTP method.
# Possible values are <tt>:post</tt>, <tt>:get</tt>, <tt>:put</tt>, <tt>:delete</tt> and <tt>:any</tt>.
# If your route needs to respond to more than one method you can use an array, e.g. <tt>[ :get, :post ]</tt>.
# The default value is <tt>:any</tt> which means that the route will respond to any of the HTTP methods.
# Using the <tt>:via</tt> option when specifying a route allows you to
# restrict it to a specific HTTP method. Possible values are <tt>:post</tt>,
# <tt>:get</tt>, <tt>:patch</tt>, <tt>:put</tt>, <tt>:delete</tt> and
# <tt>:any</tt>. If your route needs to respond to more than one method you
# can use an array, e.g. <tt>[ :get, :post ]</tt>. The default value is
# <tt>:any</tt> which means that the route will respond to any of the HTTP
# methods.
#
# Examples:
#
@ -198,7 +201,7 @@ module ActionDispatch
# === HTTP helper methods
#
# An alternative method of specifying which HTTP method a route should respond to is to use the helper
# methods <tt>get</tt>, <tt>post</tt>, <tt>put</tt> and <tt>delete</tt>.
# methods <tt>get</tt>, <tt>post</tt>, <tt>patch</tt>, <tt>put</tt> and <tt>delete</tt>.
#
# Examples:
#
@ -283,6 +286,6 @@ module ActionDispatch
autoload :PolymorphicRoutes, 'action_dispatch/routing/polymorphic_routes'
SEPARATORS = %w( / . ? ) #:nodoc:
HTTP_METHODS = [:get, :head, :post, :put, :delete, :options] #:nodoc:
HTTP_METHODS = [:get, :head, :post, :patch, :put, :delete, :options] #:nodoc:
end
end

View file

@ -7,6 +7,8 @@ require 'action_dispatch/routing/redirection'
module ActionDispatch
module Routing
class Mapper
cattr_accessor(:default_method_for_update) {:put}
class Constraints #:nodoc:
def self.new(app, constraints, request = Rack::Request)
if constraints.any?
@ -465,7 +467,7 @@ module ActionDispatch
#
# Example:
#
# get 'bacon', :to => 'food#bacon'
# get 'bacon', :to => 'food#bacon'
def get(*args, &block)
map_method(:get, args, &block)
end
@ -475,17 +477,27 @@ module ActionDispatch
#
# Example:
#
# post 'bacon', :to => 'food#bacon'
# post 'bacon', :to => 'food#bacon'
def post(*args, &block)
map_method(:post, args, &block)
end
# Define a route that only recognizes HTTP PATCH.
# For supported arguments, see <tt>Base#match</tt>.
#
# Example:
#
# patch 'bacon', :to => 'food#bacon'
def patch(*args, &block)
map_method(:patch, args, &block)
end
# Define a route that only recognizes HTTP PUT.
# For supported arguments, see <tt>Base#match</tt>.
#
# Example:
#
# put 'bacon', :to => 'food#bacon'
# put 'bacon', :to => 'food#bacon'
def put(*args, &block)
map_method(:put, args, &block)
end
@ -495,7 +507,7 @@ module ActionDispatch
#
# Example:
#
# delete 'broccoli', :to => 'food#broccoli'
# delete 'broccoli', :to => 'food#broccoli'
def delete(*args, &block)
map_method(:delete, args, &block)
end
@ -522,13 +534,13 @@ module ActionDispatch
# This will create a number of routes for each of the posts and comments
# controller. For <tt>Admin::PostsController</tt>, Rails will create:
#
# GET /admin/posts
# GET /admin/posts/new
# POST /admin/posts
# GET /admin/posts/1
# GET /admin/posts/1/edit
# PUT /admin/posts/1
# DELETE /admin/posts/1
# GET /admin/posts
# GET /admin/posts/new
# POST /admin/posts
# GET /admin/posts/1
# GET /admin/posts/1/edit
# PUT/PATCH /admin/posts/1
# DELETE /admin/posts/1
#
# If you want to route /posts (without the prefix /admin) to
# <tt>Admin::PostsController</tt>, you could use
@ -556,13 +568,13 @@ module ActionDispatch
# not use scope. In the last case, the following paths map to
# +PostsController+:
#
# GET /admin/posts
# GET /admin/posts/new
# POST /admin/posts
# GET /admin/posts/1
# GET /admin/posts/1/edit
# PUT /admin/posts/1
# DELETE /admin/posts/1
# GET /admin/posts
# GET /admin/posts/new
# POST /admin/posts
# GET /admin/posts/1
# GET /admin/posts/1/edit
# PUT/PATCH /admin/posts/1
# DELETE /admin/posts/1
module Scoping
# Scopes a set of routes to the given default options.
#
@ -651,13 +663,13 @@ module ActionDispatch
#
# This generates the following routes:
#
# admin_posts GET /admin/posts(.:format) admin/posts#index
# admin_posts POST /admin/posts(.:format) admin/posts#create
# new_admin_post GET /admin/posts/new(.:format) admin/posts#new
# edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit
# admin_post GET /admin/posts/:id(.:format) admin/posts#show
# admin_post PUT /admin/posts/:id(.:format) admin/posts#update
# admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy
# admin_posts GET /admin/posts(.:format) admin/posts#index
# admin_posts POST /admin/posts(.:format) admin/posts#create
# new_admin_post GET /admin/posts/new(.:format) admin/posts#new
# edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit
# admin_post GET /admin/posts/:id(.:format) admin/posts#show
# admin_post PUT/PATCH /admin/posts/:id(.:format) admin/posts#update
# admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy
#
# === Options
#
@ -972,12 +984,12 @@ module ActionDispatch
# the +GeoCoders+ controller (note that the controller is named after
# the plural):
#
# GET /geocoder/new
# POST /geocoder
# GET /geocoder
# GET /geocoder/edit
# PUT /geocoder
# DELETE /geocoder
# GET /geocoder/new
# POST /geocoder
# GET /geocoder
# GET /geocoder/edit
# PUT/PATCH /geocoder
# DELETE /geocoder
#
# === Options
# Takes same options as +resources+.
@ -1002,8 +1014,10 @@ module ActionDispatch
member do
get :edit if parent_resource.actions.include?(:edit)
get :show if parent_resource.actions.include?(:show)
put :update if parent_resource.actions.include?(:update)
delete :destroy if parent_resource.actions.include?(:destroy)
if parent_resource.actions.include?(:update)
send default_method_for_update, :update
end
end
end
@ -1020,13 +1034,13 @@ module ActionDispatch
# creates seven different routes in your application, all mapping to
# the +Photos+ controller:
#
# GET /photos
# GET /photos/new
# POST /photos
# GET /photos/:id
# GET /photos/:id/edit
# PUT /photos/:id
# DELETE /photos/:id
# GET /photos
# GET /photos/new
# POST /photos
# GET /photos/:id
# GET /photos/:id/edit
# PUT/PATCH /photos/:id
# DELETE /photos/:id
#
# Resources can also be nested infinitely by using this block syntax:
#
@ -1036,13 +1050,13 @@ module ActionDispatch
#
# This generates the following comments routes:
#
# GET /photos/:photo_id/comments
# GET /photos/:photo_id/comments/new
# POST /photos/:photo_id/comments
# GET /photos/:photo_id/comments/:id
# GET /photos/:photo_id/comments/:id/edit
# PUT /photos/:photo_id/comments/:id
# DELETE /photos/:photo_id/comments/:id
# GET /photos/:photo_id/comments
# GET /photos/:photo_id/comments/new
# POST /photos/:photo_id/comments
# GET /photos/:photo_id/comments/:id
# GET /photos/:photo_id/comments/:id/edit
# PUT/PATCH /photos/:photo_id/comments/:id
# DELETE /photos/:photo_id/comments/:id
#
# === Options
# Takes same options as <tt>Base#match</tt> as well as:
@ -1104,13 +1118,13 @@ module ActionDispatch
#
# The +comments+ resource here will have the following routes generated for it:
#
# post_comments GET /posts/:post_id/comments(.:format)
# post_comments POST /posts/:post_id/comments(.:format)
# new_post_comment GET /posts/:post_id/comments/new(.:format)
# edit_comment GET /sekret/comments/:id/edit(.:format)
# comment GET /sekret/comments/:id(.:format)
# comment PUT /sekret/comments/:id(.:format)
# comment DELETE /sekret/comments/:id(.:format)
# post_comments GET /posts/:post_id/comments(.:format)
# post_comments POST /posts/:post_id/comments(.:format)
# new_post_comment GET /posts/:post_id/comments/new(.:format)
# edit_comment GET /sekret/comments/:id/edit(.:format)
# comment GET /sekret/comments/:id(.:format)
# comment PUT/PATCH /sekret/comments/:id(.:format)
# comment DELETE /sekret/comments/:id(.:format)
#
# === Examples
#
@ -1138,11 +1152,14 @@ module ActionDispatch
get :new
end if parent_resource.actions.include?(:new)
# TODO: Only accept patch or put depending on config
member do
get :edit if parent_resource.actions.include?(:edit)
get :show if parent_resource.actions.include?(:show)
put :update if parent_resource.actions.include?(:update)
delete :destroy if parent_resource.actions.include?(:destroy)
if parent_resource.actions.include?(:update)
send default_method_for_update, :update
end
end
end

View file

@ -26,8 +26,8 @@ module ActionDispatch
# object's <tt>@response</tt> instance variable will point to the same
# response object.
#
# You can also perform POST, PUT, DELETE, and HEAD requests with +#post+,
# +#put+, +#delete+, and +#head+.
# You can also perform POST, PATCH, PUT, DELETE, and HEAD requests with
# +#post+, +#patch+, +#put+, +#delete+, and +#head+.
def get(path, parameters = nil, headers = nil)
process :get, path, parameters, headers
end
@ -38,6 +38,12 @@ module ActionDispatch
process :post, path, parameters, headers
end
# Performs a PATCH request with the given parameters. See +#get+ for more
# details.
def patch(path, parameters = nil, headers = nil)
process :patch, path, parameters, headers
end
# Performs a PUT request with the given parameters. See +#get+ for more
# details.
def put(path, parameters = nil, headers = nil)
@ -65,10 +71,10 @@ module ActionDispatch
# Performs an XMLHttpRequest request with the given parameters, mirroring
# a request from the Prototype library.
#
# The request_method is +:get+, +:post+, +:put+, +:delete+ or +:head+; the
# parameters are +nil+, a hash, or a url-encoded or multipart string;
# the headers are a hash. Keys are automatically upcased and prefixed
# with 'HTTP_' if not already.
# The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or
# +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart
# string; the headers are a hash. Keys are automatically upcased and
# prefixed with 'HTTP_' if not already.
def xml_http_request(request_method, path, parameters = nil, headers = nil)
headers ||= {}
headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
@ -108,6 +114,12 @@ module ActionDispatch
request_via_redirect(:post, path, parameters, headers)
end
# Performs a PATCH request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
def patch_via_redirect(path, parameters = nil, headers = nil)
request_via_redirect(:patch, path, parameters, headers)
end
# Performs a PUT request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
def put_via_redirect(path, parameters = nil, headers = nil)
@ -318,7 +330,7 @@ module ActionDispatch
@integration_session = Integration::Session.new(app)
end
%w(get post put head delete options cookies assigns
%w(get post put patch head delete options cookies assigns
xml_http_request xhr get_via_redirect post_via_redirect).each do |method|
define_method(method) do |*args|
reset! unless integration_session

View file

@ -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 }

View file

@ -250,7 +250,7 @@ module ActionView
#
# You can force the form to use the full array of HTTP verbs by setting
#
# :method => (:get|:post|:put|:delete)
# :method => (:get|:post|:patch|:put|:delete)
#
# in the options hash. If the verb is not GET or POST, which are natively supported by HTML forms, the
# form will be set to POST and a hidden input called _method will carry the intended verb for the server
@ -385,7 +385,7 @@ module ActionView
object = convert_to_model(object)
as = options[:as]
action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :put] : [:new, :post]
action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, ActionView::Base.default_method_for_update] : [:new, :post]
options[:html].reverse_merge!(
:class => as ? "#{action}_#{as}" : dom_class(object, action),
:id => as ? "#{action}_#{as}" : [options[:namespace], dom_id(object, action)].compact.join("_").presence,

View file

@ -146,12 +146,12 @@ module ActionView
# create an HTML form and immediately submit the form for processing using
# the HTTP verb specified. Useful for having links perform a POST operation
# in dangerous actions like deleting a record (which search bots can follow
# while spidering your site). Supported verbs are <tt>:post</tt>, <tt>:delete</tt> and <tt>:put</tt>.
# while spidering your site). Supported verbs are <tt>:post</tt>, <tt>:delete</tt>, <tt>:patch</tt>, and <tt>:put</tt>.
# Note that if the user has JavaScript disabled, the request will fall back
# to using GET. If <tt>:href => '#'</tt> is used and the user has JavaScript
# disabled clicking the link will have no effect. If you are relying on the
# POST behavior, you should check for it in your controller's action by using
# the request object's methods for <tt>post?</tt>, <tt>delete?</tt> or <tt>put?</tt>.
# the request object's methods for <tt>post?</tt>, <tt>delete?</tt>, <tt>:patch</tt>, or <tt>put?</tt>.
# * <tt>:remote => true</tt> - This will allow the unobtrusive JavaScript
# driver to make an Ajax request to the URL in question instead of following
# the link. The drivers each provide mechanisms for listening for the
@ -272,7 +272,7 @@ module ActionView
#
# There are a few special +html_options+:
# * <tt>:method</tt> - Symbol of HTTP verb. Supported verbs are <tt>:post</tt>, <tt>:get</tt>,
# <tt>:delete</tt> and <tt>:put</tt>. By default it will be <tt>:post</tt>.
# <tt>:delete</tt>, <tt>:patch</tt>, and <tt>:put</tt>. By default it will be <tt>:post</tt>.
# * <tt>:disabled</tt> - If set to true, it will generate a disabled button.
# * <tt>:confirm</tt> - This will use the unobtrusive JavaScript driver to
# prompt with the question specified. If the user accepts, the link is
@ -329,7 +329,7 @@ module ActionView
remote = html_options.delete('remote')
method = html_options.delete('method').to_s
method_tag = %w{put delete}.include?(method) ? method_tag(method) : ""
method_tag = %w{put patch delete}.include?(method) ? method_tag(method) : ""
form_method = method == 'get' ? 'get' : 'post'
form_options = html_options.delete('form') || {}

View file

@ -33,6 +33,7 @@ module ActionView
end
initializer "action_view.set_configs" do |app|
ActionView::Base.default_method_for_update = app.config.default_method_for_update
ActiveSupport.on_load(:action_view) do
app.config.action_view.each do |k,v|
send "#{k}=", v

View file

@ -180,7 +180,7 @@ class PageCachingTest < ActionController::TestCase
end
[:ok, :no_content, :found, :not_found].each do |status|
[:get, :post, :put, :delete].each do |method|
[:get, :post, :patch, :put, :delete].each do |method|
unless method == :get && status == :ok
define_method "test_shouldnt_cache_#{method}_with_#{status}_status" do
send(method, status)

View file

@ -63,6 +63,12 @@ class SessionTest < ActiveSupport::TestCase
@session.post_via_redirect(path, args, headers)
end
def test_patch_via_redirect
path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" }
@session.expects(:request_via_redirect).with(:patch, path, args, headers)
@session.patch_via_redirect(path, args, headers)
end
def test_put_via_redirect
path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" }
@session.expects(:request_via_redirect).with(:put, path, args, headers)
@ -87,6 +93,12 @@ class SessionTest < ActiveSupport::TestCase
@session.post(path,params,headers)
end
def test_patch
path = "/index"; params = "blah"; headers = {:location => 'blah'}
@session.expects(:process).with(:patch,path,params,headers)
@session.patch(path,params,headers)
end
def test_put
path = "/index"; params = "blah"; headers = {:location => 'blah'}
@session.expects(:process).with(:put,path,params,headers)
@ -131,6 +143,16 @@ class SessionTest < ActiveSupport::TestCase
@session.xml_http_request(:post,path,params,headers)
end
def test_xml_http_request_patch
path = "/index"; params = "blah"; headers = {:location => 'blah'}
headers_after_xhr = headers.merge(
"HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
"HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*"
)
@session.expects(:process).with(:patch,path,params,headers_after_xhr)
@session.xml_http_request(:patch,path,params,headers)
end
def test_xml_http_request_put
path = "/index"; params = "blah"; headers = {:location => 'blah'}
headers_after_xhr = headers.merge(
@ -228,7 +250,7 @@ class IntegrationTestUsesCorrectClass < ActionDispatch::IntegrationTest
@integration_session.stubs(:generic_url_rewriter)
@integration_session.stubs(:process)
%w( get post head put delete options ).each do |verb|
%w( get post head patch put delete options ).each do |verb|
assert_nothing_raised("'#{verb}' should use integration test methods") { __send__(verb, '/') }
end
end

View file

@ -770,6 +770,41 @@ class RespondWithControllerTest < ActionController::TestCase
end
end
def test_using_resource_for_patch_with_html_redirects_on_success
with_test_route_set do
patch :using_resource
assert_equal "text/html", @response.content_type
assert_equal 302, @response.status
assert_equal "http://www.example.com/customers/13", @response.location
assert @response.redirect?
end
end
def test_using_resource_for_patch_with_html_rerender_on_failure
with_test_route_set do
errors = { :name => :invalid }
Customer.any_instance.stubs(:errors).returns(errors)
patch :using_resource
assert_equal "text/html", @response.content_type
assert_equal 200, @response.status
assert_equal "Edit world!\n", @response.body
assert_nil @response.location
end
end
def test_using_resource_for_patch_with_html_rerender_on_failure_even_on_method_override
with_test_route_set do
errors = { :name => :invalid }
Customer.any_instance.stubs(:errors).returns(errors)
@request.env["rack.methodoverride.original_method"] = "POST"
patch :using_resource
assert_equal "text/html", @response.content_type
assert_equal 200, @response.status
assert_equal "Edit world!\n", @response.body
assert_nil @response.location
end
end
def test_using_resource_for_put_with_html_redirects_on_success
with_test_route_set do
put :using_resource

View file

@ -114,6 +114,10 @@ module RequestForgeryProtectionTests
assert_blocked { post :index, :format=>'xml' }
end
def test_should_not_allow_patch_without_token
assert_blocked { patch :index }
end
def test_should_not_allow_put_without_token
assert_blocked { put :index }
end
@ -130,6 +134,10 @@ module RequestForgeryProtectionTests
assert_not_blocked { post :index, :custom_authenticity_token => @token }
end
def test_should_allow_patch_with_token
assert_not_blocked { patch :index, :custom_authenticity_token => @token }
end
def test_should_allow_put_with_token
assert_not_blocked { put :index, :custom_authenticity_token => @token }
end
@ -148,6 +156,11 @@ module RequestForgeryProtectionTests
assert_not_blocked { delete :index }
end
def test_should_allow_patch_with_token_in_header
@request.env['HTTP_X_CSRF_TOKEN'] = @token
assert_not_blocked { patch :index }
end
def test_should_allow_put_with_token_in_header
@request.env['HTTP_X_CSRF_TOKEN'] = @token
assert_not_blocked { put :index }
@ -232,7 +245,7 @@ class FreeCookieControllerTest < ActionController::TestCase
end
def test_should_allow_all_methods_without_token
[:post, :put, :delete].each do |method|
[:post, :patch, :put, :delete].each do |method|
assert_nothing_raised { send(method, :index)}
end
end

View file

@ -158,7 +158,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_with_collection_actions
actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete }
actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete, 'e' => :patch }
with_routing do |set|
set.draw do
@ -167,6 +167,7 @@ class ResourcesTest < ActionController::TestCase
put :b, :on => :collection
post :c, :on => :collection
delete :d, :on => :collection
patch :e, :on => :collection
end
end
@ -185,7 +186,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_with_collection_actions_and_name_prefix
actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete }
actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete, 'e' => :patch }
with_routing do |set|
set.draw do
@ -195,6 +196,7 @@ class ResourcesTest < ActionController::TestCase
put :b, :on => :collection
post :c, :on => :collection
delete :d, :on => :collection
patch :e, :on => :collection
end
end
end
@ -241,7 +243,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_with_collection_action_and_name_prefix_and_formatted
actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete }
actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete, 'e' => :patch }
with_routing do |set|
set.draw do
@ -251,6 +253,7 @@ class ResourcesTest < ActionController::TestCase
put :b, :on => :collection
post :c, :on => :collection
delete :d, :on => :collection
patch :e, :on => :collection
end
end
end
@ -270,7 +273,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_with_member_action
[:put, :post].each do |method|
[:patch, :put, :post].each do |method|
with_restful_routing :messages, :member => { :mark => method } do
mark_options = {:action => 'mark', :id => '1'}
mark_path = "/messages/1/mark"
@ -294,7 +297,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_member_when_override_paths_for_default_restful_actions_with
[:put, :post].each do |method|
[:patch, :put, :post].each do |method|
with_restful_routing :messages, :member => { :mark => method }, :path_names => {:new => 'nuevo'} do
mark_options = {:action => 'mark', :id => '1', :controller => "messages"}
mark_path = "/messages/1/mark"
@ -311,7 +314,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_with_two_member_actions_with_same_method
[:put, :post].each do |method|
[:patch, :put, :post].each do |method|
with_routing do |set|
set.draw do
resources :messages do
@ -564,7 +567,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_singleton_resource_with_member_action
[:put, :post].each do |method|
[:patch, :put, :post].each do |method|
with_routing do |set|
set.draw do
resource :account do
@ -586,7 +589,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_singleton_resource_with_two_member_actions_with_same_method
[:put, :post].each do |method|
[:patch, :put, :post].each do |method|
with_routing do |set|
set.draw do
resource :account do
@ -651,12 +654,16 @@ class ResourcesTest < ActionController::TestCase
end
end
def test_should_not_allow_delete_or_put_on_collection_path
def test_should_not_allow_delete_or_patch_or_put_on_collection_path
controller_name = :messages
with_restful_routing controller_name do
options = { :controller => controller_name.to_s }
collection_path = "/#{controller_name}"
assert_raise(ActionController::RoutingError) do
assert_recognizes(options.merge(:action => 'update'), :path => collection_path, :method => :patch)
end
assert_raise(ActionController::RoutingError) do
assert_recognizes(options.merge(:action => 'update'), :path => collection_path, :method => :put)
end

View file

@ -648,11 +648,12 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
match '/match' => 'books#get', :via => :get
match '/match' => 'books#post', :via => :post
match '/match' => 'books#put', :via => :put
match '/match' => 'books#patch', :via => :patch
match '/match' => 'books#delete', :via => :delete
end
end
%w(GET POST PUT DELETE).each do |request_method|
%w(GET PATCH POST PUT DELETE).each do |request_method|
define_method("test_request_method_recognized_with_#{request_method}") do
setup_request_method_routes_for(request_method)
params = rs.recognize_path("/match", :method => request_method)
@ -1035,6 +1036,7 @@ class RouteSetTest < ActiveSupport::TestCase
post "/people" => "people#create"
get "/people/:id" => "people#show", :as => "person"
put "/people/:id" => "people#update"
patch "/people/:id" => "people#update"
delete "/people/:id" => "people#destroy"
end
@ -1047,6 +1049,9 @@ class RouteSetTest < ActiveSupport::TestCase
params = set.recognize_path("/people/5", :method => :put)
assert_equal("update", params[:action])
params = set.recognize_path("/people/5", :method => :patch)
assert_equal("update", params[:action])
assert_raise(ActionController::UnknownHttpMethod) {
set.recognize_path("/people", :method => :bacon)
}
@ -1059,6 +1064,10 @@ class RouteSetTest < ActiveSupport::TestCase
assert_equal("update", params[:action])
assert_equal("5", params[:id])
params = set.recognize_path("/people/5", :method => :patch)
assert_equal("update", params[:action])
assert_equal("5", params[:id])
params = set.recognize_path("/people/5", :method => :delete)
assert_equal("destroy", params[:action])
assert_equal("5", params[:id])
@ -1112,6 +1121,7 @@ class RouteSetTest < ActiveSupport::TestCase
set.draw do
get "people/:id" => "people#show", :as => "person"
put "people/:id" => "people#update"
patch "people/:id" => "people#update"
get "people/:id(.:format)" => "people#show"
end
@ -1122,6 +1132,9 @@ class RouteSetTest < ActiveSupport::TestCase
params = set.recognize_path("/people/5", :method => :put)
assert_equal("update", params[:action])
params = set.recognize_path("/people/5", :method => :patch)
assert_equal("update", params[:action])
params = set.recognize_path("/people/5.png", :method => :get)
assert_equal("show", params[:action])
assert_equal("5", params[:id])

View file

@ -325,14 +325,14 @@ class RequestTest < ActiveSupport::TestCase
end
test "String request methods" do
[:get, :post, :put, :delete].each do |method|
[:get, :post, :patch, :put, :delete].each do |method|
request = stub_request 'REQUEST_METHOD' => method.to_s.upcase
assert_equal method.to_s.upcase, request.method
end
end
test "Symbol forms of request methods via method_symbol" do
[:get, :post, :put, :delete].each do |method|
[:get, :post, :patch, :put, :delete].each do |method|
request = stub_request 'REQUEST_METHOD' => method.to_s.upcase
assert_equal method, request.method_symbol
end
@ -346,7 +346,7 @@ class RequestTest < ActiveSupport::TestCase
end
test "allow method hacking on post" do
%w(GET OPTIONS PUT POST DELETE).each do |method|
%w(GET OPTIONS PATCH PUT POST DELETE).each do |method|
request = stub_request "REQUEST_METHOD" => method.to_s.upcase
assert_equal(method == "HEAD" ? "GET" : method, request.method)
end
@ -360,7 +360,7 @@ class RequestTest < ActiveSupport::TestCase
end
test "restrict method hacking" do
[:get, :put, :delete].each do |method|
[:get, :patch, :put, :delete].each do |method|
request = stub_request 'REQUEST_METHOD' => method.to_s.upcase,
'action_dispatch.request.request_parameters' => { :_method => 'put' }
assert_equal method.to_s.upcase, request.method
@ -375,6 +375,13 @@ class RequestTest < ActiveSupport::TestCase
assert request.head?
end
test "post masquerading as patch" do
request = stub_request 'REQUEST_METHOD' => 'PATCH', "rack.methodoverride.original_method" => "POST"
assert_equal "POST", request.method
assert_equal "PATCH", request.request_method
assert request.patch?
end
test "post masquerading as put" do
request = stub_request 'REQUEST_METHOD' => 'PUT', "rack.methodoverride.original_method" => "POST"
assert_equal "POST", request.method

View file

@ -2195,6 +2195,15 @@ class FormHelperTest < ActionView::TestCase
assert_equal expected, output_buffer
end
def test_form_for_with_default_method_as_patch
ActionView::Base.default_method_for_update = :patch
form_for(@post) {}
expected = whole_form("/posts/123", "edit_post_123", "edit_post", "patch")
assert_dom_equal expected, output_buffer
ensure
ActionView::Base.default_method_for_update = :put
end
def test_fields_for_returns_block_result
output = fields_for(Post.new) { |f| "fields" }
assert_equal "fields", output

View file

@ -78,6 +78,12 @@ class FormTagHelperTest < ActionView::TestCase
assert_dom_equal expected, actual
end
def test_form_tag_with_method_patch
actual = form_tag({}, { :method => :patch })
expected = whole_form("http://www.example.com", :method => :patch)
assert_dom_equal expected, actual
end
def test_form_tag_with_method_put
actual = form_tag({}, { :method => :put })
expected = whole_form("http://www.example.com", :method => :put)

View file

@ -62,9 +62,9 @@ module ActiveModel
#
# Returns a boolean that specifies whether the object has been persisted yet.
# This is used when calculating the URL for an object. If the object is
# not persisted, a form for that object, for instance, will be POSTed to the
# collection. If it is persisted, a form for the object will be PUT to the
# URL for the object.
# not persisted, a form for that object, for instance, will route to the
# create action. If it is persisted, a form for the object will routes to
# the update action.
def test_persisted?
assert model.respond_to?(:persisted?), "The model should respond to persisted?"
assert_boolean model.persisted?, "persisted?"

View file

@ -15,6 +15,7 @@ module ActiveResource
HTTP_FORMAT_HEADER_NAMES = { :get => 'Accept',
:put => 'Content-Type',
:post => 'Content-Type',
:patch => 'Content-Type',
:delete => 'Accept',
:head => 'Accept'
}
@ -86,6 +87,12 @@ module ActiveResource
with_auth { request(:delete, path, build_request_headers(headers, :delete, self.site.merge(path))) }
end
# Executes a PATCH request (see HTTP protocol documentation if unfamiliar).
# Used to update resources.
def patch(path, body = '', headers = {})
with_auth { request(:patch, path, body.to_s, build_request_headers(headers, :patch, self.site.merge(path))) }
end
# Executes a PUT request (see HTTP protocol documentation if unfamiliar).
# Used to update resources.
def put(path, body = '', headers = {})

View file

@ -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 @@ module ActiveResource
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 @@ module ActiveResource
end
end
def patch(method_name, options = {}, body = '')
connection.patch(custom_method_element_url(method_name, options), body, self.class.headers)
end
def put(method_name, options = {}, body = '')
connection.put(custom_method_element_url(method_name, options), body, self.class.headers)
end

View file

@ -15,7 +15,7 @@ module ActiveResource
#
# 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 @@ module ActiveResource
@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 @@ module ActiveResource
end
# body? methods
{ true => %w(post put),
{ true => %w(post patch put),
false => %w(get delete head) }.each do |has_body, methods|
methods.each do |method|
# def post(path, body, headers)

View file

@ -11,11 +11,15 @@ class FormatTest < ActiveSupport::TestCase
end
def test_http_format_header_name
header_name = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[:get]
assert_equal 'Accept', header_name
[:get, :head].each do |verb|
header_name = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[verb]
assert_equal 'Accept', header_name
end
headers_names = [ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[:put], ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[:post]]
headers_names.each{ |name| assert_equal 'Content-Type', name }
[:patch, :put, :post].each do |verb|
header_name = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[verb]
assert_equal 'Content-Type', header_name
end
end
def test_formats_on_single_element

View file

@ -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)

View file

@ -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://".|

View file

@ -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",

View file

@ -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

View file

@ -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

View file

@ -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+.

View file

@ -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

View file

@ -11,7 +11,7 @@ module Rails
: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 @@ module Rails
@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

View file

@ -37,7 +37,7 @@ module Rails
# GET show
# GET edit
# PUT update
# PUT/PATCH update
# DELETE destroy
def self.find(klass, params=nil)
"#{klass}.find(#{params})"
@ -58,13 +58,13 @@ module Rails
"#{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

View file

@ -31,6 +31,9 @@ module <%= app_const_base %>
# 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)'

View file

@ -54,8 +54,8 @@ class <%= controller_class_name %>Controller < ApplicationController
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]") %>

View file

@ -146,7 +146,7 @@ module ApplicationTests
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 @@ module ApplicationTests
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 @@ module ApplicationTests
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'

View file

@ -325,6 +325,11 @@ class AppGeneratorTest < Rails::Generators::TestCase
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|