Skip to content

Commit

Permalink
Allow respond_with to deal with http verb accordingly.
Browse files Browse the repository at this point in the history
  • Loading branch information
josevalim committed Jul 31, 2009
1 parent 7a4a679 commit 5b7e81e
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 40 deletions.
111 changes: 74 additions & 37 deletions actionpack/lib/action_controller/base/mime_responds.rb
Expand Up @@ -202,11 +202,11 @@ def respond_to(*mimes, &block)
# formats allowed:
#
# class PeopleController < ApplicationController
# respond_to :html, :xml, :json
# respond_to :xml, :json
#
# def index
# @people = Person.find(:all)
# respond_with(@person)
# respond_with(@people)
# end
# end
#
Expand All @@ -216,64 +216,101 @@ def respond_to(*mimes, &block)
#
# If neither are available, it will raise an error.
#
# Extra parameters given to respond_with are used when :to_format is invoked.
# This allows you to set status and location for several formats at the same
# time. Consider this restful controller response on create for both xml
# and json formats:
# respond_with holds semantics for each HTTP verb. The example above cover
# GET requests. Let's check a POST request example:
#
# def create
# @person = Person.new(params[:person])
# @person.save
# respond_with(@person)
# end
#
# Since the request is a POST, respond_with will check wether @people
# resource have errors or not. If it has errors, it will render the error
# object with unprocessable entity status (422).
#
# If no error was found, it will render the @people resource, with status
# created (201) and location header set to person_url(@people).
#
# If you also want to provide html behavior in the method above, you can
# supply a block to customize it:
#
# class PeopleController < ApplicationController
# respond_to :xml, :json
# respond_to :html, :xml, :json # Add :html to respond_to definition
#
# def create
# @person = Person.new(params[:person])
#
# if @person.save
# respond_with(@person, :status => :ok, :location => person_url(@person))
# else
# respond_with(@person.errors, :status => :unprocessable_entity)
# @person = Person.new(params[:pe])
#
# respond_with(@person) do |format|
# if @person.save
# flash[:notice] = 'Person was successfully created.'
# format.html { redirect_to @person }
# else
# format.html { render :action => "new" }
# end
# end
# end
# end
#
# Finally, respond_with also accepts blocks, as in respond_to. Let's take
# the same controller and create action above and add common html behavior:
# It works similarly for PUT requests:
#
# class PeopleController < ApplicationController
# respond_to :html, :xml, :json
# def update
# @person = Person.find(params[:id])
# @person.update_attributes(params[:person])
# respond_with(@person)
# end
#
# def create
# @person = Person.new(params[:person])
# In case of failures, it works as POST requests, but in success failures
# it just reply status ok (200) to the client.
#
# if @person.save
# options = { :status => :ok, :location => person_url(@person) }
# A DELETE request also works in the same way:
#
# respond_with(@person, options) do |format|
# format.html { redirect_to options[:location] }
# end
# else
# respond_with(@person.errors, :status => :unprocessable_entity) do
# format.html { render :action => :new }
# end
# end
# end
# def destroy
# @person = Person.find(params[:id])
# @person.destroy
# respond_with(@person)
# end
#
# It just replies with status ok, indicating the record was successfuly
# destroyed.
#
def respond_with(resource, options={}, &block)
begin
respond_to(&block)
rescue ActionView::MissingTemplate => e
format = self.formats.first
respond_to(&block)
rescue ActionView::MissingTemplate => e
format = self.formats.first
resource = normalize_resource_options_by_verb(resource, options)

if resource.respond_to?(:"to_#{format}")
render options.merge(format => resource)
if resource.respond_to?(:"to_#{format}")
if options.delete(:no_content)
head options
else
raise e
render options.merge(format => resource)
end
else
raise e
end
end

protected

# Change respond with behavior based on the HTTP verb.
#
def normalize_resource_options_by_verb(resource_or_array, options)
resource = resource_or_array.is_a?(Array) ? resource_or_array.last : resource_or_array
has_errors = resource.respond_to?(:errors) && !resource.errors.empty?

if has_errors && (request.post? || request.put?)
options.reverse_merge!(:status => :unprocessable_entity)
return resource.errors
elsif request.post?
options.reverse_merge!(:status => :created, :location => resource_or_array)
elsif !request.get?
options.reverse_merge!(:status => :ok, :no_content => true)
end

return resource
end

# Collect mimes declared in the class method respond_to valid for the
# current action.
#
Expand Down
65 changes: 62 additions & 3 deletions actionpack/test/controller/mime_responds_test.rb
Expand Up @@ -474,13 +474,25 @@ def test_format_with_custom_response_type_and_request_headers
class RespondResource
undef_method :to_json

def self.model_name
@_model_name ||= ActiveModel::Name.new(name)
end

def to_param
13
end

def to_xml
"XML"
end

def to_js
"JS"
end

def errors
[]
end
end

class RespondWithController < ActionController::Base
Expand Down Expand Up @@ -520,6 +532,10 @@ def _render_js(js, options)
self.content_type ||= Mime::JS
self.response_body = js.respond_to?(:to_js) ? js.to_js : js
end

def respond_resource_url(id)
request.host + "/respond/resource/#{id.to_param}"
end
end

class InheritedRespondWithController < RespondWithController
Expand Down Expand Up @@ -593,6 +609,51 @@ def test_using_resource
end
end

def test_using_resource_for_post
@request.accept = "application/xml"

post :using_resource
assert_equal "application/xml", @response.content_type
assert_equal 201, @response.status
assert_equal "XML", @response.body
assert_equal "www.example.com/respond/resource/13", @response.location

errors = { :name => :invalid }
RespondResource.any_instance.stubs(:errors).returns(errors)
post :using_resource
assert_equal "application/xml", @response.content_type
assert_equal 422, @response.status
assert_equal errors.to_xml, @response.body
assert_nil @response.location
end

def test_using_resource_for_put
@request.accept = "application/xml"

put :using_resource
assert_equal "application/xml", @response.content_type
assert_equal 200, @response.status
assert_equal " ", @response.body
assert_nil @response.location

errors = { :name => :invalid }
RespondResource.any_instance.stubs(:errors).returns(errors)
put :using_resource
assert_equal "application/xml", @response.content_type
assert_equal 422, @response.status
assert_equal errors.to_xml, @response.body
assert_nil @response.location
end

def test_using_resource_for_delete
@request.accept = "application/xml"
delete :using_resource
assert_equal "application/xml", @response.content_type
assert_equal 200, @response.status
assert_equal " ", @response.body
assert_nil @response.location
end

def test_using_resource_with_options
@request.accept = "application/xml"
get :using_resource_with_options
Expand Down Expand Up @@ -648,8 +709,6 @@ def test_not_acceptable
end

class AbstractPostController < ActionController::Base
respond_to :html, :iphone

self.view_paths = File.dirname(__FILE__) + "/../fixtures/post_test/"
end

Expand All @@ -658,7 +717,7 @@ class PostController < AbstractPostController
around_filter :with_iphone

def index
respond_to # It will use formats declared above
respond_to(:html, :iphone)
end

protected
Expand Down

0 comments on commit 5b7e81e

Please sign in to comment.