public
Description: Inherited Resources speeds up development by making your controllers inherit all restful actions so you just have to focus on what is important.
Homepage: http://blog.plataformatec.com.br/
Clone URL: git://github.com/josevalim/inherited_resources.git
Click here to lend your support to: inherited_resources and make a donation at www.pledgie.com !
100644 345 lines (306 sloc) 12.078 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# Provides an extension for Rails respond_to by expading MimeResponds::Responder
# and adding respond_to class method and respond_with instance method.
#
module ActionController #:nodoc:
  class Base #:nodoc:
 
    protected
      # Defines respond_to method to store formats that are rendered by default.
      #
      # Examples:
      #
      # respond_to :html, :xml, :json
      #
      # All actions on your controller will respond to :html, :xml and :json.
      # But if you want to specify it based on your actions, you can use only and
      # except:
      #
      # respond_to :html
      # respond_to :xml, :json, :except => [ :edit ]
      #
      # The definition above explicits that all actions respond to :html. And all
      # actions except :edit respond to :xml and :json.
      #
      # You can specify also only parameters:
      #
      # respond_to :rjs, :only => :create
      #
      # Which would be the same as:
      #
      # respond_to :rjs => :create
      #
      def self.respond_to(*formats)
        options = formats.extract_options!
        formats_hash = {}
 
        only_actions = Array(options.delete(:only))
        except_actions = Array(options.delete(:except))
 
        only_actions.map!{ |a| a.to_sym }
        except_actions.map!{ |a| a.to_sym }
 
        formats.each do |format|
          formats_hash[format.to_sym] = {}
          formats_hash[format.to_sym][:only] = only_actions unless only_actions.empty?
          formats_hash[format.to_sym][:except] = except_actions unless except_actions.empty?
        end
 
        options.each do |format, actions|
          formats_hash[format.to_sym] = {}
          next if actions == :all || actions == 'all'
 
          actions = Array(actions)
          actions.map!{ |a| a.to_sym }
 
          formats_hash[format.to_sym][:only] = actions unless actions.empty?
        end
 
        write_inheritable_hash(:formats_for_respond_to, formats_hash)
      end
      class_inheritable_reader :formats_for_respond_to
 
      # Define defaults respond_to
      respond_to :html
      respond_to :xml, :except => [ :edit ]
 
      # Method to clear all respond_to declared until the current controller.
      # This is like freeing the controller from the inheritance chain. :)
      #
      def self.clear_respond_to!
        formats = formats_for_respond_to
        formats.each { |k,v| formats[k] = { :only => [] } }
        write_inheritable_hash(:formats_for_respond_to, formats)
      end
 
      # respond_with accepts an object and tries to render a view based in the
      # controller and actions that called respond_with. If the view cannot be
      # found, it will try to call :to_format in the object.
      #
      # class ProjectsController < ApplicationController
      # respond_to :html, :xml
      #
      # def show
      # @project = Project.find(:id)
      # respond_with(@project)
      # end
      # end
      #
      # When the client request a xml, we will check first for projects/show.xml
      # if it can't be found, we will call :to_xml in the object @project. If the
      # object eventually doesn't respond to :to_xml it will render 404.
      #
      # If you want to overwrite the formats specified in the class, you can
      # send your new formats using the options :to.
      #
      # def show
      # @project = Project.find(:id)
      # respond_with(@project, :to => :json)
      # end
      #
      # That means that this action will ONLY reply to json requests.
      #
      # All other options sent will be forwarded to the render method. So you can
      # do:
      #
      # def create
      # # ...
      # if @project.save
      # respond_with(@project, :status => :ok, :location => @project)
      # else
      # respond_with(@project.errors, :status => :unprocessable_entity)
      # end
      # end
      #
      # respond_with does not accept blocks, if you want advanced configurations
      # check respond_to method sending :with => @object as option.
      #
      # Returns true if anything is rendered. Returns false otherwise.
      #
      def respond_with(object, options = {})
        attempt_to_respond = false
 
        # You can also send a responder object as parameter.
        #
        responder = options.delete(:responder) || Responder.new(self)
 
        # Check for given mime types
        #
        mime_types = Array(options.delete(:to))
        mime_types.map!{ |mime| mime.to_sym }
 
        # If :skip_not_acceptable is sent, it will not render :not_acceptable
        # if the mime type sent by the client cannot be found.
        #
        skip_not_acceptable = options.delete(:skip_not_acceptable)
 
        for priority in responder.mime_type_priority
          if priority == Mime::ALL && template_exists?
            render options.merge(:action => action_name)
            return true
 
          elsif responder.action_respond_to_format?(priority.to_sym, mime_types)
            attempt_to_respond = true
            response.template.template_format = priority.to_sym
            response.content_type = priority.to_s
 
            if template_exists?
              render options.merge(:action => action_name)
              return true
            elsif object.respond_to?(:"to_#{priority.to_sym}")
              render options.merge(:text => object.send(:"to_#{priority.to_sym}"))
              return true
            end
          end
        end
 
        # If we got here we could not render the object. But if attempted to
        # render (this means, the format sent by the client was valid) we should
        # render a 404.
        #
        # If we even didn't attempt to respond, we respond :not_acceptable
        # unless is told otherwise.
        #
        if attempt_to_respond
          render :text => '404 Not Found', :status => 404
          return true
        elsif !skip_not_acceptable
          head :not_acceptable
          return false
        end
 
        return false
      end
 
      # Extends respond_to behaviour.
      #
      # You can now pass objects using the options :with.
      #
      # respond_to(:html, :xml, :rjs, :with => @project)
      #
      # If you pass an object and send any block, it's exactly the same as:
      #
      # respond_with(@project, :to => [:html, :xml, :rjs])
      #
      # But the main difference of respond_to and respond_with is that the first
      # allows further customizations:
      #
      # respond_to(:html, :with => @project) do |format|
      # format.xml { render :xml => @project.errors }
      # end
      #
      # It's the same as:
      #
      # 1. When responding to html, execute respond_with(@object).
      # 2. When accessing a xml, execute the block given.
      #
      # Formats defined in blocks have precedence to formats sent as arguments.
      # In other words, if you pass a format as argument and as block, the block
      # will always be executed.
      #
      # And as in respond_with, all extra options sent will be forwarded to
      # the render method:
      #
      # respond_to(:with => @projects.errors, :status => :unprocessable_entity) do |format|
      # format.html { render :template => 'new' }
      # end
      #
      def respond_to(*types, &block)
        options = types.extract_options!
        object = options.delete(:with)
        responder = Responder.new(self)
        
        # This is the default respond_to behaviour, when no object is given.
        if object.nil?
          block ||= lambda { |responder| types.each { |type| responder.send(type) } }
          block.call(responder)
          responder.respond
          return true # we are done here
 
        else
          # If a block is given, it checks if we can perform the requested format.
          #
          # Even if Mime::ALL is sent by the client, we do not respond_to it now.
          # This is done using calling :respond_to_block instead of :respond.
          #
          # It's worth to remember that responder_to_block does not respond
          # :not_acceptable also.
          #
          if block_given?
            block.call(responder)
            responder.respond_to_block
            return true if responder.responded? || performed?
          end
 
          # Let's see if we get lucky rendering with :respond_with.
          # At the end, respond_with checks for Mime::ALL if any template exist.
          #
          # Notice that we are sending the responder (for performance gain) and
          # sending :skip_not_acceptable because we don't want to respond
          # :not_acceptable yet.
          #
          if respond_with(object, options.merge(:to => types, :responder => responder, :skip_not_acceptable => true))
            return true
 
          # Since respond_with couldn't help us, our last chance is to reply to
          # any block given if the user send all as mime type.
          #
          elsif block_given?
            return true if responder.respond_to_all
          end
        end
 
        # If we get here it means that we could not satisfy our request.
        # Now we finally return :not_acceptable.
        #
        head :not_acceptable
        return false
      end
 
    private
 
      # Define template_exists? for Rails 2.3
      unless ActionController::Base.private_instance_methods.include? 'template_exists?'
        def template_exists?
          self.view_paths.find_template("#{controller_name}/#{action_name}", response.template.template_format)
        rescue ActionView::MissingTemplate
          false
        end
      end
 
    # If ApplicationController is already defined around here, we should call
    # inherited_with_inheritable_attributes to insert formats_for_respond_to.
    # This usually happens only on Rails 2.3.
    #
    if defined?(ApplicationController)
      self.send(:inherited_with_inheritable_attributes, ApplicationController)
    end
 
  end
 
  module MimeResponds #:nodoc:
    class Responder #:nodoc:
 
      # Create an attr_reader for @mime_type_priority
      attr_reader :mime_type_priority
 
      # Stores if this Responder instance called any block.
      def responded?; @responded; end
 
      # Similar as respond but if we can't find a valid mime type,
      # we do not send :not_acceptable message as head.
      #
      # It does not respond to Mime::ALL in priority as well.
      #
      def respond_to_block
        for priority in @mime_type_priority
          next if priority == Mime::ALL
 
          if @responses[priority]
            @responses[priority].call
            return (@responded = true) # mime type match found, be happy and return
          end
        end
 
        if @order.include?(Mime::ALL)
          @responses[Mime::ALL].call
          return (@responded = true)
        else
          return (@responded = false)
        end
      end
 
      # Respond to the first format given if Mime::ALL is included in the
      # mime type priorites. This is the behaviour expected when the client
      # sends "*/*" as mime type.
      #
      def respond_to_all
        if @mime_type_priority.include?(Mime::ALL) && first = @responses[@order.first]
          first.call
          return (@responded = true)
        end
      end
 
      # Receives an format and checks if the current action responds to
      # the given format. If additional mimes are sent, only them are checked.
      #
      def action_respond_to_format?(format, additional_mimes = [])
        if !additional_mimes.blank?
          additional_mimes.include?(format.to_sym)
        elsif formats = @controller.formats_for_respond_to[format.to_sym]
          if formats[:only]
            formats[:only].include?(@controller.action_name.to_sym)
          elsif formats[:except]
            !formats[:except].include?(@controller.action_name.to_sym)
          else
            true
          end
        else
          false
        end
      end
 
    end
  end
end