Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 323 lines (296 sloc) 10.475 kb
1c16649 @dhh Added better support for using the same actions to output for different ...
dhh authored
1 module ActionController #:nodoc:
2 module MimeResponds #:nodoc:
3e8ba61 @josevalim Refactor even more Responder. Move mime negotiation to request and added...
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 added...
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 added...
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 added...
josevalim authored
50 end
51 end
3f445b3 @josevalim Refactor Responder to only calculate available mime types. Those are sen...
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 sen...
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?
684a6b3 @josevalim Attempt to render the template inside the responder, so it can be used f...
josevalim authored
181 collect_mimes_for_render(mimes, block){ default_render }
09de34c @josevalim Added respond_with.
josevalim authored
182 end
672941d @jeremy Introduce a default respond_to block for custom types. Closes #8174.
jeremy authored
183
6e0ac74 @josevalim Renamed ActionController::Renderer to ActionController::Responder and Ac...
josevalim authored
184 # respond_with wraps a resource around a responder for default representation.
1fd65c8 @josevalim Encapsulate respond_with behavior in a presenter.
josevalim authored
185 # First it invokes respond_to, if a response cannot be found (ie. no block
186 # for the request was given and template was not available), it instantiates
6e0ac74 @josevalim Renamed ActionController::Renderer to ActionController::Responder and Ac...
josevalim authored
187 # an ActionController::Responder with the controller and resource.
5b7e81e @josevalim Allow respond_with to deal with http verb accordingly.
josevalim authored
188 #
1fd65c8 @josevalim Encapsulate respond_with behavior in a presenter.
josevalim authored
189 # ==== Example
5b7e81e @josevalim Allow respond_with to deal with http verb accordingly.
josevalim authored
190 #
1fd65c8 @josevalim Encapsulate respond_with behavior in a presenter.
josevalim authored
191 # def index
192 # @users = User.all
193 # respond_with(@users)
09de34c @josevalim Added respond_with.
josevalim authored
194 # end
195 #
1fd65c8 @josevalim Encapsulate respond_with behavior in a presenter.
josevalim authored
196 # It also accepts a block to be given. It's used to overwrite a default
197 # response:
f59984c @josevalim Add nagivational behavior to respond_with.
josevalim authored
198 #
199 # def destroy
1fd65c8 @josevalim Encapsulate respond_with behavior in a presenter.
josevalim authored
200 # @user = User.find(params[:id])
201 # flash[:notice] = "User was successfully created." if @user.save
5b7e81e @josevalim Allow respond_with to deal with http verb accordingly.
josevalim authored
202 #
1fd65c8 @josevalim Encapsulate respond_with behavior in a presenter.
josevalim authored
203 # respond_with(@user) do |format|
204 # format.html { render }
205 # end
7034272 @josevalim Add destroyed? to ActiveRecord, include tests for polymorphic urls for d...
josevalim authored
206 # end
207 #
6e0ac74 @josevalim Renamed ActionController::Renderer to ActionController::Responder and Ac...
josevalim authored
208 # All options given to respond_with are sent to the underlying responder,
209 # except for the option :responder itself. Since the responder interface
aed135d @josevalim Renamed presenter to renderer, added some documentation and defined its ...
josevalim authored
210 # is quite simple (it just needs to respond to call), you can even give
211 # a proc to it.
7034272 @josevalim Add destroyed? to ActiveRecord, include tests for polymorphic urls for d...
josevalim authored
212 #
4f9047e @josevalim Ensure collections are not treated as nested resources.
josevalim authored
213 def respond_with(*resources, &block)
684a6b3 @josevalim Attempt to render the template inside the responder, so it can be used f...
josevalim authored
214 collect_mimes_for_render([], block) do
215 options = resources.extract_options!
216 (options.delete(:responder) || responder).call(self, resources, options)
217 end
aed135d @josevalim Renamed presenter to renderer, added some documentation and defined its ...
josevalim authored
218 end
7a4a679 @josevalim Remove any resource logic from respond_to.
josevalim authored
219
6e0ac74 @josevalim Renamed ActionController::Renderer to ActionController::Responder and Ac...
josevalim authored
220 def responder
221 ActionController::Responder
3f445b3 @josevalim Refactor Responder to only calculate available mime types. Those are sen...
josevalim authored
222 end
223
7a4a679 @josevalim Remove any resource logic from respond_to.
josevalim authored
224 protected
225
09de34c @josevalim Added respond_with.
josevalim authored
226 # Collect mimes declared in the class method respond_to valid for the
227 # current action.
228 #
229 def collect_mimes_from_class_level #:nodoc:
230 action = action_name.to_sym
3e8ba61 @josevalim Refactor even more Responder. Move mime negotiation to request and added...
josevalim authored
231
09de34c @josevalim Added respond_with.
josevalim authored
232 mimes_for_respond_to.keys.select do |mime|
233 config = mimes_for_respond_to[mime]
234
235 if config[:except]
236 !config[:except].include?(action)
237 elsif config[:only]
238 config[:only].include?(action)
239 else
240 true
3e8ba61 @josevalim Refactor even more Responder. Move mime negotiation to request and added...
josevalim authored
241 end
242 end
09de34c @josevalim Added respond_with.
josevalim authored
243 end
3e8ba61 @josevalim Refactor even more Responder. Move mime negotiation to request and added...
josevalim authored
244
684a6b3 @josevalim Attempt to render the template inside the responder, so it can be used f...
josevalim authored
245 # Receives a collection of mimes and a block with formats and initialize a
246 # collector. If a response was added to the collector, uses it to satisfy
247 # the request, otherwise yields the block given.
248 #
249 def collect_mimes_for_render(mimes, formats)
250 collector = Collector.new
251 mimes = collect_mimes_from_class_level if mimes.empty?
252 mimes.each { |mime| collector.send(mime) }
253 formats.call(collector) if formats
254
255 if format = request.negotiate_mime(collector.order)
256 self.formats = [format.to_sym]
257
258 if response = collector.response_for(format)
259 response.call
260 else
261 yield
262 end
263 else
264 head :not_acceptable
265 end
266 end
267
6e0ac74 @josevalim Renamed ActionController::Renderer to ActionController::Responder and Ac...
josevalim authored
268 class Collector #:nodoc:
3e8ba61 @josevalim Refactor even more Responder. Move mime negotiation to request and added...
josevalim authored
269 attr_accessor :order
3f445b3 @josevalim Refactor Responder to only calculate available mime types. Those are sen...
josevalim authored
270
3e8ba61 @josevalim Refactor even more Responder. Move mime negotiation to request and added...
josevalim authored
271 def initialize
3f445b3 @josevalim Refactor Responder to only calculate available mime types. Those are sen...
josevalim authored
272 @order, @responses = [], {}
7af12d0 @dhh Added synonym and custom type handling to respond_to [DHH]
dhh authored
273 end
6480d49 @jamis Add MimeResponds::Responder#any for managing multiple types with identic...
jamis authored
274
275 def any(*args, &block)
011e469 @lifo Make MimeResponds::Responder#any work without explicit types. Closes #11...
lifo authored
276 if args.any?
277 args.each { |type| send(type, &block) }
278 else
3e8ba61 @josevalim Refactor even more Responder. Move mime negotiation to request and added...
josevalim authored
279 custom(Mime::ALL, &block)
011e469 @lifo Make MimeResponds::Responder#any work without explicit types. Closes #11...
lifo authored
280 end
6dea52c @dhh Finish custom handling [DHH]
dhh authored
281 end
7e280c3 @josevalim Remove Mime::ALL from Mime::SET.
josevalim authored
282 alias :all :any
3f445b3 @josevalim Refactor Responder to only calculate available mime types. Those are sen...
josevalim authored
283
284 def custom(mime_type, &block)
285 mime_type = mime_type.is_a?(Mime::Type) ? mime_type : Mime::Type.lookup(mime_type.to_s)
286
287 @order << mime_type
288 @responses[mime_type] ||= block
6dc1288 @wycats Remove method missing use in respond_to
wycats authored
289 end
672941d @jeremy Introduce a default respond_to block for custom types. Closes #8174.
jeremy authored
290
3f445b3 @josevalim Refactor Responder to only calculate available mime types. Those are sen...
josevalim authored
291 def response_for(mime)
3e8ba61 @josevalim Refactor even more Responder. Move mime negotiation to request and added...
josevalim authored
292 @responses[mime] || @responses[Mime::ALL]
3f445b3 @josevalim Refactor Responder to only calculate available mime types. Those are sen...
josevalim authored
293 end
294
3e8ba61 @josevalim Refactor even more Responder. Move mime negotiation to request and added...
josevalim authored
295 def self.generate_method_for_mime(mime)
296 sym = mime.is_a?(Symbol) ? mime : mime.to_sym
297 const = sym.to_s.upcase
298 class_eval <<-RUBY, __FILE__, __LINE__ + 1
299 def #{sym}(&block) # def html(&block)
300 custom(Mime::#{const}, &block) # custom(Mime::HTML, &block)
301 end # end
302 RUBY
3f445b3 @josevalim Refactor Responder to only calculate available mime types. Those are sen...
josevalim authored
303 end
304
3e8ba61 @josevalim Refactor even more Responder. Move mime negotiation to request and added...
josevalim authored
305 Mime::SET.each do |mime|
306 generate_method_for_mime(mime)
307 end
3f445b3 @josevalim Refactor Responder to only calculate available mime types. Those are sen...
josevalim authored
308
6dc1288 @wycats Remove method missing use in respond_to
wycats authored
309 def method_missing(symbol, &block)
310 mime_constant = Mime.const_get(symbol.to_s.upcase)
3f445b3 @josevalim Refactor Responder to only calculate available mime types. Those are sen...
josevalim authored
311
6dc1288 @wycats Remove method missing use in respond_to
wycats authored
312 if Mime::SET.include?(mime_constant)
3e8ba61 @josevalim Refactor even more Responder. Move mime negotiation to request and added...
josevalim authored
313 self.class.generate_method_for_mime(mime_constant)
6dc1288 @wycats Remove method missing use in respond_to
wycats authored
314 send(symbol, &block)
6dea52c @dhh Finish custom handling [DHH]
dhh authored
315 else
316 super
317 end
6480d49 @jamis Add MimeResponds::Responder#any for managing multiple types with identic...
jamis authored
318 end
672941d @jeremy Introduce a default respond_to block for custom types. Closes #8174.
jeremy authored
319
1c16649 @dhh Added better support for using the same actions to output for different ...
dhh authored
320 end
321 end
0ee1cb2 @jeremy Ruby 1.9 compat, consistent load paths
jeremy authored
322 end
Something went wrong with that request. Please try again.