Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

root/branches/1-2-stable/actionpack/lib/action_controller/mime_responds.rb

Revision 5232, 6.6 kB (checked in by bitsweat, 2 years ago)

r5515@ks: jeremy | 2006-10-08 13:24:42 -0700
#6281
r5516@ks: jeremy | 2006-10-08 13:29:49 -0700
respond_to :html doesn't assume .rhtml. Closes #6281.

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