提交 7f2548e3 编写于 作者: X Xavier Noria

Merge pull request #5130 from dlee/revised_patch_verb

Add config.default_method_for_update to support PATCH
......@@ -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'
......
......@@ -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.
......
......@@ -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
......
......@@ -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)
......
......@@ -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?
......
......@@ -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)
......
......@@ -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
......@@ -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?
......@@ -480,6 +482,16 @@ 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>.
#
......@@ -527,7 +539,7 @@ def map_method(method, args, &block)
# POST /admin/posts
# GET /admin/posts/1
# GET /admin/posts/1/edit
# PUT /admin/posts/1
# PUT/PATCH /admin/posts/1
# DELETE /admin/posts/1
#
# If you want to route /posts (without the prefix /admin) to
......@@ -561,7 +573,7 @@ def map_method(method, args, &block)
# POST /admin/posts
# GET /admin/posts/1
# GET /admin/posts/1/edit
# PUT /admin/posts/1
# PUT/PATCH /admin/posts/1
# DELETE /admin/posts/1
module Scoping
# Scopes a set of routes to the given default options.
......@@ -656,7 +668,7 @@ def controller(controller, options={})
# 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 PUT/PATCH /admin/posts/:id(.:format) admin/posts#update
# admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy
#
# === Options
......@@ -976,7 +988,7 @@ def resources_path_names(options)
# POST /geocoder
# GET /geocoder
# GET /geocoder/edit
# PUT /geocoder
# PUT/PATCH /geocoder
# DELETE /geocoder
#
# === Options
......@@ -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
......@@ -1025,7 +1039,7 @@ def resource(*resources, &block)
# POST /photos
# GET /photos/:id
# GET /photos/:id/edit
# PUT /photos/:id
# PUT/PATCH /photos/:id
# DELETE /photos/:id
#
# Resources can also be nested infinitely by using this block syntax:
......@@ -1041,7 +1055,7 @@ def resource(*resources, &block)
# POST /photos/:photo_id/comments
# GET /photos/:photo_id/comments/:id
# GET /photos/:photo_id/comments/:id/edit
# PUT /photos/:photo_id/comments/:id
# PUT/PATCH /photos/:photo_id/comments/:id
# DELETE /photos/:photo_id/comments/:id
#
# === Options
......@@ -1109,7 +1123,7 @@ def resource(*resources, &block)
# 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 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,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
......
......@@ -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 }
......
......@@ -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,
......
......@@ -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') || {}
......
......@@ -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
......
......@@ -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)
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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,12 +654,16 @@ 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
......
......@@ -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])
......
......@@ -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
......
......@@ -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
......
......@@ -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)
......
......@@ -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?"
......
......@@ -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 = {})
......
......@@ -12,7 +12,7 @@ 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
# 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
#
......@@ -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
......
......@@ -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)
......
......@@ -11,11 +11,15 @@ def setup
end
def test_http_format_header_name
header_name = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[:get]
[: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
......
......@@ -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)
......
......@@ -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://".|
......
......@@ -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",
......
......@@ -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
......
......@@ -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
......
......@@ -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:
......@@ -241,9 +244,10 @@ In addition to the routes for magazines, this declaration will also route ads to
|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+.
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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)'
......
......@@ -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]") %>
......
......@@ -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'
......
......@@ -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|
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册