29294476f7241b4f50f872c50d89c009b51215a2
[depot.git] / mime_responds.rb
1 module ActionController #:nodoc:
2 module MimeResponds #:nodoc:
3 def self.included(base)
4 base.module_eval do
5 include ActionController::MimeResponds::InstanceMethods
6 end
7 end
8
9 module InstanceMethods
10 # Without web-service support, an action which collects the data for displaying a list of people
11 # might look something like this:
12 #
13 # def index
14 # @people = Person.find(:all)
15 # end
16 #
17 # Here's the same action, with web-service support baked in:
18 #
19 # def index
20 # @people = Person.find(:all)
21 #
22 # respond_to do |format|
23 # format.html
24 # format.xml { render :xml => @people.to_xml }
25 # end
26 # end
27 #
28 # What that says is, "if the client wants HTML in response to this action, just respond as we
29 # would have before, but if the client wants XML, return them the list of people in XML format."
30 # (Rails determines the desired response format from the HTTP Accept header submitted by the client.)
31 #
32 # Supposing you have an action that adds a new person, optionally creating their company
33 # (by name) if it does not already exist, without web-services, it might look like this:
34 #
35 # def create
36 # @company = Company.find_or_create_by_name(params[:company][:name])
37 # @person = @company.people.create(params[:person])
38 #
39 # redirect_to(person_list_url)
40 # end
41 #
42 # Here's the same action, with web-service support baked in:
43 #
44 # def create
45 # company = params[:person].delete(:company)
46 # @company = Company.find_or_create_by_name(company[:name])
47 # @person = @company.people.create(params[:person])
48 #
49 # respond_to do |format|
50 # format.html { redirect_to(person_list_url) }
51 # format.js
52 # format.xml { render :xml => @person.to_xml(:include => @company) }
53 # end
54 # end
55 #
56 # If the client wants HTML, we just redirect them back to the person list. If they want Javascript
57 # (format.js), then it is an RJS request and we render the RJS template associated with this action.
58 # Lastly, if the client wants XML, we render the created person as XML, but with a twist: we also
59 # include the person's company in the rendered XML, so you get something like this:
60 #
61 # <person>
62 # <id>...</id>
63 # ...
64 # <company>
65 # <id>...</id>
66 # <name>...</name>
67 # ...
68 # </company>
69 # </person>
70 #
71 # Note, however, the extra bit at the top of that action:
72 #
73 # company = params[:person].delete(:company)
74 # @company = Company.find_or_create_by_name(company[:name])
75 #
76 # This is because the incoming XML document (if a web-service request is in process) can only contain a
77 # single root-node. So, we have to rearrange things so that the request looks like this (url-encoded):
78 #
79 # person[name]=...&person[company][name]=...&...
80 #
81 # And, like this (xml-encoded):
82 #
83 # <person>
84 # <name>...</name>
85 # <company>
86 # <name>...</name>
87 # </company>
88 # </person>
89 #
90 # In other words, we make the request so that it operates on a single entity's person. Then, in the action,
91 # we extract the company data from the request, find or create the company, and then create the new person
92 # with the remaining data.
93 #
94 # Note that you can define your own XML parameter parser which would allow you to describe multiple entities
95 # in a single request (i.e., by wrapping them all in a single root node), but if you just go with the flow
96 # and accept Rails' defaults, life will be much easier.
97 #
98 # If you need to use a MIME type which isn't supported by default, you can register your own handlers in
99 # environment.rb as follows.
100 #
101 # Mime::Type.register "image/jpg", :jpg
102 def respond_to(*types, &block)
103 raise ArgumentError, "respond_to takes either types or a block, never both" unless types.any? ^ block
104 block ||= lambda { |responder| types.each { |type| responder.send(type) } }
105 responder = Responder.new(self)
106 block.call(responder)
107 responder.respond
108 end
109 end
110
111 class Responder #:nodoc:
112 def initialize(controller)
113 @controller = controller
114 @request = controller.request
115 @response = controller.response
116
117 if ActionController::Base.use_accept_header
118 @mime_type_priority = Array(Mime::Type.lookup_by_extension(@request.parameters[:format]) || @request.accepts)
119 else
120 @mime_type_priority = [@request.format]
121 end
122
123 @order = []
124 @responses = {}
125 end
126
127 def custom(mime_type, &block)
128 mime_type = mime_type.is_a?(Mime::Type) ? mime_type : Mime::Type.lookup(mime_type.to_s)
129
130 @order << mime_type
131
132 @responses[mime_type] ||= Proc.new do
133 @response.template.template_format = mime_type.to_sym
134 @response.content_type = mime_type.to_s
135 block_given? ? block.call : @controller.send(:render, :action => @controller.action_name)
136 end
137 end
138
139 def any(*args, &block)
140 if args.any?
141 args.each { |type| send(type, &block) }
142 else
143 custom(@mime_type_priority.first, &block)
144 end
145 end
146
147 def method_missing(symbol, &block)
148 mime_constant = symbol.to_s.upcase
149
150 if Mime::SET.include?(Mime.const_get(mime_constant))
151 custom(Mime.const_get(mime_constant), &block)
152 else
153 super
154 end
155 end
156
157 def respond
158 for priority in @mime_type_priority
159 if priority == Mime::ALL
160 @responses[@order.first].call
161 return
162 else
163 if @responses[priority]
164 @responses[priority].call
165 return # mime type match found, be happy and return
166 end
167 end
168 end
169
170 if @order.include?(Mime::ALL)
171 @responses[Mime::ALL].call
172 else
173 @controller.send :head, :not_acceptable
174 end
175 end
176 end
177 end
178 end