~michaelforrest/use-case-mapper/trunk

« back to all changes in this revision

Viewing changes to vendor/rails/actionpack/lib/action_controller/mime_responds.rb

  • Committer: Michael Forrest
  • Date: 2010-10-15 16:28:50 UTC
  • Revision ID: michael.forrest@canonical.com-20101015162850-tj2vchanv0kr0dun
refrozeĀ gems

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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 self.generate_method_for_mime(mime)
 
148
        sym = mime.is_a?(Symbol) ? mime : mime.to_sym
 
149
        const = sym.to_s.upcase
 
150
        class_eval <<-RUBY, __FILE__, __LINE__ + 1
 
151
          def #{sym}(&block)                # def html(&block)
 
152
            custom(Mime::#{const}, &block)  #   custom(Mime::HTML, &block)
 
153
          end                               # end
 
154
        RUBY
 
155
      end
 
156
 
 
157
      Mime::SET.each do |mime|
 
158
        generate_method_for_mime(mime)
 
159
      end
 
160
 
 
161
      def method_missing(symbol, &block)
 
162
        mime_constant = Mime.const_get(symbol.to_s.upcase)
 
163
      
 
164
        if Mime::SET.include?(mime_constant)
 
165
          self.class.generate_method_for_mime(mime_constant)
 
166
          send(symbol, &block)
 
167
        else
 
168
          super
 
169
        end
 
170
      end
 
171
 
 
172
      def respond
 
173
        for priority in @mime_type_priority
 
174
          if priority == Mime::ALL
 
175
            @responses[@order.first].call
 
176
            return
 
177
          else
 
178
            if @responses[priority]
 
179
              @responses[priority].call
 
180
              return # mime type match found, be happy and return
 
181
            end
 
182
          end
 
183
        end
 
184
 
 
185
        if @order.include?(Mime::ALL)
 
186
          @responses[Mime::ALL].call
 
187
        else
 
188
          @controller.send :head, :not_acceptable
 
189
        end
 
190
      end
 
191
    end
 
192
  end
 
193
end