Skip to content

Commit

Permalink
Merge pull request #5130 from dlee/revised_patch_verb
Browse files Browse the repository at this point in the history
Add config.default_method_for_update to support PATCH
  • Loading branch information
fxn committed Feb 22, 2012
2 parents f28d9f1 + 002713c commit 7f2548e
Show file tree
Hide file tree
Showing 40 changed files with 402 additions and 151 deletions.
1 change: 1 addition & 0 deletions Gemfile
Expand Up @@ -8,6 +8,7 @@ else
gem 'arel'
end

gem 'rack-test', :git => "https://github.com/brynary/rack-test.git"
gem 'bcrypt-ruby', '~> 3.0.0'
gem 'jquery-rails'

Expand Down
4 changes: 2 additions & 2 deletions actionpack/lib/action_controller/metal/http_authentication.rb
Expand Up @@ -279,7 +279,7 @@ def secret_token(request)
#
# An implementation might choose not to accept a previously used nonce or a previously used digest, in order to
# protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for
# POST or PUT requests and a time-stamp for GET requests. For more details on the issues involved see Section 4
# POST, PUT, or PATCH requests and a time-stamp for GET requests. For more details on the issues involved see Section 4
# of this document.
#
# The nonce is opaque to the client. Composed of Time, and hash of Time with secret
Expand All @@ -293,7 +293,7 @@ def nonce(secret_key, time = Time.now)
end

# Might want a shorter timeout depending on whether the request
# is a PUT or POST, and if client is browser or web service.
# is a PATCH, PUT, or POST, and if client is browser or web service.
# Can be much shorter if the Stale directive is implemented. This would
# allow a user to use new nonce without prompting user again for their
# username and password.
Expand Down
13 changes: 7 additions & 6 deletions actionpack/lib/action_controller/metal/responder.rb
Expand Up @@ -53,7 +53,7 @@ module ActionController #:nodoc:
# end
# end
#
# The same happens for PUT and DELETE requests.
# The same happens for PATCH/PUT and DELETE requests.
#
# === Nested resources
#
Expand Down Expand Up @@ -116,8 +116,9 @@ module ActionController #:nodoc:
class Responder
attr_reader :controller, :request, :format, :resource, :resources, :options

ACTIONS_FOR_VERBS = {
DEFAULT_ACTIONS_FOR_VERBS = {
:post => :new,
:patch => :edit,
:put => :edit
}

Expand All @@ -132,7 +133,7 @@ def initialize(controller, resources, options={})
end

delegate :head, :render, :redirect_to, :to => :controller
delegate :get?, :post?, :put?, :delete?, :to => :request
delegate :get?, :post?, :patch?, :put?, :delete?, :to => :request

# Undefine :to_json and :to_yaml since it's defined on Object
undef_method(:to_json) if method_defined?(:to_json)
Expand Down Expand Up @@ -259,11 +260,11 @@ def has_errors?
resource.respond_to?(:errors) && !resource.errors.empty?
end

# By default, render the <code>:edit</code> action for HTML requests with failure, unless
# the verb is POST.
# By default, render the <code>:edit</code> action for HTML requests with errors, unless
# the verb was POST.
#
def default_action
@action ||= ACTIONS_FOR_VERBS[request.request_method_symbol]
@action ||= DEFAULT_ACTIONS_FOR_VERBS[request.request_method_symbol]
end

def resource_errors
Expand Down
7 changes: 6 additions & 1 deletion actionpack/lib/action_controller/test_case.rb
Expand Up @@ -225,7 +225,7 @@ def exists?
# == Basic example
#
# Functional tests are written as follows:
# 1. First, one uses the +get+, +post+, +put+, +delete+ or +head+ method to simulate
# 1. First, one uses the +get+, +post+, +patch+, +put+, +delete+ or +head+ method to simulate
# an HTTP request.
# 2. Then, one asserts whether the current state is as expected. "State" can be anything:
# the controller's HTTP response, the database contents, etc.
Expand Down Expand Up @@ -392,6 +392,11 @@ def post(action, *args)
process(action, "POST", *args)
end

# Executes a request simulating PATCH HTTP method and set/volley the response
def patch(action, *args)
process(action, "PATCH", *args)
end

# Executes a request simulating PUT HTTP method and set/volley the response
def put(action, *args)
process(action, "PUT", *args)
Expand Down
6 changes: 6 additions & 0 deletions actionpack/lib/action_dispatch/http/request.rb
Expand Up @@ -97,6 +97,12 @@ def post?
HTTP_METHOD_LOOKUP[request_method] == :post
end

# Is this a PATCH request?
# Equivalent to <tt>request.request_method == :patch</tt>.
def patch?
HTTP_METHOD_LOOKUP[request_method] == :patch
end

# Is this a PUT request?
# Equivalent to <tt>request.request_method_symbol == :put</tt>.
def put?
Expand Down
1 change: 1 addition & 0 deletions actionpack/lib/action_dispatch/railtie.rb
Expand Up @@ -23,6 +23,7 @@ class Railtie < Rails::Railtie
ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length
ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header
ActionDispatch::Response.default_charset = app.config.action_dispatch.default_charset || app.config.encoding
ActionDispatch::Routing::Mapper.default_method_for_update = app.config.default_method_for_update

ActionDispatch::ExceptionWrapper.rescue_responses.merge!(config.action_dispatch.rescue_responses)
ActionDispatch::ExceptionWrapper.rescue_templates.merge!(config.action_dispatch.rescue_templates)
Expand Down
15 changes: 9 additions & 6 deletions actionpack/lib/action_dispatch/routing.rb
Expand Up @@ -182,10 +182,13 @@ module ActionDispatch
#
# == HTTP Methods
#
# Using the <tt>:via</tt> option when specifying a route allows you to restrict it to a specific HTTP method.
# Possible values are <tt>:post</tt>, <tt>:get</tt>, <tt>:put</tt>, <tt>:delete</tt> and <tt>:any</tt>.
# If your route needs to respond to more than one method you can use an array, e.g. <tt>[ :get, :post ]</tt>.
# The default value is <tt>:any</tt> which means that the route will respond to any of the HTTP methods.
# Using the <tt>:via</tt> option when specifying a route allows you to
# restrict it to a specific HTTP method. Possible values are <tt>:post</tt>,
# <tt>:get</tt>, <tt>:patch</tt>, <tt>:put</tt>, <tt>:delete</tt> and
# <tt>:any</tt>. If your route needs to respond to more than one method you
# can use an array, e.g. <tt>[ :get, :post ]</tt>. The default value is
# <tt>:any</tt> which means that the route will respond to any of the HTTP
# methods.
#
# Examples:
#
Expand All @@ -198,7 +201,7 @@ module ActionDispatch
# === HTTP helper methods
#
# An alternative method of specifying which HTTP method a route should respond to is to use the helper
# methods <tt>get</tt>, <tt>post</tt>, <tt>put</tt> and <tt>delete</tt>.
# methods <tt>get</tt>, <tt>post</tt>, <tt>patch</tt>, <tt>put</tt> and <tt>delete</tt>.
#
# Examples:
#
Expand Down Expand Up @@ -283,6 +286,6 @@ module Routing
autoload :PolymorphicRoutes, 'action_dispatch/routing/polymorphic_routes'

SEPARATORS = %w( / . ? ) #:nodoc:
HTTP_METHODS = [:get, :head, :post, :put, :delete, :options] #:nodoc:
HTTP_METHODS = [:get, :head, :post, :patch, :put, :delete, :options] #:nodoc:
end
end
125 changes: 71 additions & 54 deletions actionpack/lib/action_dispatch/routing/mapper.rb
Expand Up @@ -7,6 +7,8 @@
module ActionDispatch
module Routing
class Mapper
cattr_accessor(:default_method_for_update) {:put}

class Constraints #:nodoc:
def self.new(app, constraints, request = Rack::Request)
if constraints.any?
Expand Down Expand Up @@ -465,7 +467,7 @@ module HttpHelpers
#
# Example:
#
# get 'bacon', :to => 'food#bacon'
# get 'bacon', :to => 'food#bacon'
def get(*args, &block)
map_method(:get, args, &block)
end
Expand All @@ -475,17 +477,27 @@ def get(*args, &block)
#
# Example:
#
# post 'bacon', :to => 'food#bacon'
# post 'bacon', :to => 'food#bacon'
def post(*args, &block)
map_method(:post, args, &block)
end

# Define a route that only recognizes HTTP PATCH.
# For supported arguments, see <tt>Base#match</tt>.
#
# Example:
#
# patch 'bacon', :to => 'food#bacon'
def patch(*args, &block)
map_method(:patch, args, &block)
end

# Define a route that only recognizes HTTP PUT.
# For supported arguments, see <tt>Base#match</tt>.
#
# Example:
#
# put 'bacon', :to => 'food#bacon'
# put 'bacon', :to => 'food#bacon'
def put(*args, &block)
map_method(:put, args, &block)
end
Expand All @@ -495,7 +507,7 @@ def put(*args, &block)
#
# Example:
#
# delete 'broccoli', :to => 'food#broccoli'
# delete 'broccoli', :to => 'food#broccoli'
def delete(*args, &block)
map_method(:delete, args, &block)
end
Expand All @@ -522,13 +534,13 @@ def map_method(method, args, &block)
# This will create a number of routes for each of the posts and comments
# controller. For <tt>Admin::PostsController</tt>, Rails will create:
#
# GET /admin/posts
# GET /admin/posts/new
# POST /admin/posts
# GET /admin/posts/1
# GET /admin/posts/1/edit
# PUT /admin/posts/1
# DELETE /admin/posts/1
# GET /admin/posts
# GET /admin/posts/new
# POST /admin/posts
# GET /admin/posts/1
# GET /admin/posts/1/edit
# PUT/PATCH /admin/posts/1
# DELETE /admin/posts/1
#
# If you want to route /posts (without the prefix /admin) to
# <tt>Admin::PostsController</tt>, you could use
Expand Down Expand Up @@ -556,13 +568,13 @@ def map_method(method, args, &block)
# not use scope. In the last case, the following paths map to
# +PostsController+:
#
# GET /admin/posts
# GET /admin/posts/new
# POST /admin/posts
# GET /admin/posts/1
# GET /admin/posts/1/edit
# PUT /admin/posts/1
# DELETE /admin/posts/1
# GET /admin/posts
# GET /admin/posts/new
# POST /admin/posts
# GET /admin/posts/1
# GET /admin/posts/1/edit
# PUT/PATCH /admin/posts/1
# DELETE /admin/posts/1
module Scoping
# Scopes a set of routes to the given default options.
#
Expand Down Expand Up @@ -651,13 +663,13 @@ def controller(controller, options={})
#
# This generates the following routes:
#
# admin_posts GET /admin/posts(.:format) admin/posts#index
# admin_posts POST /admin/posts(.:format) admin/posts#create
# new_admin_post GET /admin/posts/new(.:format) admin/posts#new
# edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit
# admin_post GET /admin/posts/:id(.:format) admin/posts#show
# admin_post PUT /admin/posts/:id(.:format) admin/posts#update
# admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy
# admin_posts GET /admin/posts(.:format) admin/posts#index
# admin_posts POST /admin/posts(.:format) admin/posts#create
# new_admin_post GET /admin/posts/new(.:format) admin/posts#new
# edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit
# admin_post GET /admin/posts/:id(.:format) admin/posts#show
# admin_post PUT/PATCH /admin/posts/:id(.:format) admin/posts#update
# admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy
#
# === Options
#
Expand Down Expand Up @@ -972,12 +984,12 @@ def resources_path_names(options)
# the +GeoCoders+ controller (note that the controller is named after
# the plural):
#
# GET /geocoder/new
# POST /geocoder
# GET /geocoder
# GET /geocoder/edit
# PUT /geocoder
# DELETE /geocoder
# GET /geocoder/new
# POST /geocoder
# GET /geocoder
# GET /geocoder/edit
# PUT/PATCH /geocoder
# DELETE /geocoder
#
# === Options
# Takes same options as +resources+.
Expand All @@ -1002,8 +1014,10 @@ def resource(*resources, &block)
member do
get :edit if parent_resource.actions.include?(:edit)
get :show if parent_resource.actions.include?(:show)
put :update if parent_resource.actions.include?(:update)
delete :destroy if parent_resource.actions.include?(:destroy)
if parent_resource.actions.include?(:update)
send default_method_for_update, :update
end
end
end

Expand All @@ -1020,13 +1034,13 @@ def resource(*resources, &block)
# creates seven different routes in your application, all mapping to
# the +Photos+ controller:
#
# GET /photos
# GET /photos/new
# POST /photos
# GET /photos/:id
# GET /photos/:id/edit
# PUT /photos/:id
# DELETE /photos/:id
# GET /photos
# GET /photos/new
# POST /photos
# GET /photos/:id
# GET /photos/:id/edit
# PUT/PATCH /photos/:id
# DELETE /photos/:id
#
# Resources can also be nested infinitely by using this block syntax:
#
Expand All @@ -1036,13 +1050,13 @@ def resource(*resources, &block)
#
# This generates the following comments routes:
#
# GET /photos/:photo_id/comments
# GET /photos/:photo_id/comments/new
# POST /photos/:photo_id/comments
# GET /photos/:photo_id/comments/:id
# GET /photos/:photo_id/comments/:id/edit
# PUT /photos/:photo_id/comments/:id
# DELETE /photos/:photo_id/comments/:id
# GET /photos/:photo_id/comments
# GET /photos/:photo_id/comments/new
# POST /photos/:photo_id/comments
# GET /photos/:photo_id/comments/:id
# GET /photos/:photo_id/comments/:id/edit
# PUT/PATCH /photos/:photo_id/comments/:id
# DELETE /photos/:photo_id/comments/:id
#
# === Options
# Takes same options as <tt>Base#match</tt> as well as:
Expand Down Expand Up @@ -1104,13 +1118,13 @@ def resource(*resources, &block)
#
# The +comments+ resource here will have the following routes generated for it:
#
# post_comments GET /posts/:post_id/comments(.:format)
# post_comments POST /posts/:post_id/comments(.:format)
# new_post_comment GET /posts/:post_id/comments/new(.:format)
# edit_comment GET /sekret/comments/:id/edit(.:format)
# comment GET /sekret/comments/:id(.:format)
# comment PUT /sekret/comments/:id(.:format)
# comment DELETE /sekret/comments/:id(.:format)
# post_comments GET /posts/:post_id/comments(.:format)
# post_comments POST /posts/:post_id/comments(.:format)
# new_post_comment GET /posts/:post_id/comments/new(.:format)
# edit_comment GET /sekret/comments/:id/edit(.:format)
# comment GET /sekret/comments/:id(.:format)
# comment PUT/PATCH /sekret/comments/:id(.:format)
# comment DELETE /sekret/comments/:id(.:format)
#
# === Examples
#
Expand Down Expand Up @@ -1138,11 +1152,14 @@ def resources(*resources, &block)
get :new
end if parent_resource.actions.include?(:new)

# TODO: Only accept patch or put depending on config
member do
get :edit if parent_resource.actions.include?(:edit)
get :show if parent_resource.actions.include?(:show)
put :update if parent_resource.actions.include?(:update)
delete :destroy if parent_resource.actions.include?(:destroy)
if parent_resource.actions.include?(:update)
send default_method_for_update, :update
end
end
end

Expand Down

0 comments on commit 7f2548e

Please sign in to comment.