提交 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 ...@@ -8,6 +8,7 @@ else
gem 'arel' gem 'arel'
end end
gem 'rack-test', :git => "https://github.com/brynary/rack-test.git"
gem 'bcrypt-ruby', '~> 3.0.0' gem 'bcrypt-ruby', '~> 3.0.0'
gem 'jquery-rails' gem 'jquery-rails'
......
...@@ -279,7 +279,7 @@ def secret_token(request) ...@@ -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 # 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 # 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. # of this document.
# #
# The nonce is opaque to the client. Composed of Time, and hash of Time with secret # The nonce is opaque to the client. Composed of Time, and hash of Time with secret
...@@ -293,7 +293,7 @@ def nonce(secret_key, time = Time.now) ...@@ -293,7 +293,7 @@ def nonce(secret_key, time = Time.now)
end end
# Might want a shorter timeout depending on whether the request # 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 # 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 # allow a user to use new nonce without prompting user again for their
# username and password. # username and password.
......
...@@ -53,7 +53,7 @@ module ActionController #:nodoc: ...@@ -53,7 +53,7 @@ module ActionController #:nodoc:
# end # end
# end # end
# #
# The same happens for PUT and DELETE requests. # The same happens for PATCH/PUT and DELETE requests.
# #
# === Nested resources # === Nested resources
# #
...@@ -116,8 +116,9 @@ module ActionController #:nodoc: ...@@ -116,8 +116,9 @@ module ActionController #:nodoc:
class Responder class Responder
attr_reader :controller, :request, :format, :resource, :resources, :options attr_reader :controller, :request, :format, :resource, :resources, :options
ACTIONS_FOR_VERBS = { DEFAULT_ACTIONS_FOR_VERBS = {
:post => :new, :post => :new,
:patch => :edit,
:put => :edit :put => :edit
} }
...@@ -132,7 +133,7 @@ def initialize(controller, resources, options={}) ...@@ -132,7 +133,7 @@ def initialize(controller, resources, options={})
end end
delegate :head, :render, :redirect_to, :to => :controller 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 # Undefine :to_json and :to_yaml since it's defined on Object
undef_method(:to_json) if method_defined?(:to_json) undef_method(:to_json) if method_defined?(:to_json)
...@@ -259,11 +260,11 @@ def has_errors? ...@@ -259,11 +260,11 @@ def has_errors?
resource.respond_to?(:errors) && !resource.errors.empty? resource.respond_to?(:errors) && !resource.errors.empty?
end end
# By default, render the <code>:edit</code> action for HTML requests with failure, unless # By default, render the <code>:edit</code> action for HTML requests with errors, unless
# the verb is POST. # the verb was POST.
# #
def default_action def default_action
@action ||= ACTIONS_FOR_VERBS[request.request_method_symbol] @action ||= DEFAULT_ACTIONS_FOR_VERBS[request.request_method_symbol]
end end
def resource_errors def resource_errors
......
...@@ -225,7 +225,7 @@ def exists? ...@@ -225,7 +225,7 @@ def exists?
# == Basic example # == Basic example
# #
# Functional tests are written as follows: # 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. # an HTTP request.
# 2. Then, one asserts whether the current state is as expected. "State" can be anything: # 2. Then, one asserts whether the current state is as expected. "State" can be anything:
# the controller's HTTP response, the database contents, etc. # the controller's HTTP response, the database contents, etc.
...@@ -392,6 +392,11 @@ def post(action, *args) ...@@ -392,6 +392,11 @@ def post(action, *args)
process(action, "POST", *args) process(action, "POST", *args)
end 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 # Executes a request simulating PUT HTTP method and set/volley the response
def put(action, *args) def put(action, *args)
process(action, "PUT", *args) process(action, "PUT", *args)
......
...@@ -97,6 +97,12 @@ def post? ...@@ -97,6 +97,12 @@ def post?
HTTP_METHOD_LOOKUP[request_method] == :post HTTP_METHOD_LOOKUP[request_method] == :post
end 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? # Is this a PUT request?
# Equivalent to <tt>request.request_method_symbol == :put</tt>. # Equivalent to <tt>request.request_method_symbol == :put</tt>.
def put? def put?
......
...@@ -23,6 +23,7 @@ class Railtie < Rails::Railtie ...@@ -23,6 +23,7 @@ class Railtie < Rails::Railtie
ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length
ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header 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::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_responses.merge!(config.action_dispatch.rescue_responses)
ActionDispatch::ExceptionWrapper.rescue_templates.merge!(config.action_dispatch.rescue_templates) ActionDispatch::ExceptionWrapper.rescue_templates.merge!(config.action_dispatch.rescue_templates)
......
...@@ -182,10 +182,13 @@ module ActionDispatch ...@@ -182,10 +182,13 @@ module ActionDispatch
# #
# == HTTP Methods # == HTTP Methods
# #
# Using the <tt>:via</tt> option when specifying a route allows you to restrict it to a specific HTTP method. # Using the <tt>:via</tt> option when specifying a route allows you to
# Possible values are <tt>:post</tt>, <tt>:get</tt>, <tt>:put</tt>, <tt>:delete</tt> and <tt>:any</tt>. # restrict it to a specific HTTP method. Possible values are <tt>:post</tt>,
# If your route needs to respond to more than one method you can use an array, e.g. <tt>[ :get, :post ]</tt>. # <tt>:get</tt>, <tt>:patch</tt>, <tt>:put</tt>, <tt>:delete</tt> and
# The default value is <tt>:any</tt> which means that the route will respond to any of the HTTP methods. # <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: # Examples:
# #
...@@ -198,7 +201,7 @@ module ActionDispatch ...@@ -198,7 +201,7 @@ module ActionDispatch
# === HTTP helper methods # === HTTP helper methods
# #
# An alternative method of specifying which HTTP method a route should respond to is to use the helper # 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: # Examples:
# #
...@@ -283,6 +286,6 @@ module Routing ...@@ -283,6 +286,6 @@ module Routing
autoload :PolymorphicRoutes, 'action_dispatch/routing/polymorphic_routes' autoload :PolymorphicRoutes, 'action_dispatch/routing/polymorphic_routes'
SEPARATORS = %w( / . ? ) #:nodoc: SEPARATORS = %w( / . ? ) #:nodoc:
HTTP_METHODS = [:get, :head, :post, :put, :delete, :options] #:nodoc: HTTP_METHODS = [:get, :head, :post, :patch, :put, :delete, :options] #:nodoc:
end end
end end
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
module ActionDispatch module ActionDispatch
module Routing module Routing
class Mapper class Mapper
cattr_accessor(:default_method_for_update) {:put}
class Constraints #:nodoc: class Constraints #:nodoc:
def self.new(app, constraints, request = Rack::Request) def self.new(app, constraints, request = Rack::Request)
if constraints.any? if constraints.any?
...@@ -465,7 +467,7 @@ module HttpHelpers ...@@ -465,7 +467,7 @@ module HttpHelpers
# #
# Example: # Example:
# #
# get 'bacon', :to => 'food#bacon' # get 'bacon', :to => 'food#bacon'
def get(*args, &block) def get(*args, &block)
map_method(:get, args, &block) map_method(:get, args, &block)
end end
...@@ -475,17 +477,27 @@ def get(*args, &block) ...@@ -475,17 +477,27 @@ def get(*args, &block)
# #
# Example: # Example:
# #
# post 'bacon', :to => 'food#bacon' # post 'bacon', :to => 'food#bacon'
def post(*args, &block) def post(*args, &block)
map_method(:post, args, &block) map_method(:post, args, &block)
end 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. # Define a route that only recognizes HTTP PUT.
# For supported arguments, see <tt>Base#match</tt>. # For supported arguments, see <tt>Base#match</tt>.
# #
# Example: # Example:
# #
# put 'bacon', :to => 'food#bacon' # put 'bacon', :to => 'food#bacon'
def put(*args, &block) def put(*args, &block)
map_method(:put, args, &block) map_method(:put, args, &block)
end end
...@@ -495,7 +507,7 @@ def put(*args, &block) ...@@ -495,7 +507,7 @@ def put(*args, &block)
# #
# Example: # Example:
# #
# delete 'broccoli', :to => 'food#broccoli' # delete 'broccoli', :to => 'food#broccoli'
def delete(*args, &block) def delete(*args, &block)
map_method(:delete, args, &block) map_method(:delete, args, &block)
end end
...@@ -522,13 +534,13 @@ def map_method(method, args, &block) ...@@ -522,13 +534,13 @@ def map_method(method, args, &block)
# This will create a number of routes for each of the posts and comments # This will create a number of routes for each of the posts and comments
# controller. For <tt>Admin::PostsController</tt>, Rails will create: # controller. For <tt>Admin::PostsController</tt>, Rails will create:
# #
# GET /admin/posts # GET /admin/posts
# GET /admin/posts/new # GET /admin/posts/new
# POST /admin/posts # POST /admin/posts
# GET /admin/posts/1 # GET /admin/posts/1
# GET /admin/posts/1/edit # GET /admin/posts/1/edit
# PUT /admin/posts/1 # PUT/PATCH /admin/posts/1
# DELETE /admin/posts/1 # DELETE /admin/posts/1
# #
# If you want to route /posts (without the prefix /admin) to # If you want to route /posts (without the prefix /admin) to
# <tt>Admin::PostsController</tt>, you could use # <tt>Admin::PostsController</tt>, you could use
...@@ -556,13 +568,13 @@ def map_method(method, args, &block) ...@@ -556,13 +568,13 @@ def map_method(method, args, &block)
# not use scope. In the last case, the following paths map to # not use scope. In the last case, the following paths map to
# +PostsController+: # +PostsController+:
# #
# GET /admin/posts # GET /admin/posts
# GET /admin/posts/new # GET /admin/posts/new
# POST /admin/posts # POST /admin/posts
# GET /admin/posts/1 # GET /admin/posts/1
# GET /admin/posts/1/edit # GET /admin/posts/1/edit
# PUT /admin/posts/1 # PUT/PATCH /admin/posts/1
# DELETE /admin/posts/1 # DELETE /admin/posts/1
module Scoping module Scoping
# Scopes a set of routes to the given default options. # Scopes a set of routes to the given default options.
# #
...@@ -651,13 +663,13 @@ def controller(controller, options={}) ...@@ -651,13 +663,13 @@ def controller(controller, options={})
# #
# This generates the following routes: # This generates the following routes:
# #
# admin_posts GET /admin/posts(.:format) admin/posts#index # admin_posts GET /admin/posts(.:format) admin/posts#index
# admin_posts POST /admin/posts(.:format) admin/posts#create # admin_posts POST /admin/posts(.:format) admin/posts#create
# new_admin_post GET /admin/posts/new(.:format) admin/posts#new # new_admin_post GET /admin/posts/new(.:format) admin/posts#new
# edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit # edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit
# admin_post GET /admin/posts/:id(.:format) admin/posts#show # 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 # admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy
# #
# === Options # === Options
# #
...@@ -972,12 +984,12 @@ def resources_path_names(options) ...@@ -972,12 +984,12 @@ def resources_path_names(options)
# the +GeoCoders+ controller (note that the controller is named after # the +GeoCoders+ controller (note that the controller is named after
# the plural): # the plural):
# #
# GET /geocoder/new # GET /geocoder/new
# POST /geocoder # POST /geocoder
# GET /geocoder # GET /geocoder
# GET /geocoder/edit # GET /geocoder/edit
# PUT /geocoder # PUT/PATCH /geocoder
# DELETE /geocoder # DELETE /geocoder
# #
# === Options # === Options
# Takes same options as +resources+. # Takes same options as +resources+.
...@@ -1002,8 +1014,10 @@ def resource(*resources, &block) ...@@ -1002,8 +1014,10 @@ def resource(*resources, &block)
member do member do
get :edit if parent_resource.actions.include?(:edit) get :edit if parent_resource.actions.include?(:edit)
get :show if parent_resource.actions.include?(:show) get :show if parent_resource.actions.include?(:show)
put :update if parent_resource.actions.include?(:update)
delete :destroy if parent_resource.actions.include?(:destroy) delete :destroy if parent_resource.actions.include?(:destroy)
if parent_resource.actions.include?(:update)
send default_method_for_update, :update
end
end end
end end
...@@ -1020,13 +1034,13 @@ def resource(*resources, &block) ...@@ -1020,13 +1034,13 @@ def resource(*resources, &block)
# creates seven different routes in your application, all mapping to # creates seven different routes in your application, all mapping to
# the +Photos+ controller: # the +Photos+ controller:
# #
# GET /photos # GET /photos
# GET /photos/new # GET /photos/new
# POST /photos # POST /photos
# GET /photos/:id # GET /photos/:id
# GET /photos/:id/edit # GET /photos/:id/edit
# PUT /photos/:id # PUT/PATCH /photos/:id
# DELETE /photos/:id # DELETE /photos/:id
# #
# Resources can also be nested infinitely by using this block syntax: # Resources can also be nested infinitely by using this block syntax:
# #
...@@ -1036,13 +1050,13 @@ def resource(*resources, &block) ...@@ -1036,13 +1050,13 @@ def resource(*resources, &block)
# #
# This generates the following comments routes: # This generates the following comments routes:
# #
# GET /photos/:photo_id/comments # GET /photos/:photo_id/comments
# GET /photos/:photo_id/comments/new # GET /photos/:photo_id/comments/new
# POST /photos/:photo_id/comments # POST /photos/:photo_id/comments
# GET /photos/:photo_id/comments/:id # GET /photos/:photo_id/comments/:id
# GET /photos/:photo_id/comments/:id/edit # 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 # DELETE /photos/:photo_id/comments/:id
# #
# === Options # === Options
# Takes same options as <tt>Base#match</tt> as well as: # Takes same options as <tt>Base#match</tt> as well as:
...@@ -1104,13 +1118,13 @@ def resource(*resources, &block) ...@@ -1104,13 +1118,13 @@ def resource(*resources, &block)
# #
# The +comments+ resource here will have the following routes generated for it: # The +comments+ resource here will have the following routes generated for it:
# #
# post_comments GET /posts/:post_id/comments(.:format) # post_comments GET /posts/:post_id/comments(.:format)
# post_comments POST /posts/:post_id/comments(.:format) # post_comments POST /posts/:post_id/comments(.:format)
# new_post_comment GET /posts/:post_id/comments/new(.:format) # new_post_comment GET /posts/:post_id/comments/new(.:format)
# edit_comment GET /sekret/comments/:id/edit(.:format) # edit_comment GET /sekret/comments/:id/edit(.:format)
# comment GET /sekret/comments/:id(.: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) # comment DELETE /sekret/comments/:id(.:format)
# #
# === Examples # === Examples
# #
...@@ -1138,11 +1152,14 @@ def resources(*resources, &block) ...@@ -1138,11 +1152,14 @@ def resources(*resources, &block)
get :new get :new
end if parent_resource.actions.include?(:new) end if parent_resource.actions.include?(:new)
# TODO: Only accept patch or put depending on config
member do member do
get :edit if parent_resource.actions.include?(:edit) get :edit if parent_resource.actions.include?(:edit)
get :show if parent_resource.actions.include?(:show) get :show if parent_resource.actions.include?(:show)
put :update if parent_resource.actions.include?(:update)
delete :destroy if parent_resource.actions.include?(:destroy) delete :destroy if parent_resource.actions.include?(:destroy)
if parent_resource.actions.include?(:update)
send default_method_for_update, :update
end
end end
end end
......
...@@ -26,8 +26,8 @@ module RequestHelpers ...@@ -26,8 +26,8 @@ module RequestHelpers
# object's <tt>@response</tt> instance variable will point to the same # object's <tt>@response</tt> instance variable will point to the same
# response object. # response object.
# #
# You can also perform POST, PUT, DELETE, and HEAD requests with +#post+, # You can also perform POST, PATCH, PUT, DELETE, and HEAD requests with
# +#put+, +#delete+, and +#head+. # +#post+, +#patch+, +#put+, +#delete+, and +#head+.
def get(path, parameters = nil, headers = nil) def get(path, parameters = nil, headers = nil)
process :get, path, parameters, headers process :get, path, parameters, headers
end end
...@@ -38,6 +38,12 @@ def post(path, parameters = nil, headers = nil) ...@@ -38,6 +38,12 @@ def post(path, parameters = nil, headers = nil)
process :post, path, parameters, headers process :post, path, parameters, headers
end 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 # Performs a PUT request with the given parameters. See +#get+ for more
# details. # details.
def put(path, parameters = nil, headers = nil) def put(path, parameters = nil, headers = nil)
...@@ -65,10 +71,10 @@ def options(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 # Performs an XMLHttpRequest request with the given parameters, mirroring
# a request from the Prototype library. # a request from the Prototype library.
# #
# The request_method is +:get+, +:post+, +:put+, +:delete+ or +:head+; the # The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or
# parameters are +nil+, a hash, or a url-encoded or multipart string; # +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart
# the headers are a hash. Keys are automatically upcased and prefixed # string; the headers are a hash. Keys are automatically upcased and
# with 'HTTP_' if not already. # prefixed with 'HTTP_' if not already.
def xml_http_request(request_method, path, parameters = nil, headers = nil) def xml_http_request(request_method, path, parameters = nil, headers = nil)
headers ||= {} headers ||= {}
headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
...@@ -108,6 +114,12 @@ def post_via_redirect(path, parameters = nil, headers = nil) ...@@ -108,6 +114,12 @@ def post_via_redirect(path, parameters = nil, headers = nil)
request_via_redirect(:post, path, parameters, headers) request_via_redirect(:post, path, parameters, headers)
end 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. # Performs a PUT request, following any subsequent redirect.
# See +request_via_redirect+ for more information. # See +request_via_redirect+ for more information.
def put_via_redirect(path, parameters = nil, headers = nil) def put_via_redirect(path, parameters = nil, headers = nil)
...@@ -318,7 +330,7 @@ def reset! ...@@ -318,7 +330,7 @@ def reset!
@integration_session = Integration::Session.new(app) @integration_session = Integration::Session.new(app)
end 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| xml_http_request xhr get_via_redirect post_via_redirect).each do |method|
define_method(method) do |*args| define_method(method) do |*args|
reset! unless integration_session reset! unless integration_session
......
...@@ -132,6 +132,8 @@ module ActionView #:nodoc: ...@@ -132,6 +132,8 @@ module ActionView #:nodoc:
class Base class Base
include Helpers, ::ERB::Util, Context 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. # Specify the proc used to decorate input tags that refer to attributes with errors.
cattr_accessor :field_error_proc cattr_accessor :field_error_proc
@@field_error_proc = Proc.new{ |html_tag, instance| "<div class=\"field_with_errors\">#{html_tag}</div>".html_safe } @@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) ...@@ -250,7 +250,7 @@ def convert_to_model(object)
# #
# You can force the form to use the full array of HTTP verbs by setting # 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 # 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 # form will be set to POST and a hidden input called _method will carry the intended verb for the server
...@@ -385,7 +385,7 @@ def apply_form_for_options!(record, object, options) #:nodoc: ...@@ -385,7 +385,7 @@ def apply_form_for_options!(record, object, options) #:nodoc:
object = convert_to_model(object) object = convert_to_model(object)
as = options[:as] 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!( options[:html].reverse_merge!(
:class => as ? "#{action}_#{as}" : dom_class(object, action), :class => as ? "#{action}_#{as}" : dom_class(object, action),
:id => as ? "#{action}_#{as}" : [options[:namespace], dom_id(object, action)].compact.join("_").presence, :id => as ? "#{action}_#{as}" : [options[:namespace], dom_id(object, action)].compact.join("_").presence,
......
...@@ -146,12 +146,12 @@ def url_for(options = nil) ...@@ -146,12 +146,12 @@ def url_for(options = nil)
# create an HTML form and immediately submit the form for processing using # create an HTML form and immediately submit the form for processing using
# the HTTP verb specified. Useful for having links perform a POST operation # the HTTP verb specified. Useful for having links perform a POST operation
# in dangerous actions like deleting a record (which search bots can follow # 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 # 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 # 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 # 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 # 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 # * <tt>:remote => true</tt> - This will allow the unobtrusive JavaScript
# driver to make an Ajax request to the URL in question instead of following # 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 # the link. The drivers each provide mechanisms for listening for the
...@@ -272,7 +272,7 @@ def link_to(*args, &block) ...@@ -272,7 +272,7 @@ def link_to(*args, &block)
# #
# There are a few special +html_options+: # There are a few special +html_options+:
# * <tt>:method</tt> - Symbol of HTTP verb. Supported verbs are <tt>:post</tt>, <tt>:get</tt>, # * <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>:disabled</tt> - If set to true, it will generate a disabled button.
# * <tt>:confirm</tt> - This will use the unobtrusive JavaScript driver to # * <tt>:confirm</tt> - This will use the unobtrusive JavaScript driver to
# prompt with the question specified. If the user accepts, the link is # prompt with the question specified. If the user accepts, the link is
...@@ -329,7 +329,7 @@ def button_to(name, options = {}, html_options = {}) ...@@ -329,7 +329,7 @@ def button_to(name, options = {}, html_options = {})
remote = html_options.delete('remote') remote = html_options.delete('remote')
method = html_options.delete('method').to_s 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_method = method == 'get' ? 'get' : 'post'
form_options = html_options.delete('form') || {} form_options = html_options.delete('form') || {}
......
...@@ -33,6 +33,7 @@ class Railtie < Rails::Railtie ...@@ -33,6 +33,7 @@ class Railtie < Rails::Railtie
end end
initializer "action_view.set_configs" do |app| 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 ActiveSupport.on_load(:action_view) do
app.config.action_view.each do |k,v| app.config.action_view.each do |k,v|
send "#{k}=", v send "#{k}=", v
......
...@@ -180,7 +180,7 @@ def test_should_cache_ok_at_custom_path ...@@ -180,7 +180,7 @@ def test_should_cache_ok_at_custom_path
end end
[:ok, :no_content, :found, :not_found].each do |status| [: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 unless method == :get && status == :ok
define_method "test_shouldnt_cache_#{method}_with_#{status}_status" do define_method "test_shouldnt_cache_#{method}_with_#{status}_status" do
send(method, status) send(method, status)
......
...@@ -63,6 +63,12 @@ def test_post_via_redirect ...@@ -63,6 +63,12 @@ def test_post_via_redirect
@session.post_via_redirect(path, args, headers) @session.post_via_redirect(path, args, headers)
end 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 def test_put_via_redirect
path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" } path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" }
@session.expects(:request_via_redirect).with(:put, path, args, headers) @session.expects(:request_via_redirect).with(:put, path, args, headers)
...@@ -87,6 +93,12 @@ def test_post ...@@ -87,6 +93,12 @@ def test_post
@session.post(path,params,headers) @session.post(path,params,headers)
end 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 def test_put
path = "/index"; params = "blah"; headers = {:location => 'blah'} path = "/index"; params = "blah"; headers = {:location => 'blah'}
@session.expects(:process).with(:put,path,params,headers) @session.expects(:process).with(:put,path,params,headers)
...@@ -131,6 +143,16 @@ def test_xml_http_request_post ...@@ -131,6 +143,16 @@ def test_xml_http_request_post
@session.xml_http_request(:post,path,params,headers) @session.xml_http_request(:post,path,params,headers)
end 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 def test_xml_http_request_put
path = "/index"; params = "blah"; headers = {:location => 'blah'} path = "/index"; params = "blah"; headers = {:location => 'blah'}
headers_after_xhr = headers.merge( headers_after_xhr = headers.merge(
...@@ -228,7 +250,7 @@ def test_integration_methods_called ...@@ -228,7 +250,7 @@ def test_integration_methods_called
@integration_session.stubs(:generic_url_rewriter) @integration_session.stubs(:generic_url_rewriter)
@integration_session.stubs(:process) @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, '/') } assert_nothing_raised("'#{verb}' should use integration test methods") { __send__(verb, '/') }
end end
end end
......
...@@ -770,6 +770,41 @@ def test_using_resource_for_post_with_json_yields_unprocessable_entity_on_failur ...@@ -770,6 +770,41 @@ def test_using_resource_for_post_with_json_yields_unprocessable_entity_on_failur
end end
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 def test_using_resource_for_put_with_html_redirects_on_success
with_test_route_set do with_test_route_set do
put :using_resource put :using_resource
......
...@@ -114,6 +114,10 @@ def test_should_not_allow_post_without_token_irrespective_of_format ...@@ -114,6 +114,10 @@ def test_should_not_allow_post_without_token_irrespective_of_format
assert_blocked { post :index, :format=>'xml' } assert_blocked { post :index, :format=>'xml' }
end end
def test_should_not_allow_patch_without_token
assert_blocked { patch :index }
end
def test_should_not_allow_put_without_token def test_should_not_allow_put_without_token
assert_blocked { put :index } assert_blocked { put :index }
end end
...@@ -130,6 +134,10 @@ def test_should_allow_post_with_token ...@@ -130,6 +134,10 @@ def test_should_allow_post_with_token
assert_not_blocked { post :index, :custom_authenticity_token => @token } assert_not_blocked { post :index, :custom_authenticity_token => @token }
end 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 def test_should_allow_put_with_token
assert_not_blocked { put :index, :custom_authenticity_token => @token } assert_not_blocked { put :index, :custom_authenticity_token => @token }
end end
...@@ -148,6 +156,11 @@ def test_should_allow_delete_with_token_in_header ...@@ -148,6 +156,11 @@ def test_should_allow_delete_with_token_in_header
assert_not_blocked { delete :index } assert_not_blocked { delete :index }
end 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 def test_should_allow_put_with_token_in_header
@request.env['HTTP_X_CSRF_TOKEN'] = @token @request.env['HTTP_X_CSRF_TOKEN'] = @token
assert_not_blocked { put :index } assert_not_blocked { put :index }
...@@ -232,7 +245,7 @@ def test_should_not_render_button_to_with_token_tag ...@@ -232,7 +245,7 @@ def test_should_not_render_button_to_with_token_tag
end end
def test_should_allow_all_methods_without_token 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)} assert_nothing_raised { send(method, :index)}
end end
end end
......
...@@ -158,7 +158,7 @@ def test_with_name_prefix ...@@ -158,7 +158,7 @@ def test_with_name_prefix
end end
def test_with_collection_actions 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| with_routing do |set|
set.draw do set.draw do
...@@ -167,6 +167,7 @@ def test_with_collection_actions ...@@ -167,6 +167,7 @@ def test_with_collection_actions
put :b, :on => :collection put :b, :on => :collection
post :c, :on => :collection post :c, :on => :collection
delete :d, :on => :collection delete :d, :on => :collection
patch :e, :on => :collection
end end
end end
...@@ -185,7 +186,7 @@ def test_with_collection_actions ...@@ -185,7 +186,7 @@ def test_with_collection_actions
end end
def test_with_collection_actions_and_name_prefix 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| with_routing do |set|
set.draw do set.draw do
...@@ -195,6 +196,7 @@ def test_with_collection_actions_and_name_prefix ...@@ -195,6 +196,7 @@ def test_with_collection_actions_and_name_prefix
put :b, :on => :collection put :b, :on => :collection
post :c, :on => :collection post :c, :on => :collection
delete :d, :on => :collection delete :d, :on => :collection
patch :e, :on => :collection
end end
end end
end end
...@@ -241,7 +243,7 @@ def test_with_collection_actions_and_name_prefix_and_member_action_with_same_nam ...@@ -241,7 +243,7 @@ def test_with_collection_actions_and_name_prefix_and_member_action_with_same_nam
end end
def test_with_collection_action_and_name_prefix_and_formatted 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| with_routing do |set|
set.draw do set.draw do
...@@ -251,6 +253,7 @@ def test_with_collection_action_and_name_prefix_and_formatted ...@@ -251,6 +253,7 @@ def test_with_collection_action_and_name_prefix_and_formatted
put :b, :on => :collection put :b, :on => :collection
post :c, :on => :collection post :c, :on => :collection
delete :d, :on => :collection delete :d, :on => :collection
patch :e, :on => :collection
end end
end end
end end
...@@ -270,7 +273,7 @@ def test_with_collection_action_and_name_prefix_and_formatted ...@@ -270,7 +273,7 @@ def test_with_collection_action_and_name_prefix_and_formatted
end end
def test_with_member_action def test_with_member_action
[:put, :post].each do |method| [:patch, :put, :post].each do |method|
with_restful_routing :messages, :member => { :mark => method } do with_restful_routing :messages, :member => { :mark => method } do
mark_options = {:action => 'mark', :id => '1'} mark_options = {:action => 'mark', :id => '1'}
mark_path = "/messages/1/mark" mark_path = "/messages/1/mark"
...@@ -294,7 +297,7 @@ def test_with_member_action_and_requirement ...@@ -294,7 +297,7 @@ def test_with_member_action_and_requirement
end end
def test_member_when_override_paths_for_default_restful_actions_with 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 with_restful_routing :messages, :member => { :mark => method }, :path_names => {:new => 'nuevo'} do
mark_options = {:action => 'mark', :id => '1', :controller => "messages"} mark_options = {:action => 'mark', :id => '1', :controller => "messages"}
mark_path = "/messages/1/mark" mark_path = "/messages/1/mark"
...@@ -311,7 +314,7 @@ def test_member_when_override_paths_for_default_restful_actions_with ...@@ -311,7 +314,7 @@ def test_member_when_override_paths_for_default_restful_actions_with
end end
def test_with_two_member_actions_with_same_method def test_with_two_member_actions_with_same_method
[:put, :post].each do |method| [:patch, :put, :post].each do |method|
with_routing do |set| with_routing do |set|
set.draw do set.draw do
resources :messages do resources :messages do
...@@ -564,7 +567,7 @@ def test_should_create_nested_singleton_resource_routes ...@@ -564,7 +567,7 @@ def test_should_create_nested_singleton_resource_routes
end end
def test_singleton_resource_with_member_action def test_singleton_resource_with_member_action
[:put, :post].each do |method| [:patch, :put, :post].each do |method|
with_routing do |set| with_routing do |set|
set.draw do set.draw do
resource :account do resource :account do
...@@ -586,7 +589,7 @@ def test_singleton_resource_with_member_action ...@@ -586,7 +589,7 @@ def test_singleton_resource_with_member_action
end end
def test_singleton_resource_with_two_member_actions_with_same_method 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| with_routing do |set|
set.draw do set.draw do
resource :account do resource :account do
...@@ -651,12 +654,16 @@ def test_should_nest_singleton_resource_in_resources ...@@ -651,12 +654,16 @@ def test_should_nest_singleton_resource_in_resources
end end
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 controller_name = :messages
with_restful_routing controller_name do with_restful_routing controller_name do
options = { :controller => controller_name.to_s } options = { :controller => controller_name.to_s }
collection_path = "/#{controller_name}" 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_raise(ActionController::RoutingError) do
assert_recognizes(options.merge(:action => 'update'), :path => collection_path, :method => :put) assert_recognizes(options.merge(:action => 'update'), :path => collection_path, :method => :put)
end end
......
...@@ -648,11 +648,12 @@ def setup_request_method_routes_for(method) ...@@ -648,11 +648,12 @@ def setup_request_method_routes_for(method)
match '/match' => 'books#get', :via => :get match '/match' => 'books#get', :via => :get
match '/match' => 'books#post', :via => :post match '/match' => 'books#post', :via => :post
match '/match' => 'books#put', :via => :put match '/match' => 'books#put', :via => :put
match '/match' => 'books#patch', :via => :patch
match '/match' => 'books#delete', :via => :delete match '/match' => 'books#delete', :via => :delete
end end
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 define_method("test_request_method_recognized_with_#{request_method}") do
setup_request_method_routes_for(request_method) setup_request_method_routes_for(request_method)
params = rs.recognize_path("/match", :method => request_method) params = rs.recognize_path("/match", :method => request_method)
...@@ -1035,6 +1036,7 @@ def test_recognize_with_http_methods ...@@ -1035,6 +1036,7 @@ def test_recognize_with_http_methods
post "/people" => "people#create" post "/people" => "people#create"
get "/people/:id" => "people#show", :as => "person" get "/people/:id" => "people#show", :as => "person"
put "/people/:id" => "people#update" put "/people/:id" => "people#update"
patch "/people/:id" => "people#update"
delete "/people/:id" => "people#destroy" delete "/people/:id" => "people#destroy"
end end
...@@ -1047,6 +1049,9 @@ def test_recognize_with_http_methods ...@@ -1047,6 +1049,9 @@ def test_recognize_with_http_methods
params = set.recognize_path("/people/5", :method => :put) params = set.recognize_path("/people/5", :method => :put)
assert_equal("update", params[:action]) assert_equal("update", params[:action])
params = set.recognize_path("/people/5", :method => :patch)
assert_equal("update", params[:action])
assert_raise(ActionController::UnknownHttpMethod) { assert_raise(ActionController::UnknownHttpMethod) {
set.recognize_path("/people", :method => :bacon) set.recognize_path("/people", :method => :bacon)
} }
...@@ -1059,6 +1064,10 @@ def test_recognize_with_http_methods ...@@ -1059,6 +1064,10 @@ def test_recognize_with_http_methods
assert_equal("update", params[:action]) assert_equal("update", params[:action])
assert_equal("5", params[:id]) 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) params = set.recognize_path("/people/5", :method => :delete)
assert_equal("destroy", params[:action]) assert_equal("destroy", params[:action])
assert_equal("5", params[:id]) assert_equal("5", params[:id])
...@@ -1112,6 +1121,7 @@ def test_recognize_with_conditions_and_format ...@@ -1112,6 +1121,7 @@ def test_recognize_with_conditions_and_format
set.draw do set.draw do
get "people/:id" => "people#show", :as => "person" get "people/:id" => "people#show", :as => "person"
put "people/:id" => "people#update" put "people/:id" => "people#update"
patch "people/:id" => "people#update"
get "people/:id(.:format)" => "people#show" get "people/:id(.:format)" => "people#show"
end end
...@@ -1122,6 +1132,9 @@ def test_recognize_with_conditions_and_format ...@@ -1122,6 +1132,9 @@ def test_recognize_with_conditions_and_format
params = set.recognize_path("/people/5", :method => :put) params = set.recognize_path("/people/5", :method => :put)
assert_equal("update", params[:action]) 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) params = set.recognize_path("/people/5.png", :method => :get)
assert_equal("show", params[:action]) assert_equal("show", params[:action])
assert_equal("5", params[:id]) assert_equal("5", params[:id])
......
...@@ -325,14 +325,14 @@ def url_for(options = {}) ...@@ -325,14 +325,14 @@ def url_for(options = {})
end end
test "String request methods" do 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 request = stub_request 'REQUEST_METHOD' => method.to_s.upcase
assert_equal method.to_s.upcase, request.method assert_equal method.to_s.upcase, request.method
end end
end end
test "Symbol forms of request methods via method_symbol" do 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 request = stub_request 'REQUEST_METHOD' => method.to_s.upcase
assert_equal method, request.method_symbol assert_equal method, request.method_symbol
end end
...@@ -346,7 +346,7 @@ def url_for(options = {}) ...@@ -346,7 +346,7 @@ def url_for(options = {})
end end
test "allow method hacking on post" do 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 request = stub_request "REQUEST_METHOD" => method.to_s.upcase
assert_equal(method == "HEAD" ? "GET" : method, request.method) assert_equal(method == "HEAD" ? "GET" : method, request.method)
end end
...@@ -360,7 +360,7 @@ def url_for(options = {}) ...@@ -360,7 +360,7 @@ def url_for(options = {})
end end
test "restrict method hacking" do 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, request = stub_request 'REQUEST_METHOD' => method.to_s.upcase,
'action_dispatch.request.request_parameters' => { :_method => 'put' } 'action_dispatch.request.request_parameters' => { :_method => 'put' }
assert_equal method.to_s.upcase, request.method assert_equal method.to_s.upcase, request.method
...@@ -375,6 +375,13 @@ def url_for(options = {}) ...@@ -375,6 +375,13 @@ def url_for(options = {})
assert request.head? assert request.head?
end 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 test "post masquerading as put" do
request = stub_request 'REQUEST_METHOD' => 'PUT', "rack.methodoverride.original_method" => "POST" request = stub_request 'REQUEST_METHOD' => 'PUT', "rack.methodoverride.original_method" => "POST"
assert_equal "POST", request.method assert_equal "POST", request.method
......
...@@ -2195,6 +2195,15 @@ def test_form_for_with_existing_object_and_custom_url ...@@ -2195,6 +2195,15 @@ def test_form_for_with_existing_object_and_custom_url
assert_equal expected, output_buffer assert_equal expected, output_buffer
end 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 def test_fields_for_returns_block_result
output = fields_for(Post.new) { |f| "fields" } output = fields_for(Post.new) { |f| "fields" }
assert_equal "fields", output assert_equal "fields", output
......
...@@ -78,6 +78,12 @@ def test_form_tag_multipart ...@@ -78,6 +78,12 @@ def test_form_tag_multipart
assert_dom_equal expected, actual assert_dom_equal expected, actual
end 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 def test_form_tag_with_method_put
actual = form_tag({}, { :method => :put }) actual = form_tag({}, { :method => :put })
expected = whole_form("http://www.example.com", :method => :put) expected = whole_form("http://www.example.com", :method => :put)
......
...@@ -62,9 +62,9 @@ def test_to_partial_path ...@@ -62,9 +62,9 @@ def test_to_partial_path
# #
# Returns a boolean that specifies whether the object has been persisted yet. # 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 # 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 # not persisted, a form for that object, for instance, will route to the
# collection. If it is persisted, a form for the object will be PUT to the # create action. If it is persisted, a form for the object will routes to
# URL for the object. # the update action.
def test_persisted? def test_persisted?
assert model.respond_to?(:persisted?), "The model should respond to persisted?" assert model.respond_to?(:persisted?), "The model should respond to persisted?"
assert_boolean model.persisted?, "persisted?" assert_boolean model.persisted?, "persisted?"
......
...@@ -15,6 +15,7 @@ class Connection ...@@ -15,6 +15,7 @@ class Connection
HTTP_FORMAT_HEADER_NAMES = { :get => 'Accept', HTTP_FORMAT_HEADER_NAMES = { :get => 'Accept',
:put => 'Content-Type', :put => 'Content-Type',
:post => 'Content-Type', :post => 'Content-Type',
:patch => 'Content-Type',
:delete => 'Accept', :delete => 'Accept',
:head => 'Accept' :head => 'Accept'
} }
...@@ -86,6 +87,12 @@ def delete(path, headers = {}) ...@@ -86,6 +87,12 @@ def delete(path, headers = {})
with_auth { request(:delete, path, build_request_headers(headers, :delete, self.site.merge(path))) } with_auth { request(:delete, path, build_request_headers(headers, :delete, self.site.merge(path))) }
end 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). # Executes a PUT request (see HTTP protocol documentation if unfamiliar).
# Used to update resources. # Used to update resources.
def put(path, body = '', headers = {}) def put(path, body = '', headers = {})
......
...@@ -11,10 +11,10 @@ module ActiveResource ...@@ -11,10 +11,10 @@ module ActiveResource
# #
# This route set creates routes for the following HTTP requests: # This route set creates routes for the following HTTP requests:
# #
# POST /people/new/register.json # PeopleController.register # 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 # DELETE /people/1/deactivate.json # PeopleController.deactivate with :id => 1
# GET /people/active.json # PeopleController.active # GET /people/active.json # PeopleController.active
# #
# Using this module, Active Resource can use these custom REST methods just like the # Using this module, Active Resource can use these custom REST methods just like the
# standard methods. # standard methods.
...@@ -63,6 +63,10 @@ def post(custom_method_name, options = {}, body = '') ...@@ -63,6 +63,10 @@ def post(custom_method_name, options = {}, body = '')
connection.post(custom_method_collection_url(custom_method_name, options), body, headers) connection.post(custom_method_collection_url(custom_method_name, options), body, headers)
end 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 = '') def put(custom_method_name, options = {}, body = '')
connection.put(custom_method_collection_url(custom_method_name, options), body, headers) connection.put(custom_method_collection_url(custom_method_name, options), body, headers)
end end
...@@ -98,6 +102,10 @@ def post(method_name, options = {}, body = nil) ...@@ -98,6 +102,10 @@ def post(method_name, options = {}, body = nil)
end end
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 = '') def put(method_name, options = {}, body = '')
connection.put(custom_method_element_url(method_name, options), body, self.class.headers) connection.put(custom_method_element_url(method_name, options), body, self.class.headers)
end end
......
...@@ -15,7 +15,7 @@ class InvalidRequestError < StandardError; end #:nodoc: ...@@ -15,7 +15,7 @@ class InvalidRequestError < StandardError; end #:nodoc:
# #
# mock.http_method(path, request_headers = {}, body = nil, status = 200, response_headers = {}) # 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+. # +head+.
# * <tt>path</tt> - A string, starting with a "/", defining the URI that is expected to be # * <tt>path</tt> - A string, starting with a "/", defining the URI that is expected to be
# called. # called.
...@@ -55,7 +55,7 @@ def initialize(responses) ...@@ -55,7 +55,7 @@ def initialize(responses)
@responses = responses @responses = responses
end 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 = {}) # 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) # @responses[Request.new(:post, path, nil, request_headers)] = Response.new(body || "", status, response_headers)
# end # end
...@@ -217,7 +217,7 @@ def reset! ...@@ -217,7 +217,7 @@ def reset!
end end
# body? methods # body? methods
{ true => %w(post put), { true => %w(post patch put),
false => %w(get delete head) }.each do |has_body, methods| false => %w(get delete head) }.each do |has_body, methods|
methods.each do |method| methods.each do |method|
# def post(path, body, headers) # def post(path, body, headers)
......
...@@ -11,11 +11,15 @@ def setup ...@@ -11,11 +11,15 @@ def setup
end end
def test_http_format_header_name def test_http_format_header_name
header_name = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[:get] [:get, :head].each do |verb|
assert_equal 'Accept', header_name 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]] [:patch, :put, :post].each do |verb|
headers_names.each{ |name| assert_equal 'Content-Type', name } header_name = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[verb]
assert_equal 'Content-Type', header_name
end
end end
def test_formats_on_single_element def test_formats_on_single_element
......
...@@ -8,7 +8,7 @@ class HttpMockTest < ActiveSupport::TestCase ...@@ -8,7 +8,7 @@ class HttpMockTest < ActiveSupport::TestCase
FORMAT_HEADER = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES 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 test "responds to simple #{method} request" do
ActiveResource::HttpMock.respond_to do |mock| ActiveResource::HttpMock.respond_to do |mock|
mock.send(method, "/people/1", { FORMAT_HEADER[method] => "application/json" }, "Response") mock.send(method, "/people/1", { FORMAT_HEADER[method] => "application/json" }, "Response")
...@@ -193,7 +193,7 @@ class HttpMockTest < ActiveSupport::TestCase ...@@ -193,7 +193,7 @@ class HttpMockTest < ActiveSupport::TestCase
end end
def request(method, path, headers = {}, body = nil) def request(method, path, headers = {}, body = nil)
if method.in?([:put, :post]) if method.in?([:patch, :put, :post])
@http.send(method, path, body, headers) @http.send(method, path, body, headers)
else else
@http.send(method, path, headers) @http.send(method, path, headers)
......
...@@ -563,7 +563,7 @@ The request object contains a lot of useful information about the request coming ...@@ -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).| |domain(n=2)|The hostname's first +n+ segments, starting from the right (the TLD).|
|format|The content type requested by the client.| |format|The content type requested by the client.|
|method|The HTTP method used for the request.| |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.| |headers|Returns a hash containing the headers associated with the request.|
|port|The port number (integer) used for the request.| |port|The port number (integer) used for the request.|
|protocol|Returns a string containing the protocol used plus "://", for example "http://".| |protocol|Returns a string containing the protocol used plus "://", for example "http://".|
......
...@@ -146,7 +146,8 @@ link_to_remote "Add new item", ...@@ -146,7 +146,8 @@ link_to_remote "Add new item",
:position => :bottom :position => :bottom
</ruby> </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> <ruby>
link_to_remote "Delete the item", link_to_remote "Delete the item",
......
...@@ -76,6 +76,8 @@ NOTE. The +config.asset_path+ configuration is ignored if the asset pipeline is ...@@ -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.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.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. * +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 ...@@ -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::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::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+. * +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::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. * +ActionDispatch::BestStandardsSupport+ enables "best standards support" so that IE8 renders some elements correctly.
...@@ -346,7 +348,8 @@ h4. Configuring Action Dispatch ...@@ -346,7 +348,8 @@ h4. Configuring Action Dispatch
h4. Configuring Action View 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 * +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 ...@@ -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. 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 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 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] ...@@ -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. 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: 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> <ruby>
form_tag(search_path, :method => "put") form_tag(search_path, :method => "patch")
</ruby> </ruby>
output: output:
...@@ -335,14 +340,14 @@ output: ...@@ -335,14 +340,14 @@ output:
<html> <html>
<form accept-charset="UTF-8" action="/search" method="post"> <form accept-charset="UTF-8" action="/search" method="post">
<div style="margin:0;padding:0"> <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="utf8" type="hidden" value="&#x2713;" />
<input name="authenticity_token" type="hidden" value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71" /> <input name="authenticity_token" type="hidden" value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71" />
</div> </div>
... ...
</html> </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 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 ...@@ -50,7 +50,7 @@ Resource routing allows you to quickly declare all of the common routes for a gi
h4. Resources on the Web 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 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+ ...@@ -82,11 +82,10 @@ creates seven different routes in your application, all mapping to the +Photos+
|POST |/photos |create |create a new photo | |POST |/photos |create |create a new photo |
|GET |/photos/:id |show |display a specific photo | |GET |/photos/:id |show |display a specific photo |
|GET |/photos/:id/edit |edit |return an HTML form for editing a 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 | |DELETE |/photos/:id |destroy |delete a specific photo |
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.
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.
h4. Paths and URLs h4. Paths and URLs
...@@ -138,10 +137,10 @@ creates six different routes in your application, all mapping to the +Geocoders+ ...@@ -138,10 +137,10 @@ creates six different routes in your application, all mapping to the +Geocoders+
|POST |/geocoder |create |create the new geocoder | |POST |/geocoder |create |create the new geocoder |
|GET |/geocoder |show |display the one and only geocoder resource | |GET |/geocoder |show |display the one and only geocoder resource |
|GET |/geocoder/edit |edit |return an HTML form for editing the geocoder | |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 | |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: 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 ...@@ -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 | |POST |/admin/posts |create | admin_posts_path |
|GET |/admin/posts/:id |show | admin_post_path(:id) | |GET |/admin/posts/:id |show | admin_post_path(:id) |
|GET |/admin/posts/:id/edit |edit | edit_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) | |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 If you want to route +/posts+ (without the prefix +/admin+) to +Admin::PostsController+, you could use
<ruby> <ruby>
...@@ -208,9 +209,11 @@ In each of these cases, the named routes remain the same as if you did not use + ...@@ -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 | |POST |/admin/posts |create | posts_path |
|GET |/admin/posts/:id |show | post_path(:id) | |GET |/admin/posts/:id |show | post_path(:id) |
|GET |/admin/posts/:id/edit|edit | edit_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) | |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 h4. Nested Resources
It's common to have resources that are logically children of other resources. For example, suppose your application includes these models: 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 ...@@ -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: 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 | |_.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 |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 | |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 | |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 |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 | |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 | |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)+). 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 ...@@ -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. 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> <ruby>
resources :photos do resources :photos do
...@@ -642,7 +646,7 @@ will recognize incoming paths beginning with +/photos+ but route to the +Images+ ...@@ -642,7 +646,7 @@ will recognize incoming paths beginning with +/photos+ but route to the +Images+
|POST |/photos |create | photos_path | |POST |/photos |create | photos_path |
|GET |/photos/:id |show | photo_path(:id) | |GET |/photos/:id |show | photo_path(:id) |
|GET |/photos/:id/edit |edit | edit_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) | |DELETE |/photos/:id |destroy | photo_path(:id) |
NOTE: Use +photos_path+, +new_photo_path+, etc. to generate paths for this resource. 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 ...@@ -686,9 +690,11 @@ will recognize incoming paths beginning with +/photos+ and route the requests to
|POST |/photos |create | images_path | |POST |/photos |create | images_path |
|GET |/photos/:id |show | image_path(:id) | |GET |/photos/:id |show | image_path(:id) |
|GET |/photos/:id/edit |edit | edit_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) | |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 h4. Overriding the +new+ and +edit+ Segments
The +:path_names+ option lets you override the automatically-generated "new" and "edit" segments in paths: 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+. ...@@ -790,9 +796,11 @@ Rails now creates routes to the +CategoriesController+.
|POST |/kategorien |create | categories_path | |POST |/kategorien |create | categories_path |
|GET |/kategorien/:id |show | category_path(:id) | |GET |/kategorien/:id |show | category_path(:id) |
|GET |/kategorien/:id/bearbeiten |edit | edit_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) | |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 h4. Overriding the Singular Form
If you want to define the singular form of a resource, you should add additional rules to the +Inflector+. 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. ...@@ -483,10 +483,11 @@ Now you can try running all the tests and they should pass.
h4. Available Request Types for Functional Tests 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+ * +get+
* +post+ * +post+
* +patch+
* +put+ * +put+
* +head+ * +head+
* +delete+ * +delete+
...@@ -638,6 +639,7 @@ In addition to the standard testing helpers, there are some additional helpers a ...@@ -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.| |+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.| |+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.| |+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.| |+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.| |+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.| |+open_session+ |Opens a new session instance.|
...@@ -810,7 +812,7 @@ class PostsControllerTest < ActionController::TestCase ...@@ -810,7 +812,7 @@ class PostsControllerTest < ActionController::TestCase
end end
test "should update post" do test "should update post" do
put :update, :id => @post.id, :post => { } patch :update, :id => @post.id, :post => { }
assert_redirected_to post_path(assigns(:post)) assert_redirected_to post_path(assigns(:post))
end end
......
...@@ -11,7 +11,7 @@ class Configuration < ::Rails::Engine::Configuration ...@@ -11,7 +11,7 @@ class Configuration < ::Rails::Engine::Configuration
:force_ssl, :helpers_paths, :logger, :log_tags, :preload_frameworks, :force_ssl, :helpers_paths, :logger, :log_tags, :preload_frameworks,
:railties_order, :relative_url_root, :secret_token, :railties_order, :relative_url_root, :secret_token,
:serve_static_assets, :ssl_options, :static_cache_control, :session_options, :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_writer :log_level
attr_reader :encoding attr_reader :encoding
...@@ -40,6 +40,7 @@ def initialize(*) ...@@ -40,6 +40,7 @@ def initialize(*)
@reload_classes_only_on_change = true @reload_classes_only_on_change = true
@file_watcher = ActiveSupport::FileUpdateChecker @file_watcher = ActiveSupport::FileUpdateChecker
@exceptions_app = nil @exceptions_app = nil
@default_method_for_update = :put
@assets = ActiveSupport::OrderedOptions.new @assets = ActiveSupport::OrderedOptions.new
@assets.enabled = false @assets.enabled = false
......
...@@ -37,7 +37,7 @@ def self.all(klass) ...@@ -37,7 +37,7 @@ def self.all(klass)
# GET show # GET show
# GET edit # GET edit
# PUT update # PUT/PATCH update
# DELETE destroy # DELETE destroy
def self.find(klass, params=nil) def self.find(klass, params=nil)
"#{klass}.find(#{params})" "#{klass}.find(#{params})"
...@@ -58,13 +58,13 @@ def save ...@@ -58,13 +58,13 @@ def save
"#{name}.save" "#{name}.save"
end end
# PUT update # PUT/PATCH update
def update_attributes(params=nil) def update_attributes(params=nil)
"#{name}.update_attributes(#{params})" "#{name}.update_attributes(#{params})"
end end
# POST create # POST create
# PUT update # PUT/PATCH update
def errors def errors
"#{name}.errors" "#{name}.errors"
end end
......
...@@ -31,6 +31,9 @@ class Application < Rails::Application ...@@ -31,6 +31,9 @@ class Application < Rails::Application
# Activate observers that should always be running. # Activate observers that should always be running.
# config.active_record.observers = :cacher, :garbage_collector, :forum_observer # 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. # 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. # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
# config.time_zone = 'Central Time (US & Canada)' # config.time_zone = 'Central Time (US & Canada)'
......
...@@ -54,8 +54,8 @@ def create ...@@ -54,8 +54,8 @@ def create
end end
end end
# PUT <%= route_url %>/1 # PUT/PATCH <%= route_url %>/1
# PUT <%= route_url %>/1.json # PUT/PATCH <%= route_url %>/1.json
def update def update
@<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %> @<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
......
...@@ -146,7 +146,7 @@ def teardown ...@@ -146,7 +146,7 @@ def teardown
test "frameworks are not preloaded by default" do test "frameworks are not preloaded by default" do
require "#{app_path}/config/environment" require "#{app_path}/config/environment"
assert ActionController.autoload?(:RecordIdentifier) assert ActionController.autoload?(:Caching)
end end
test "frameworks are preloaded with config.preload_frameworks is set" do test "frameworks are preloaded with config.preload_frameworks is set" do
...@@ -156,7 +156,7 @@ def teardown ...@@ -156,7 +156,7 @@ def teardown
require "#{app_path}/config/environment" require "#{app_path}/config/environment"
assert !ActionController.autoload?(:RecordIdentifier) assert !ActionController.autoload?(:Caching)
end end
test "filter_parameters should be able to set via config.filter_parameters" do test "filter_parameters should be able to set via config.filter_parameters" do
...@@ -246,6 +246,49 @@ def index ...@@ -246,6 +246,49 @@ def index
assert last_response.body =~ /csrf\-param/ assert last_response.body =~ /csrf\-param/
end 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 test "request forgery token param can be changed" do
make_basic_app do make_basic_app do
app.config.action_controller.request_forgery_protection_token = '_xsrf_token_here' 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 ...@@ -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["']/ assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/
end 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 def test_new_hash_style
run_generator [destination_root] run_generator [destination_root]
assert_file "config/initializers/session_store.rb" do |file| 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.
先完成此消息的编辑!
想要评论请 注册