Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 451 lines (420 sloc) 15.153 kb
1c16649 @dhh Added better support for using the same actions to output for differe…
dhh authored
1 module ActionController #:nodoc:
2 module MimeResponds #:nodoc:
3e8ba61 @josevalim Refactor even more Responder. Move mime negotiation to request and ad…
josevalim authored
3 extend ActiveSupport::Concern
4
5 included do
6 class_inheritable_reader :mimes_for_respond_to
fa0cf66 @josevalim Add a couple more tests to respond_with.
josevalim authored
7 clear_respond_to
3e8ba61 @josevalim Refactor even more Responder. Move mime negotiation to request and ad…
josevalim authored
8 end
9
10 module ClassMethods
11 # Defines mimes that are rendered by default when invoking respond_with.
12 #
13 # Examples:
14 #
15 # respond_to :html, :xml, :json
16 #
17 # All actions on your controller will respond to :html, :xml and :json.
18 #
19 # But if you want to specify it based on your actions, you can use only and
20 # except:
21 #
22 # respond_to :html
23 # respond_to :xml, :json, :except => [ :edit ]
24 #
25 # The definition above explicits that all actions respond to :html. And all
26 # actions except :edit respond to :xml and :json.
27 #
28 # You can specify also only parameters:
29 #
30 # respond_to :rjs, :only => :create
31 #
32 def respond_to(*mimes)
33 options = mimes.extract_options!
34
35 only_actions = Array(options.delete(:only))
36 except_actions = Array(options.delete(:except))
37
38 mimes.each do |mime|
39 mime = mime.to_sym
fa0cf66 @josevalim Add a couple more tests to respond_with.
josevalim authored
40 mimes_for_respond_to[mime] = {}
41 mimes_for_respond_to[mime][:only] = only_actions unless only_actions.empty?
42 mimes_for_respond_to[mime][:except] = except_actions unless except_actions.empty?
3e8ba61 @josevalim Refactor even more Responder. Move mime negotiation to request and ad…
josevalim authored
43 end
44 end
45
46 # Clear all mimes in respond_to.
47 #
fa0cf66 @josevalim Add a couple more tests to respond_with.
josevalim authored
48 def clear_respond_to
49 write_inheritable_attribute(:mimes_for_respond_to, ActiveSupport::OrderedHash.new)
3e8ba61 @josevalim Refactor even more Responder. Move mime negotiation to request and ad…
josevalim authored
50 end
51 end
3f445b3 @josevalim Refactor Responder to only calculate available mime types. Those are …
josevalim authored
52
01f032f Added responds_to to new base.
Yehuda Katz + Carl Lerche authored
53 # Without web-service support, an action which collects the data for displaying a list of people
54 # might look something like this:
55 #
56 # def index
57 # @people = Person.find(:all)
58 # end
59 #
60 # Here's the same action, with web-service support baked in:
61 #
62 # def index
63 # @people = Person.find(:all)
64 #
65 # respond_to do |format|
66 # format.html
67 # format.xml { render :xml => @people.to_xml }
68 # end
69 # end
70 #
71 # What that says is, "if the client wants HTML in response to this action, just respond as we
72 # would have before, but if the client wants XML, return them the list of people in XML format."
73 # (Rails determines the desired response format from the HTTP Accept header submitted by the client.)
74 #
75 # Supposing you have an action that adds a new person, optionally creating their company
76 # (by name) if it does not already exist, without web-services, it might look like this:
77 #
78 # def create
79 # @company = Company.find_or_create_by_name(params[:company][:name])
80 # @person = @company.people.create(params[:person])
81 #
82 # redirect_to(person_list_url)
83 # end
84 #
85 # Here's the same action, with web-service support baked in:
86 #
87 # def create
88 # company = params[:person].delete(:company)
89 # @company = Company.find_or_create_by_name(company[:name])
90 # @person = @company.people.create(params[:person])
91 #
92 # respond_to do |format|
93 # format.html { redirect_to(person_list_url) }
94 # format.js
95 # format.xml { render :xml => @person.to_xml(:include => @company) }
96 # end
97 # end
98 #
99 # If the client wants HTML, we just redirect them back to the person list. If they want Javascript
100 # (format.js), then it is an RJS request and we render the RJS template associated with this action.
101 # Lastly, if the client wants XML, we render the created person as XML, but with a twist: we also
102 # include the person's company in the rendered XML, so you get something like this:
103 #
104 # <person>
105 # <id>...</id>
106 # ...
107 # <company>
108 # <id>...</id>
109 # <name>...</name>
110 # ...
111 # </company>
112 # </person>
113 #
114 # Note, however, the extra bit at the top of that action:
115 #
116 # company = params[:person].delete(:company)
117 # @company = Company.find_or_create_by_name(company[:name])
118 #
119 # This is because the incoming XML document (if a web-service request is in process) can only contain a
120 # single root-node. So, we have to rearrange things so that the request looks like this (url-encoded):
121 #
122 # person[name]=...&person[company][name]=...&...
123 #
124 # And, like this (xml-encoded):
125 #
126 # <person>
127 # <name>...</name>
128 # <company>
129 # <name>...</name>
130 # </company>
131 # </person>
132 #
133 # In other words, we make the request so that it operates on a single entity's person. Then, in the action,
134 # we extract the company data from the request, find or create the company, and then create the new person
135 # with the remaining data.
136 #
137 # Note that you can define your own XML parameter parser which would allow you to describe multiple entities
138 # in a single request (i.e., by wrapping them all in a single root node), but if you just go with the flow
139 # and accept Rails' defaults, life will be much easier.
140 #
141 # If you need to use a MIME type which isn't supported by default, you can register your own handlers in
142 # environment.rb as follows.
143 #
144 # Mime::Type.register "image/jpg", :jpg
09de34c @josevalim Added respond_with.
josevalim authored
145 #
146 # Respond to also allows you to specify a common block for different formats by using any:
147 #
148 # def index
149 # @people = Person.find(:all)
150 #
151 # respond_to do |format|
152 # format.html
153 # format.any(:xml, :json) { render request.format.to_sym => @people }
154 # end
155 # end
156 #
157 # In the example above, if the format is xml, it will render:
158 #
159 # render :xml => @people
160 #
161 # Or if the format is json:
162 #
163 # render :json => @people
164 #
165 # Since this is a common pattern, you can use the class method respond_to
166 # with the respond_with method to have the same results:
167 #
168 # class PeopleController < ApplicationController
169 # respond_to :html, :xml, :json
170 #
171 # def index
172 # @people = Person.find(:all)
173 # respond_with(@person)
174 # end
175 # end
176 #
177 # Be sure to check respond_with and respond_to documentation for more examples.
178 #
3f445b3 @josevalim Refactor Responder to only calculate available mime types. Those are …
josevalim authored
179 def respond_to(*mimes, &block)
67b2d08 @josevalim Ensure that the proper accept header value is set during tests.
josevalim authored
180 raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given?
09de34c @josevalim Added respond_with.
josevalim authored
181
3e8ba61 @josevalim Refactor even more Responder. Move mime negotiation to request and ad…
josevalim authored
182 responder = Responder.new
183 mimes = collect_mimes_from_class_level if mimes.empty?
bbe8607 @josevalim Added tests for respond_to class method.
josevalim authored
184 mimes.each { |mime| responder.send(mime) }
fa0cf66 @josevalim Add a couple more tests to respond_with.
josevalim authored
185 block.call(responder) if block_given?
7af12d0 @dhh Added synonym and custom type handling to respond_to [DHH]
dhh authored
186
3e8ba61 @josevalim Refactor even more Responder. Move mime negotiation to request and ad…
josevalim authored
187 if format = request.negotiate_mime(responder.order)
7a4a679 @josevalim Remove any resource logic from respond_to.
josevalim authored
188 self.formats = [format.to_sym]
189
190 if response = responder.response_for(format)
191 response.call
192 else
193 default_render
194 end
09de34c @josevalim Added respond_with.
josevalim authored
195 else
196 head :not_acceptable
197 end
198 end
672941d @jeremy Introduce a default respond_to block for custom types. Closes #8174.
jeremy authored
199
09de34c @josevalim Added respond_with.
josevalim authored
200 # respond_with allows you to respond an action with a given resource. It
f59984c @josevalim Add nagivational behavior to respond_with.
josevalim authored
201 # requires that you set your class with a respond_to method with the
09de34c @josevalim Added respond_with.
josevalim authored
202 # formats allowed:
203 #
204 # class PeopleController < ApplicationController
f59984c @josevalim Add nagivational behavior to respond_with.
josevalim authored
205 # respond_to :html, :xml, :json
09de34c @josevalim Added respond_with.
josevalim authored
206 #
207 # def index
208 # @people = Person.find(:all)
5b7e81e @josevalim Allow respond_with to deal with http verb accordingly.
josevalim authored
209 # respond_with(@people)
09de34c @josevalim Added respond_with.
josevalim authored
210 # end
211 # end
212 #
f59984c @josevalim Add nagivational behavior to respond_with.
josevalim authored
213 # When a request comes, for example with format :xml, three steps happen:
214 #
215 # 1) respond_with searches for a template at people/index.xml;
216 #
217 # 2) if the template is not available, it will check if the given
218 # resource responds to :to_xml.
219 #
220 # 3) if a :location option was provided, redirect to the location with
221 # redirect status if a string was given, or render an action if a
222 # symbol was given.
223 #
224 # If all steps fail, a missing template error will be raised.
225 #
226 # === Supported options
227 #
228 # [status]
229 # Sets the response status.
230 #
231 # [head]
232 # Tell respond_with to set the content type, status and location header,
233 # but do not render the object, leaving the response body empty. This
234 # option only has effect if the resource is being rendered. If a
235 # template was found, it's going to be rendered anyway.
236 #
237 # [location]
238 # Sets the location header with the given value. It accepts a string,
239 # representing the location header value, or a symbol representing an
240 # action name.
241 #
242 # === Builtin HTTP verb semantics
09de34c @josevalim Added respond_with.
josevalim authored
243 #
f59984c @josevalim Add nagivational behavior to respond_with.
josevalim authored
244 # respond_with holds semantics for each HTTP verb. Depending on the verb
245 # and the resource status, respond_with will automatically set the options
246 # above.
09de34c @josevalim Added respond_with.
josevalim authored
247 #
f59984c @josevalim Add nagivational behavior to respond_with.
josevalim authored
248 # Above we saw an example for GET requests, where actually no option is
249 # configured. A create action for POST requests, could be written as:
5b7e81e @josevalim Allow respond_with to deal with http verb accordingly.
josevalim authored
250 #
251 # def create
252 # @person = Person.new(params[:person])
253 # @person.save
254 # respond_with(@person)
255 # end
256 #
f59984c @josevalim Add nagivational behavior to respond_with.
josevalim authored
257 # respond_with will inspect the @person object and check if we have any
258 # error. If errors are empty, it will add status and location to the options
259 # hash. Then the create action in case of success, is equivalent to this:
5b7e81e @josevalim Allow respond_with to deal with http verb accordingly.
josevalim authored
260 #
f59984c @josevalim Add nagivational behavior to respond_with.
josevalim authored
261 # respond_with(@person, :status => :created, :location => @person)
5b7e81e @josevalim Allow respond_with to deal with http verb accordingly.
josevalim authored
262 #
f59984c @josevalim Add nagivational behavior to respond_with.
josevalim authored
263 # From them on, the lookup happens as described above. Let's suppose a :xml
264 # request and we don't have a people/create.xml template. But since the
265 # @person object responds to :to_xml, it will render the newly created
266 # resource and set status and location.
09de34c @josevalim Added respond_with.
josevalim authored
267 #
f59984c @josevalim Add nagivational behavior to respond_with.
josevalim authored
268 # However, if the request is :html, a template is not available and @person
269 # does not respond to :to_html. But since a :location options was provided,
270 # it will redirect to it.
271 #
272 # In case of failures (when the @person could not be saved and errors are
273 # not empty), respond_with can be expanded as this:
274 #
275 # respond_with(@person.errors, :status => :unprocessable_entity, :location => :new)
276 #
277 # In other words, respond_with(@person) for POST requests is expanded
278 # internally into this:
279 #
280 # def create
281 # @person = Person.new(params[:person])
282 #
283 # if @person.save
284 # respond_with(@person, :status => :created, :location => @person)
285 # else
286 # respond_with(@person.errors, :status => :unprocessable_entity, :location => :new)
09de34c @josevalim Added respond_with.
josevalim authored
287 # end
288 # end
289 #
f59984c @josevalim Add nagivational behavior to respond_with.
josevalim authored
290 # For an update action for PUT requests, we would have:
09de34c @josevalim Added respond_with.
josevalim authored
291 #
5b7e81e @josevalim Allow respond_with to deal with http verb accordingly.
josevalim authored
292 # def update
293 # @person = Person.find(params[:id])
294 # @person.update_attributes(params[:person])
295 # respond_with(@person)
296 # end
09de34c @josevalim Added respond_with.
josevalim authored
297 #
f59984c @josevalim Add nagivational behavior to respond_with.
josevalim authored
298 # Which, in face of success and failure scenarios, can be expanded as:
09de34c @josevalim Added respond_with.
josevalim authored
299 #
f59984c @josevalim Add nagivational behavior to respond_with.
josevalim authored
300 # def update
301 # @person = Person.find(params[:id])
302 # @person.update_attributes(params[:person])
303 #
304 # if @person.save
305 # respond_with(@person, :status => :ok, :location => @person, :head => true)
306 # else
307 # respond_with(@person.errors, :status => :unprocessable_entity, :location => :edit)
308 # end
309 # end
310 #
311 # Notice that in case of success, we just need to reply :ok to the client.
312 # The option :head ensures that the object is not rendered.
313 #
314 # Finally, we have the destroy action with DELETE verb:
09de34c @josevalim Added respond_with.
josevalim authored
315 #
5b7e81e @josevalim Allow respond_with to deal with http verb accordingly.
josevalim authored
316 # def destroy
317 # @person = Person.find(params[:id])
318 # @person.destroy
319 # respond_with(@person)
09de34c @josevalim Added respond_with.
josevalim authored
320 # end
321 #
f59984c @josevalim Add nagivational behavior to respond_with.
josevalim authored
322 # Which is expanded as:
323 #
324 # def destroy
325 # @person = Person.find(params[:id])
326 # @person.destroy
327 # respond_with(@person, :status => :ok, :location => @person, :head => true)
328 # end
329 #
330 # In this case, since @person.destroyed? returns true, polymorphic urls will
331 # redirect to the collection url, instead of the resource url.
5b7e81e @josevalim Allow respond_with to deal with http verb accordingly.
josevalim authored
332 #
09de34c @josevalim Added respond_with.
josevalim authored
333 def respond_with(resource, options={}, &block)
5b7e81e @josevalim Allow respond_with to deal with http verb accordingly.
josevalim authored
334 respond_to(&block)
335 rescue ActionView::MissingTemplate => e
336 format = self.formats.first
337 resource = normalize_resource_options_by_verb(resource, options)
f59984c @josevalim Add nagivational behavior to respond_with.
josevalim authored
338 action = options.delete(:location) if options[:location].is_a?(Symbol)
7a4a679 @josevalim Remove any resource logic from respond_to.
josevalim authored
339
5b7e81e @josevalim Allow respond_with to deal with http verb accordingly.
josevalim authored
340 if resource.respond_to?(:"to_#{format}")
f59984c @josevalim Add nagivational behavior to respond_with.
josevalim authored
341 options.delete(:head) ? head(options) : render(options.merge(format => resource))
342 elsif action
343 render :action => action
344 elsif options[:location]
345 redirect_to options[:location]
5b7e81e @josevalim Allow respond_with to deal with http verb accordingly.
josevalim authored
346 else
347 raise e
3f445b3 @josevalim Refactor Responder to only calculate available mime types. Those are …
josevalim authored
348 end
349 end
350
7a4a679 @josevalim Remove any resource logic from respond_to.
josevalim authored
351 protected
352
5b7e81e @josevalim Allow respond_with to deal with http verb accordingly.
josevalim authored
353 # Change respond with behavior based on the HTTP verb.
354 #
355 def normalize_resource_options_by_verb(resource_or_array, options)
f59984c @josevalim Add nagivational behavior to respond_with.
josevalim authored
356 resource = resource_or_array.is_a?(Array) ? resource_or_array.last : resource_or_array
5b7e81e @josevalim Allow respond_with to deal with http verb accordingly.
josevalim authored
357
f59984c @josevalim Add nagivational behavior to respond_with.
josevalim authored
358 if resource.respond_to?(:errors) && !resource.errors.empty?
359 options[:status] ||= :unprocessable_entity
360 options[:location] ||= :new if request.post?
361 options[:location] ||= :edit if request.put?
5b7e81e @josevalim Allow respond_with to deal with http verb accordingly.
josevalim authored
362 return resource.errors
363 elsif !request.get?
f59984c @josevalim Add nagivational behavior to respond_with.
josevalim authored
364 options[:location] ||= resource_or_array
365
366 if request.post?
367 options[:status] ||= :created
368 else
369 options[:status] ||= :ok
370 options[:head] = true unless options.key?(:head)
371 end
5b7e81e @josevalim Allow respond_with to deal with http verb accordingly.
josevalim authored
372 end
373
374 return resource
375 end
376
09de34c @josevalim Added respond_with.
josevalim authored
377 # Collect mimes declared in the class method respond_to valid for the
378 # current action.
379 #
380 def collect_mimes_from_class_level #:nodoc:
381 action = action_name.to_sym
3e8ba61 @josevalim Refactor even more Responder. Move mime negotiation to request and ad…
josevalim authored
382
09de34c @josevalim Added respond_with.
josevalim authored
383 mimes_for_respond_to.keys.select do |mime|
384 config = mimes_for_respond_to[mime]
385
386 if config[:except]
387 !config[:except].include?(action)
388 elsif config[:only]
389 config[:only].include?(action)
390 else
391 true
3e8ba61 @josevalim Refactor even more Responder. Move mime negotiation to request and ad…
josevalim authored
392 end
393 end
09de34c @josevalim Added respond_with.
josevalim authored
394 end
3e8ba61 @josevalim Refactor even more Responder. Move mime negotiation to request and ad…
josevalim authored
395
3f445b3 @josevalim Refactor Responder to only calculate available mime types. Those are …
josevalim authored
396 class Responder #:nodoc:
3e8ba61 @josevalim Refactor even more Responder. Move mime negotiation to request and ad…
josevalim authored
397 attr_accessor :order
3f445b3 @josevalim Refactor Responder to only calculate available mime types. Those are …
josevalim authored
398
3e8ba61 @josevalim Refactor even more Responder. Move mime negotiation to request and ad…
josevalim authored
399 def initialize
3f445b3 @josevalim Refactor Responder to only calculate available mime types. Those are …
josevalim authored
400 @order, @responses = [], {}
7af12d0 @dhh Added synonym and custom type handling to respond_to [DHH]
dhh authored
401 end
6480d49 @jamis Add MimeResponds::Responder#any for managing multiple types with iden…
jamis authored
402
403 def any(*args, &block)
011e469 @lifo Make MimeResponds::Responder#any work without explicit types. Closes …
lifo authored
404 if args.any?
405 args.each { |type| send(type, &block) }
406 else
3e8ba61 @josevalim Refactor even more Responder. Move mime negotiation to request and ad…
josevalim authored
407 custom(Mime::ALL, &block)
011e469 @lifo Make MimeResponds::Responder#any work without explicit types. Closes …
lifo authored
408 end
6dea52c @dhh Finish custom handling [DHH]
dhh authored
409 end
7e280c3 @josevalim Remove Mime::ALL from Mime::SET.
josevalim authored
410 alias :all :any
3f445b3 @josevalim Refactor Responder to only calculate available mime types. Those are …
josevalim authored
411
412 def custom(mime_type, &block)
413 mime_type = mime_type.is_a?(Mime::Type) ? mime_type : Mime::Type.lookup(mime_type.to_s)
414
415 @order << mime_type
416 @responses[mime_type] ||= block
6dc1288 @wycats Remove method missing use in respond_to
wycats authored
417 end
672941d @jeremy Introduce a default respond_to block for custom types. Closes #8174.
jeremy authored
418
3f445b3 @josevalim Refactor Responder to only calculate available mime types. Those are …
josevalim authored
419 def response_for(mime)
3e8ba61 @josevalim Refactor even more Responder. Move mime negotiation to request and ad…
josevalim authored
420 @responses[mime] || @responses[Mime::ALL]
3f445b3 @josevalim Refactor Responder to only calculate available mime types. Those are …
josevalim authored
421 end
422
3e8ba61 @josevalim Refactor even more Responder. Move mime negotiation to request and ad…
josevalim authored
423 def self.generate_method_for_mime(mime)
424 sym = mime.is_a?(Symbol) ? mime : mime.to_sym
425 const = sym.to_s.upcase
426 class_eval <<-RUBY, __FILE__, __LINE__ + 1
427 def #{sym}(&block) # def html(&block)
428 custom(Mime::#{const}, &block) # custom(Mime::HTML, &block)
429 end # end
430 RUBY
3f445b3 @josevalim Refactor Responder to only calculate available mime types. Those are …
josevalim authored
431 end
432
3e8ba61 @josevalim Refactor even more Responder. Move mime negotiation to request and ad…
josevalim authored
433 Mime::SET.each do |mime|
434 generate_method_for_mime(mime)
435 end
3f445b3 @josevalim Refactor Responder to only calculate available mime types. Those are …
josevalim authored
436
6dc1288 @wycats Remove method missing use in respond_to
wycats authored
437 def method_missing(symbol, &block)
438 mime_constant = Mime.const_get(symbol.to_s.upcase)
3f445b3 @josevalim Refactor Responder to only calculate available mime types. Those are …
josevalim authored
439
6dc1288 @wycats Remove method missing use in respond_to
wycats authored
440 if Mime::SET.include?(mime_constant)
3e8ba61 @josevalim Refactor even more Responder. Move mime negotiation to request and ad…
josevalim authored
441 self.class.generate_method_for_mime(mime_constant)
6dc1288 @wycats Remove method missing use in respond_to
wycats authored
442 send(symbol, &block)
6dea52c @dhh Finish custom handling [DHH]
dhh authored
443 else
444 super
445 end
6480d49 @jamis Add MimeResponds::Responder#any for managing multiple types with iden…
jamis authored
446 end
672941d @jeremy Introduce a default respond_to block for custom types. Closes #8174.
jeremy authored
447
1c16649 @dhh Added better support for using the same actions to output for differe…
dhh authored
448 end
449 end
0ee1cb2 @jeremy Ruby 1.9 compat, consistent load paths
jeremy authored
450 end
Something went wrong with that request. Please try again.