Skip to content

Commit

Permalink
Added respond_with.
Browse files Browse the repository at this point in the history
Signed-off-by: Yehuda Katz <wycats@gmail.com>
  • Loading branch information
josevalim authored and wycats committed Jul 29, 2009
1 parent bbe8607 commit 09de34c
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 27 deletions.
165 changes: 139 additions & 26 deletions actionpack/lib/action_controller/base/mime_responds.rb
Original file line number Diff line number Diff line change
Expand Up @@ -145,50 +145,163 @@ def clear_respond_to!
# environment.rb as follows.
#
# Mime::Type.register "image/jpg", :jpg
#
# Respond to also allows you to specify a common block for different formats by using any:
#
# def index
# @people = Person.find(:all)
#
# respond_to do |format|
# format.html
# format.any(:xml, :json) { render request.format.to_sym => @people }
# end
# end
#
# In the example above, if the format is xml, it will render:
#
# render :xml => @people
#
# Or if the format is json:
#
# render :json => @people
#
# Since this is a common pattern, you can use the class method respond_to
# with the respond_with method to have the same results:
#
# class PeopleController < ApplicationController
# respond_to :html, :xml, :json
#
# def index
# @people = Person.find(:all)
# respond_with(@person)

This comment has been minimized.

Copy link
@jonathan

jonathan Jul 29, 2009

looks like a typo. probably wanting @people and not @person

This comment has been minimized.

Copy link
@vijaydev

vijaydev Nov 7, 2011

Member

fixed :D

# end
# end
#
# Be sure to check respond_with and respond_to documentation for more examples.
#
def respond_to(*mimes, &block)
options = mimes.extract_options!
raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given?

resource = options.delete(:with)
responder = Responder.new
block.call(responder) if block_given?

mimes = collect_mimes_from_class_level if mimes.empty?
mimes.each { |mime| responder.send(mime) }

if format = request.negotiate_mime(responder.order)
# TODO It should be just: self.formats = [ :foo ]
self.formats = [format.to_sym]
self.content_type = format
self.template.formats = [format.to_sym]
respond_to_block_or_template_or_resource(format, resource,
options, &responder.response_for(format))
else
head :not_acceptable
end
end

if response = responder.response_for(format)
response.call
# respond_with allows you to respond an action with a given resource. It
# requires that you set your class with a :respond_to method with the
# formats allowed:
#
# class PeopleController < ApplicationController
# respond_to :html, :xml, :json
#
# def index
# @people = Person.find(:all)
# respond_with(@person)
# end
# end
#
# When a request comes with format :xml, the respond_with will first search
# for a template as person/index.xml, if the template is not available, it
# will see if the given resource responds to :to_xml.
#
# 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:
#
# class PeopleController < ApplicationController
# respond_to :xml, :json
#
# 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)
# 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:
#
# class PeopleController < ApplicationController
# respond_to :html, :xml, :json
#
# def create
# @person = Person.new(params[:person])
#
# if @person.save
# options = { :status => :ok, :location => person_url(@person) }
#
# 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
# end
#
def respond_with(resource, options={}, &block)
respond_to(options.merge!(:with => resource), &block)
end

protected

def respond_to_block_or_template_or_resource(format, resource, options)
# TODO It should be just: self.formats = [ :foo ]
self.formats = [format.to_sym]
self.content_type = format
self.template.formats = [format.to_sym]

return yield if block_given?

begin
default_render
rescue ActionView::MissingTemplate => e
if resource && resource.respond_to?("to_#{format.to_sym}")
render options.merge(format.to_sym => resource)
else
default_render
raise e
end
else
head :not_acceptable
end
end

protected
# Collect mimes declared in the class method respond_to valid for the
# current action.
#
def collect_mimes_from_class_level #:nodoc:
action = action_name.to_sym

# Collect mimes declared in the class method respond_to valid for the
# current action.
#
def collect_mimes_from_class_level #:nodoc:
action = action_name.to_sym

mimes_for_respond_to.keys.select do |mime|
config = mimes_for_respond_to[mime]

if config[:except]
!config[:except].include?(action)
elsif config[:only]
config[:only].include?(action)
else
true
end
mimes_for_respond_to.keys.select do |mime|
config = mimes_for_respond_to[mime]

if config[:except]
!config[:except].include?(action)
elsif config[:only]
config[:only].include?(action)
else
true
end
end
end

class Responder #:nodoc:
attr_accessor :order
Expand Down
70 changes: 69 additions & 1 deletion actionpack/test/controller/mime_responds_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -471,8 +471,20 @@ def test_format_with_custom_response_type_and_request_headers
end
end

class RespondResource
undef_method :to_json

def to_xml
"XML"
end

def to_js
"JS"
end
end

class RespondWithController < ActionController::Base
respond_to :html
respond_to :html, :json
respond_to :xml, :except => :using_defaults
respond_to :js, :only => :using_defaults

Expand All @@ -485,6 +497,23 @@ def using_defaults
def using_defaults_with_type_list
respond_to(:js, :xml)
end

def using_resource
respond_with(RespondResource.new)
end

def using_resource_with_options
respond_with(RespondResource.new, :status => :unprocessable_entity) do |format|
format.js
end
end

protected

def _render_js(js, options)
self.content_type ||= Mime::JS
self.response_body = js.respond_to?(:to_js) ? js.to_js : js
end
end

class RespondWithControllerTest < ActionController::TestCase
Expand Down Expand Up @@ -530,6 +559,37 @@ def test_using_defaults_with_type_list
assert_equal "<p>Hello world!</p>\n", @response.body
end

def test_using_resource
@request.accept = "text/html"
get :using_resource
assert_equal "text/html", @response.content_type
assert_equal "Hello world!", @response.body

@request.accept = "application/xml"
get :using_resource
assert_equal "application/xml", @response.content_type
assert_equal "XML", @response.body

@request.accept = "application/json"
assert_raise ActionView::MissingTemplate do
get :using_resource
end
end

def test_using_resource_with_options
@request.accept = "application/xml"
get :using_resource_with_options
assert_equal "application/xml", @response.content_type
assert_equal 422, @response.status
assert_equal "XML", @response.body

@request.accept = "text/javascript"
get :using_resource_with_options
assert_equal "text/javascript", @response.content_type
assert_equal 422, @response.status
assert_equal "JS", @response.body
end

def test_not_acceptable
@request.accept = "application/xml"
get :using_defaults
Expand All @@ -538,6 +598,14 @@ def test_not_acceptable
@request.accept = "text/html"
get :using_defaults_with_type_list
assert_equal 406, @response.status

@request.accept = "application/json"
get :using_defaults_with_type_list
assert_equal 406, @response.status

@request.accept = "text/javascript"
get :using_resource
assert_equal 406, @response.status
end
end

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello world!

3 comments on commit 09de34c

@jonathan
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, the magic here seems to be that you don't really need a controller if you are using all restful actions. You could create a RESTcontroller < ApplictionController and have your PersonController < RESTController. Have your RESTcontroller have all your default rest actions and use @obj instead of @person and @options instead of options and set those with a before filter. Then, in your PersonController, just define the methods and away you go.

At least, that's a potential I see. I could be missing something.

@josevalim
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's a typo. Thanks!
And about your second comment, you are right too. Check this: http://github.com/josevalim/inherited_resources.

@gnoso
Copy link

@gnoso gnoso commented on 09de34c Aug 20, 2009

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wondering, but how would one use this along with options to the to_xml or to_json method for the model?

Please sign in to comment.