Skip to content

Commit

Permalink
Introduce a default respond_to block for custom types. Closes #8174.
Browse files Browse the repository at this point in the history
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@6856 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information
jeremy committed May 26, 2007
1 parent d0d5a1f commit 672941d
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 98 deletions.
2 changes: 2 additions & 0 deletions actionpack/CHANGELOG
@@ -1,5 +1,7 @@
*SVN* *SVN*


* Introduce a default respond_to block for custom types. #8174 [Josh Peek]

* auto_complete_field takes a :method option so you can GET or POST. #8120 [zapnap] * auto_complete_field takes a :method option so you can GET or POST. #8120 [zapnap]


* Added option to suppress :size when using :maxlength for FormTagHelper#text_field #3112 [rails@tpope.info] * Added option to suppress :size when using :maxlength for FormTagHelper#text_field #3112 [rails@tpope.info]
Expand Down
126 changes: 57 additions & 69 deletions actionpack/lib/action_controller/mime_responds.rb
Expand Up @@ -11,51 +11,51 @@ module InstanceMethods
# def index # def index
# @people = Person.find(:all) # @people = Person.find(:all)
# end # end
# #
# Here's the same action, with web-service support baked in: # Here's the same action, with web-service support baked in:
# #
# def index # def index
# @people = Person.find(:all) # @people = Person.find(:all)
# #
# respond_to do |format| # respond_to do |format|
# format.html # format.html
# format.xml { render :xml => @people.to_xml } # format.xml { render :xml => @people.to_xml }
# end # end
# end # end
# #
# What that says is, "if the client wants HTML in response to this action, just respond as we # What that says is, "if the client wants HTML in response to this action, just respond as we
# would have before, but if the client wants XML, return them the list of people in XML format." # would have before, but if the client wants XML, return them the list of people in XML format."
# (Rails determines the desired response format from the HTTP Accept header submitted by the client.) # (Rails determines the desired response format from the HTTP Accept header submitted by the client.)
# #
# Supposing you have an action that adds a new person, optionally creating their company # Supposing you have an action that adds a new person, optionally creating their company
# (by name) if it does not already exist, without web-services, it might look like this: # (by name) if it does not already exist, without web-services, it might look like this:
# #
# def create # def create
# @company = Company.find_or_create_by_name(params[:company][:name]) # @company = Company.find_or_create_by_name(params[:company][:name])
# @person = @company.people.create(params[:person]) # @person = @company.people.create(params[:person])
# #
# redirect_to(person_list_url) # redirect_to(person_list_url)
# end # end
# #
# Here's the same action, with web-service support baked in: # Here's the same action, with web-service support baked in:
# #
# def create # def create
# company = params[:person].delete(:company) # company = params[:person].delete(:company)
# @company = Company.find_or_create_by_name(company[:name]) # @company = Company.find_or_create_by_name(company[:name])
# @person = @company.people.create(params[:person]) # @person = @company.people.create(params[:person])
# #
# respond_to do |format| # respond_to do |format|
# format.html { redirect_to(person_list_url) } # format.html { redirect_to(person_list_url) }
# format.js # format.js
# format.xml { render :xml => @person.to_xml(:include => @company) } # format.xml { render :xml => @person.to_xml(:include => @company) }
# end # end
# end # end
# #
# If the client wants HTML, we just redirect them back to the person list. If they want Javascript # If the client wants HTML, we just redirect them back to the person list. If they want Javascript
# (wants.js), then it is an RJS request and we render the RJS template associated with this action. # (wants.js), then it is an RJS request and we render the RJS template associated with this action.
# Lastly, if the client wants XML, we render the created person as XML, but with a twist: we also # Lastly, if the client wants XML, we render the created person as XML, but with a twist: we also
# include the persons company in the rendered XML, so you get something like this: # include the person's company in the rendered XML, so you get something like this:
# #
# <person> # <person>
# <id>...</id> # <id>...</id>
# ... # ...
Expand All @@ -65,108 +65,96 @@ module InstanceMethods
# ... # ...
# </company> # </company>
# </person> # </person>
# #
# Note, however, the extra bit at the top of that action: # Note, however, the extra bit at the top of that action:
# #
# company = params[:person].delete(:company) # company = params[:person].delete(:company)
# @company = Company.find_or_create_by_name(company[:name]) # @company = Company.find_or_create_by_name(company[:name])
# #
# This is because the incoming XML document (if a web-service request is in process) can only contain a # This is because the incoming XML document (if a web-service request is in process) can only contain a
# single root-node. So, we have to rearrange things so that the request looks like this (url-encoded): # single root-node. So, we have to rearrange things so that the request looks like this (url-encoded):
# #
# person[name]=...&person[company][name]=...&... # person[name]=...&person[company][name]=...&...
# #
# And, like this (xml-encoded): # And, like this (xml-encoded):
# #
# <person> # <person>
# <name>...</name> # <name>...</name>
# <company> # <company>
# <name>...</name> # <name>...</name>
# </company> # </company>
# </person> # </person>
# #
# In other words, we make the request so that it operates on a single entity—a person. Then, in the action, # In other words, we make the request so that it operates on a single entity's person. Then, in the action,
# we extract the company data from the request, find or create the company, and then create the new person # we extract the company data from the request, find or create the company, and then create the new person
# with the remaining data. # with the remaining data.
# #
# Note that you can define your own XML parameter parser which would allow you to describe multiple entities # Note that you can define your own XML parameter parser which would allow you to describe multiple entities
# in a single request (i.e., by wrapping them all in a single root note), but if you just go with the flow # in a single request (i.e., by wrapping them all in a single root note), but if you just go with the flow
# and accept Rails' defaults, life will be much easier. # and accept Rails' defaults, life will be much easier.
# #
# If you need to use a MIME type which isn't supported by default, you can register your own handlers in # If you need to use a MIME type which isn't supported by default, you can register your own handlers in
# environment.rb as follows. # environment.rb as follows.
# #
# Mime::Type.register "image/jpg", :jpg # Mime::Type.register "image/jpg", :jpg
def respond_to(*types, &block) def respond_to(*types, &block)
raise ArgumentError, "respond_to takes either types or a block, never both" unless types.any? ^ block raise ArgumentError, "respond_to takes either types or a block, never both" unless types.any? ^ block
block ||= lambda { |responder| types.each { |type| responder.send(type) } } block ||= lambda { |responder| types.each { |type| responder.send(type) } }
responder = Responder.new(block.binding) responder = Responder.new(self)
block.call(responder) block.call(responder)
responder.respond responder.respond
end end
end end

class Responder #:nodoc: class Responder #:nodoc:
default_block_format = <<-END def initialize(controller)
Proc.new { @controller = controller
@template.template_format = '%s' @request = controller.request
render :action => "\#{action_name}", :content_type => Mime::%s @response = controller.response
}
END


DEFAULT_BLOCKS = [:html, :js, :xml].inject({}) do |memo, ext| format = @request.parameters[:format]
default_block = default_block_format % [ext, ext.to_s.upcase] @mime_type_priority = format && Mime::EXTENSION_LOOKUP[format] ?
memo.update(ext => default_block) [ Mime::EXTENSION_LOOKUP[format] ] :
end @request.accepts

def initialize(block_binding)
@block_binding = block_binding
@mime_type_priority = eval(
"(params[:format] && Mime::EXTENSION_LOOKUP[params[:format]]) ? " +
"[ Mime::EXTENSION_LOOKUP[params[:format]] ] : request.accepts",
block_binding
)


@order = [] @order = []
@responses = {} @responses = {}
end end


def custom(mime_type, &block) def custom(mime_type, &block)
mime_type = mime_type.is_a?(Mime::Type) ? mime_type : Mime::Type.lookup(mime_type.to_s) mime_type = mime_type.is_a?(Mime::Type) ? mime_type : Mime::Type.lookup(mime_type.to_s)

@order << mime_type @order << mime_type

if block_given? if block_given?
@responses[mime_type] = Proc.new do @responses[mime_type] = Proc.new do
eval <<-END, @block_binding @response.template.template_format = mime_type.to_sym
@template.template_format = '#{mime_type.to_sym}' @response.content_type = mime_type.to_s
response.content_type = '#{mime_type.to_s}'
END
block.call block.call
end end
else else
if source = DEFAULT_BLOCKS[mime_type.to_sym] @responses[mime_type] = Proc.new do
@responses[mime_type] = eval(source, @block_binding) @response.template.template_format = mime_type.to_sym
else @response.content_type = mime_type.to_s
raise ActionController::RenderError, "Expected a block but none was given for custom mime handler #{mime_type}" @controller.send :render, :action => @controller.action_name
end end
end end
end end


def any(*args, &block) def any(*args, &block)
args.each { |type| send(type, &block) } args.each { |type| send(type, &block) }
end end

def method_missing(symbol, &block) def method_missing(symbol, &block)
mime_constant = symbol.to_s.upcase mime_constant = symbol.to_s.upcase

if Mime::SET.include?(Mime.const_get(mime_constant)) if Mime::SET.include?(Mime.const_get(mime_constant))
custom(Mime.const_get(mime_constant), &block) custom(Mime.const_get(mime_constant), &block)
else else
super super
end end
end end

def respond def respond
for priority in @mime_type_priority for priority in @mime_type_priority
if priority == Mime::ALL if priority == Mime::ALL
Expand All @@ -179,11 +167,11 @@ def respond
end end
end end
end end

if @order.include?(Mime::ALL) if @order.include?(Mime::ALL)
@responses[Mime::ALL].call @responses[Mime::ALL].call
else else
eval 'render(:nothing => true, :status => "406 Not Acceptable")', @block_binding @controller.send :head, :not_acceptable
end end
end end
end end
Expand Down

0 comments on commit 672941d

Please sign in to comment.