halorgium-actionpack 3.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (154) hide show
  1. data/CHANGELOG +5179 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README +409 -0
  4. data/lib/abstract_controller.rb +16 -0
  5. data/lib/abstract_controller/base.rb +158 -0
  6. data/lib/abstract_controller/callbacks.rb +113 -0
  7. data/lib/abstract_controller/exceptions.rb +12 -0
  8. data/lib/abstract_controller/helpers.rb +151 -0
  9. data/lib/abstract_controller/layouts.rb +250 -0
  10. data/lib/abstract_controller/localized_cache.rb +49 -0
  11. data/lib/abstract_controller/logger.rb +61 -0
  12. data/lib/abstract_controller/rendering_controller.rb +188 -0
  13. data/lib/action_controller.rb +72 -0
  14. data/lib/action_controller/base.rb +168 -0
  15. data/lib/action_controller/caching.rb +80 -0
  16. data/lib/action_controller/caching/actions.rb +163 -0
  17. data/lib/action_controller/caching/fragments.rb +116 -0
  18. data/lib/action_controller/caching/pages.rb +154 -0
  19. data/lib/action_controller/caching/sweeping.rb +97 -0
  20. data/lib/action_controller/deprecated.rb +4 -0
  21. data/lib/action_controller/deprecated/integration_test.rb +2 -0
  22. data/lib/action_controller/deprecated/performance_test.rb +1 -0
  23. data/lib/action_controller/dispatch/dispatcher.rb +57 -0
  24. data/lib/action_controller/metal.rb +129 -0
  25. data/lib/action_controller/metal/benchmarking.rb +73 -0
  26. data/lib/action_controller/metal/compatibility.rb +145 -0
  27. data/lib/action_controller/metal/conditional_get.rb +86 -0
  28. data/lib/action_controller/metal/configuration.rb +28 -0
  29. data/lib/action_controller/metal/cookies.rb +105 -0
  30. data/lib/action_controller/metal/exceptions.rb +55 -0
  31. data/lib/action_controller/metal/filter_parameter_logging.rb +77 -0
  32. data/lib/action_controller/metal/flash.rb +162 -0
  33. data/lib/action_controller/metal/head.rb +27 -0
  34. data/lib/action_controller/metal/helpers.rb +115 -0
  35. data/lib/action_controller/metal/hide_actions.rb +47 -0
  36. data/lib/action_controller/metal/http_authentication.rb +312 -0
  37. data/lib/action_controller/metal/layouts.rb +171 -0
  38. data/lib/action_controller/metal/mime_responds.rb +317 -0
  39. data/lib/action_controller/metal/rack_convenience.rb +27 -0
  40. data/lib/action_controller/metal/redirector.rb +22 -0
  41. data/lib/action_controller/metal/render_options.rb +103 -0
  42. data/lib/action_controller/metal/rendering_controller.rb +57 -0
  43. data/lib/action_controller/metal/request_forgery_protection.rb +108 -0
  44. data/lib/action_controller/metal/rescuable.rb +13 -0
  45. data/lib/action_controller/metal/responder.rb +200 -0
  46. data/lib/action_controller/metal/session.rb +15 -0
  47. data/lib/action_controller/metal/session_management.rb +45 -0
  48. data/lib/action_controller/metal/streaming.rb +188 -0
  49. data/lib/action_controller/metal/testing.rb +39 -0
  50. data/lib/action_controller/metal/url_for.rb +41 -0
  51. data/lib/action_controller/metal/verification.rb +130 -0
  52. data/lib/action_controller/middleware.rb +38 -0
  53. data/lib/action_controller/notifications.rb +10 -0
  54. data/lib/action_controller/polymorphic_routes.rb +183 -0
  55. data/lib/action_controller/record_identifier.rb +91 -0
  56. data/lib/action_controller/testing/process.rb +111 -0
  57. data/lib/action_controller/testing/test_case.rb +345 -0
  58. data/lib/action_controller/translation.rb +13 -0
  59. data/lib/action_controller/url_rewriter.rb +204 -0
  60. data/lib/action_controller/vendor/html-scanner.rb +16 -0
  61. data/lib/action_controller/vendor/html-scanner/html/document.rb +68 -0
  62. data/lib/action_controller/vendor/html-scanner/html/node.rb +537 -0
  63. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +176 -0
  64. data/lib/action_controller/vendor/html-scanner/html/selector.rb +828 -0
  65. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +105 -0
  66. data/lib/action_controller/vendor/html-scanner/html/version.rb +11 -0
  67. data/lib/action_dispatch.rb +70 -0
  68. data/lib/action_dispatch/http/headers.rb +33 -0
  69. data/lib/action_dispatch/http/mime_type.rb +231 -0
  70. data/lib/action_dispatch/http/mime_types.rb +23 -0
  71. data/lib/action_dispatch/http/request.rb +539 -0
  72. data/lib/action_dispatch/http/response.rb +290 -0
  73. data/lib/action_dispatch/http/status_codes.rb +42 -0
  74. data/lib/action_dispatch/http/utils.rb +20 -0
  75. data/lib/action_dispatch/middleware/callbacks.rb +50 -0
  76. data/lib/action_dispatch/middleware/params_parser.rb +79 -0
  77. data/lib/action_dispatch/middleware/rescue.rb +26 -0
  78. data/lib/action_dispatch/middleware/session/abstract_store.rb +208 -0
  79. data/lib/action_dispatch/middleware/session/cookie_store.rb +235 -0
  80. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +47 -0
  81. data/lib/action_dispatch/middleware/show_exceptions.rb +143 -0
  82. data/lib/action_dispatch/middleware/stack.rb +116 -0
  83. data/lib/action_dispatch/middleware/static.rb +44 -0
  84. data/lib/action_dispatch/middleware/string_coercion.rb +29 -0
  85. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +24 -0
  86. data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +26 -0
  87. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +10 -0
  88. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +29 -0
  89. data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +2 -0
  90. data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +10 -0
  91. data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +21 -0
  92. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +2 -0
  93. data/lib/action_dispatch/routing.rb +381 -0
  94. data/lib/action_dispatch/routing/deprecated_mapper.rb +878 -0
  95. data/lib/action_dispatch/routing/mapper.rb +327 -0
  96. data/lib/action_dispatch/routing/route.rb +49 -0
  97. data/lib/action_dispatch/routing/route_set.rb +497 -0
  98. data/lib/action_dispatch/testing/assertions.rb +8 -0
  99. data/lib/action_dispatch/testing/assertions/dom.rb +35 -0
  100. data/lib/action_dispatch/testing/assertions/model.rb +19 -0
  101. data/lib/action_dispatch/testing/assertions/response.rb +145 -0
  102. data/lib/action_dispatch/testing/assertions/routing.rb +144 -0
  103. data/lib/action_dispatch/testing/assertions/selector.rb +639 -0
  104. data/lib/action_dispatch/testing/assertions/tag.rb +123 -0
  105. data/lib/action_dispatch/testing/integration.rb +504 -0
  106. data/lib/action_dispatch/testing/performance_test.rb +15 -0
  107. data/lib/action_dispatch/testing/test_request.rb +83 -0
  108. data/lib/action_dispatch/testing/test_response.rb +131 -0
  109. data/lib/action_pack.rb +24 -0
  110. data/lib/action_pack/version.rb +9 -0
  111. data/lib/action_view.rb +58 -0
  112. data/lib/action_view/base.rb +308 -0
  113. data/lib/action_view/context.rb +44 -0
  114. data/lib/action_view/erb/util.rb +48 -0
  115. data/lib/action_view/helpers.rb +62 -0
  116. data/lib/action_view/helpers/active_model_helper.rb +306 -0
  117. data/lib/action_view/helpers/ajax_helper.rb +68 -0
  118. data/lib/action_view/helpers/asset_tag_helper.rb +830 -0
  119. data/lib/action_view/helpers/atom_feed_helper.rb +198 -0
  120. data/lib/action_view/helpers/cache_helper.rb +39 -0
  121. data/lib/action_view/helpers/capture_helper.rb +168 -0
  122. data/lib/action_view/helpers/date_helper.rb +988 -0
  123. data/lib/action_view/helpers/debug_helper.rb +38 -0
  124. data/lib/action_view/helpers/form_helper.rb +1102 -0
  125. data/lib/action_view/helpers/form_options_helper.rb +600 -0
  126. data/lib/action_view/helpers/form_tag_helper.rb +495 -0
  127. data/lib/action_view/helpers/javascript_helper.rb +208 -0
  128. data/lib/action_view/helpers/number_helper.rb +311 -0
  129. data/lib/action_view/helpers/prototype_helper.rb +1309 -0
  130. data/lib/action_view/helpers/raw_output_helper.rb +9 -0
  131. data/lib/action_view/helpers/record_identification_helper.rb +20 -0
  132. data/lib/action_view/helpers/record_tag_helper.rb +58 -0
  133. data/lib/action_view/helpers/sanitize_helper.rb +259 -0
  134. data/lib/action_view/helpers/scriptaculous_helper.rb +226 -0
  135. data/lib/action_view/helpers/tag_helper.rb +151 -0
  136. data/lib/action_view/helpers/text_helper.rb +594 -0
  137. data/lib/action_view/helpers/translation_helper.rb +39 -0
  138. data/lib/action_view/helpers/url_helper.rb +639 -0
  139. data/lib/action_view/locale/en.yml +117 -0
  140. data/lib/action_view/paths.rb +80 -0
  141. data/lib/action_view/render/partials.rb +342 -0
  142. data/lib/action_view/render/rendering.rb +134 -0
  143. data/lib/action_view/safe_buffer.rb +28 -0
  144. data/lib/action_view/template/error.rb +101 -0
  145. data/lib/action_view/template/handler.rb +36 -0
  146. data/lib/action_view/template/handlers.rb +52 -0
  147. data/lib/action_view/template/handlers/builder.rb +17 -0
  148. data/lib/action_view/template/handlers/erb.rb +53 -0
  149. data/lib/action_view/template/handlers/rjs.rb +18 -0
  150. data/lib/action_view/template/resolver.rb +165 -0
  151. data/lib/action_view/template/template.rb +131 -0
  152. data/lib/action_view/template/text.rb +38 -0
  153. data/lib/action_view/test_case.rb +163 -0
  154. metadata +236 -0
@@ -0,0 +1,878 @@
1
+ module ActionDispatch
2
+ module Routing
3
+ # Mapper instances are used to build routes. The object passed to the draw
4
+ # block in config/routes.rb is a Mapper instance.
5
+ #
6
+ # Mapper instances have relatively few instance methods, in order to avoid
7
+ # clashes with named routes.
8
+ #
9
+ # == Overview
10
+ #
11
+ # ActionController::Resources are a way of defining RESTful \resources. A RESTful \resource, in basic terms,
12
+ # is something that can be pointed at and it will respond with a representation of the data requested.
13
+ # In real terms this could mean a user with a browser requests an HTML page, or that a desktop application
14
+ # requests XML data.
15
+ #
16
+ # RESTful design is based on the assumption that there are four generic verbs that a user of an
17
+ # application can request from a \resource (the noun).
18
+ #
19
+ # \Resources can be requested using four basic HTTP verbs (GET, POST, PUT, DELETE), the method used
20
+ # denotes the type of action that should take place.
21
+ #
22
+ # === The Different Methods and their Usage
23
+ #
24
+ # * GET - Requests for a \resource, no saving or editing of a \resource should occur in a GET request.
25
+ # * POST - Creation of \resources.
26
+ # * PUT - Editing of attributes on a \resource.
27
+ # * DELETE - Deletion of a \resource.
28
+ #
29
+ # === Examples
30
+ #
31
+ # # A GET request on the Posts resource is asking for all Posts
32
+ # GET /posts
33
+ #
34
+ # # A GET request on a single Post resource is asking for that particular Post
35
+ # GET /posts/1
36
+ #
37
+ # # A POST request on the Posts resource is asking for a Post to be created with the supplied details
38
+ # POST /posts # with => { :post => { :title => "My Whizzy New Post", :body => "I've got a brand new combine harvester" } }
39
+ #
40
+ # # A PUT request on a single Post resource is asking for a Post to be updated
41
+ # PUT /posts # with => { :id => 1, :post => { :title => "Changed Whizzy Title" } }
42
+ #
43
+ # # A DELETE request on a single Post resource is asking for it to be deleted
44
+ # DELETE /posts # with => { :id => 1 }
45
+ #
46
+ # By using the REST convention, users of our application can assume certain things about how the data
47
+ # is requested and how it is returned. Rails simplifies the routing part of RESTful design by
48
+ # supplying you with methods to create them in your routes.rb file.
49
+ #
50
+ # Read more about REST at http://en.wikipedia.org/wiki/Representational_State_Transfer
51
+ class DeprecatedMapper #:doc:
52
+ def initialize(set) #:nodoc:
53
+ @set = set
54
+ end
55
+
56
+ # Create an unnamed route with the provided +path+ and +options+. See
57
+ # ActionDispatch::Routing for an introduction to routes.
58
+ def connect(path, options = {})
59
+ options = options.dup
60
+
61
+ if conditions = options.delete(:conditions)
62
+ conditions = conditions.dup
63
+ method = [conditions.delete(:method)].flatten.compact
64
+ method.map! { |m|
65
+ m = m.to_s.upcase
66
+
67
+ if m == "HEAD"
68
+ raise ArgumentError, "HTTP method HEAD is invalid in route conditions. Rails processes HEAD requests the same as GETs, returning just the response headers"
69
+ end
70
+
71
+ unless HTTP_METHODS.include?(m.downcase.to_sym)
72
+ raise ArgumentError, "Invalid HTTP method specified in route conditions"
73
+ end
74
+
75
+ m
76
+ }
77
+
78
+ if method.length > 1
79
+ method = Regexp.union(*method)
80
+ elsif method.length == 1
81
+ method = method.first
82
+ else
83
+ method = nil
84
+ end
85
+ end
86
+
87
+ path_prefix = options.delete(:path_prefix)
88
+ name_prefix = options.delete(:name_prefix)
89
+ namespace = options.delete(:namespace)
90
+
91
+ name = options.delete(:_name)
92
+ name = "#{name_prefix}#{name}" if name_prefix
93
+
94
+ requirements = options.delete(:requirements) || {}
95
+ defaults = options.delete(:defaults) || {}
96
+ options.each do |k, v|
97
+ if v.is_a?(Regexp)
98
+ if value = options.delete(k)
99
+ requirements[k.to_sym] = value
100
+ end
101
+ else
102
+ value = options.delete(k)
103
+ defaults[k.to_sym] = value.is_a?(Symbol) ? value : value.to_param
104
+ end
105
+ end
106
+
107
+ requirements.each do |_, requirement|
108
+ if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
109
+ raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
110
+ end
111
+ if requirement.multiline?
112
+ raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
113
+ end
114
+ end
115
+
116
+ possible_names = Routing.possible_controllers.collect { |n| Regexp.escape(n) }
117
+ requirements[:controller] ||= Regexp.union(*possible_names)
118
+
119
+ if defaults[:controller]
120
+ defaults[:action] ||= 'index'
121
+ defaults[:controller] = defaults[:controller].to_s
122
+ defaults[:controller] = "#{namespace}#{defaults[:controller]}" if namespace
123
+ end
124
+
125
+ if defaults[:action]
126
+ defaults[:action] = defaults[:action].to_s
127
+ end
128
+
129
+ if path.is_a?(String)
130
+ path = "#{path_prefix}/#{path}" if path_prefix
131
+ path = path.gsub('.:format', '(.:format)')
132
+ path = optionalize_trailing_dynamic_segments(path, requirements, defaults)
133
+ glob = $1.to_sym if path =~ /\/\*(\w+)$/
134
+ path = ::Rack::Mount::Utils.normalize_path(path)
135
+
136
+ if glob && !defaults[glob].blank?
137
+ raise ActionController::RoutingError, "paths cannot have non-empty default values"
138
+ end
139
+ end
140
+
141
+ app = Routing::RouteSet::Dispatcher.new(:defaults => defaults, :glob => glob)
142
+
143
+ conditions = {}
144
+ conditions[:request_method] = method if method
145
+ conditions[:path_info] = path if path
146
+
147
+ @set.add_route(app, conditions, requirements, defaults, name)
148
+ end
149
+
150
+ def optionalize_trailing_dynamic_segments(path, requirements, defaults) #:nodoc:
151
+ path = (path =~ /^\//) ? path.dup : "/#{path}"
152
+ optional, segments = true, []
153
+
154
+ required_segments = requirements.keys
155
+ required_segments -= defaults.keys.compact
156
+
157
+ old_segments = path.split('/')
158
+ old_segments.shift
159
+ length = old_segments.length
160
+
161
+ old_segments.reverse.each_with_index do |segment, index|
162
+ required_segments.each do |required|
163
+ if segment =~ /#{required}/
164
+ optional = false
165
+ break
166
+ end
167
+ end
168
+
169
+ if optional
170
+ if segment == ":id" && segments.include?(":action")
171
+ optional = false
172
+ elsif segment == ":controller" || segment == ":action" || segment == ":id"
173
+ # Ignore
174
+ elsif !(segment =~ /^:\w+$/) &&
175
+ !(segment =~ /^:\w+\(\.:format\)$/)
176
+ optional = false
177
+ elsif segment =~ /^:(\w+)$/
178
+ if defaults.has_key?($1.to_sym)
179
+ defaults.delete($1.to_sym)
180
+ else
181
+ optional = false
182
+ end
183
+ end
184
+ end
185
+
186
+ if optional && index < length - 1
187
+ segments.unshift('(/', segment)
188
+ segments.push(')')
189
+ elsif optional
190
+ segments.unshift('/(', segment)
191
+ segments.push(')')
192
+ else
193
+ segments.unshift('/', segment)
194
+ end
195
+ end
196
+
197
+ segments.join
198
+ end
199
+ private :optionalize_trailing_dynamic_segments
200
+
201
+ # Creates a named route called "root" for matching the root level request.
202
+ def root(options = {})
203
+ if options.is_a?(Symbol)
204
+ if source_route = @set.named_routes.routes[options]
205
+ options = source_route.defaults.merge({ :conditions => source_route.conditions })
206
+ end
207
+ end
208
+ named_route("root", '', options)
209
+ end
210
+
211
+ def named_route(name, path, options = {}) #:nodoc:
212
+ options[:_name] = name
213
+ connect(path, options)
214
+ end
215
+
216
+ # Enables the use of resources in a module by setting the name_prefix, path_prefix, and namespace for the model.
217
+ # Example:
218
+ #
219
+ # map.namespace(:admin) do |admin|
220
+ # admin.resources :products,
221
+ # :has_many => [ :tags, :images, :variants ]
222
+ # end
223
+ #
224
+ # This will create +admin_products_url+ pointing to "admin/products", which will look for an Admin::ProductsController.
225
+ # It'll also create +admin_product_tags_url+ pointing to "admin/products/#{product_id}/tags", which will look for
226
+ # Admin::TagsController.
227
+ def namespace(name, options = {}, &block)
228
+ if options[:namespace]
229
+ with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}", :name_prefix => "#{options.delete(:name_prefix)}#{name}_", :namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block)
230
+ else
231
+ with_options({:path_prefix => name, :name_prefix => "#{name}_", :namespace => "#{name}/" }.merge(options), &block)
232
+ end
233
+ end
234
+
235
+ def method_missing(route_name, *args, &proc) #:nodoc:
236
+ super unless args.length >= 1 && proc.nil?
237
+ named_route(route_name, *args)
238
+ end
239
+
240
+ INHERITABLE_OPTIONS = :namespace, :shallow
241
+
242
+ class Resource #:nodoc:
243
+ DEFAULT_ACTIONS = :index, :create, :new, :edit, :show, :update, :destroy
244
+
245
+ attr_reader :collection_methods, :member_methods, :new_methods
246
+ attr_reader :path_prefix, :name_prefix, :path_segment
247
+ attr_reader :plural, :singular
248
+ attr_reader :options
249
+
250
+ def initialize(entities, options)
251
+ @plural ||= entities
252
+ @singular ||= options[:singular] || plural.to_s.singularize
253
+ @path_segment = options.delete(:as) || @plural
254
+
255
+ @options = options
256
+
257
+ arrange_actions
258
+ add_default_actions
259
+ set_allowed_actions
260
+ set_prefixes
261
+ end
262
+
263
+ def controller
264
+ @controller ||= "#{options[:namespace]}#{(options[:controller] || plural).to_s}"
265
+ end
266
+
267
+ def requirements(with_id = false)
268
+ @requirements ||= @options[:requirements] || {}
269
+ @id_requirement ||= { :id => @requirements.delete(:id) || /[^#{Routing::SEPARATORS.join}]+/ }
270
+
271
+ with_id ? @requirements.merge(@id_requirement) : @requirements
272
+ end
273
+
274
+ def conditions
275
+ @conditions ||= @options[:conditions] || {}
276
+ end
277
+
278
+ def path
279
+ @path ||= "#{path_prefix}/#{path_segment}"
280
+ end
281
+
282
+ def new_path
283
+ new_action = self.options[:path_names][:new] if self.options[:path_names]
284
+ new_action ||= ActionController::Base.resources_path_names[:new]
285
+ @new_path ||= "#{path}/#{new_action}"
286
+ end
287
+
288
+ def shallow_path_prefix
289
+ @shallow_path_prefix ||= @options[:shallow] ? @options[:namespace].try(:sub, /\/$/, '') : path_prefix
290
+ end
291
+
292
+ def member_path
293
+ @member_path ||= "#{shallow_path_prefix}/#{path_segment}/:id"
294
+ end
295
+
296
+ def nesting_path_prefix
297
+ @nesting_path_prefix ||= "#{shallow_path_prefix}/#{path_segment}/:#{singular}_id"
298
+ end
299
+
300
+ def shallow_name_prefix
301
+ @shallow_name_prefix ||= @options[:shallow] ? @options[:namespace].try(:gsub, /\//, '_') : name_prefix
302
+ end
303
+
304
+ def nesting_name_prefix
305
+ "#{shallow_name_prefix}#{singular}_"
306
+ end
307
+
308
+ def action_separator
309
+ @action_separator ||= ActionController::Base.resource_action_separator
310
+ end
311
+
312
+ def uncountable?
313
+ @singular.to_s == @plural.to_s
314
+ end
315
+
316
+ def has_action?(action)
317
+ !DEFAULT_ACTIONS.include?(action) || action_allowed?(action)
318
+ end
319
+
320
+ protected
321
+ def arrange_actions
322
+ @collection_methods = arrange_actions_by_methods(options.delete(:collection))
323
+ @member_methods = arrange_actions_by_methods(options.delete(:member))
324
+ @new_methods = arrange_actions_by_methods(options.delete(:new))
325
+ end
326
+
327
+ def add_default_actions
328
+ add_default_action(member_methods, :get, :edit)
329
+ add_default_action(new_methods, :get, :new)
330
+ end
331
+
332
+ def set_allowed_actions
333
+ only, except = @options.values_at(:only, :except)
334
+ @allowed_actions ||= {}
335
+
336
+ if only == :all || except == :none
337
+ only = nil
338
+ except = []
339
+ elsif only == :none || except == :all
340
+ only = []
341
+ except = nil
342
+ end
343
+
344
+ if only
345
+ @allowed_actions[:only] = Array(only).map {|a| a.to_sym }
346
+ elsif except
347
+ @allowed_actions[:except] = Array(except).map {|a| a.to_sym }
348
+ end
349
+ end
350
+
351
+ def action_allowed?(action)
352
+ only, except = @allowed_actions.values_at(:only, :except)
353
+ (!only || only.include?(action)) && (!except || !except.include?(action))
354
+ end
355
+
356
+ def set_prefixes
357
+ @path_prefix = options.delete(:path_prefix)
358
+ @name_prefix = options.delete(:name_prefix)
359
+ end
360
+
361
+ def arrange_actions_by_methods(actions)
362
+ (actions || {}).inject({}) do |flipped_hash, (key, value)|
363
+ (flipped_hash[value] ||= []) << key
364
+ flipped_hash
365
+ end
366
+ end
367
+
368
+ def add_default_action(collection, method, action)
369
+ (collection[method] ||= []).unshift(action)
370
+ end
371
+ end
372
+
373
+ class SingletonResource < Resource #:nodoc:
374
+ def initialize(entity, options)
375
+ @singular = @plural = entity
376
+ options[:controller] ||= @singular.to_s.pluralize
377
+ super
378
+ end
379
+
380
+ alias_method :shallow_path_prefix, :path_prefix
381
+ alias_method :shallow_name_prefix, :name_prefix
382
+ alias_method :member_path, :path
383
+ alias_method :nesting_path_prefix, :path
384
+ end
385
+
386
+ # Creates named routes for implementing verb-oriented controllers
387
+ # for a collection \resource.
388
+ #
389
+ # For example:
390
+ #
391
+ # map.resources :messages
392
+ #
393
+ # will map the following actions in the corresponding controller:
394
+ #
395
+ # class MessagesController < ActionController::Base
396
+ # # GET messages_url
397
+ # def index
398
+ # # return all messages
399
+ # end
400
+ #
401
+ # # GET new_message_url
402
+ # def new
403
+ # # return an HTML form for describing a new message
404
+ # end
405
+ #
406
+ # # POST messages_url
407
+ # def create
408
+ # # create a new message
409
+ # end
410
+ #
411
+ # # GET message_url(:id => 1)
412
+ # def show
413
+ # # find and return a specific message
414
+ # end
415
+ #
416
+ # # GET edit_message_url(:id => 1)
417
+ # def edit
418
+ # # return an HTML form for editing a specific message
419
+ # end
420
+ #
421
+ # # PUT message_url(:id => 1)
422
+ # def update
423
+ # # find and update a specific message
424
+ # end
425
+ #
426
+ # # DELETE message_url(:id => 1)
427
+ # def destroy
428
+ # # delete a specific message
429
+ # end
430
+ # end
431
+ #
432
+ # Along with the routes themselves, +resources+ generates named routes for use in
433
+ # controllers and views. <tt>map.resources :messages</tt> produces the following named routes and helpers:
434
+ #
435
+ # Named Route Helpers
436
+ # ============ =====================================================
437
+ # messages messages_url, hash_for_messages_url,
438
+ # messages_path, hash_for_messages_path
439
+ #
440
+ # message message_url(id), hash_for_message_url(id),
441
+ # message_path(id), hash_for_message_path(id)
442
+ #
443
+ # new_message new_message_url, hash_for_new_message_url,
444
+ # new_message_path, hash_for_new_message_path
445
+ #
446
+ # edit_message edit_message_url(id), hash_for_edit_message_url(id),
447
+ # edit_message_path(id), hash_for_edit_message_path(id)
448
+ #
449
+ # You can use these helpers instead of +url_for+ or methods that take +url_for+ parameters. For example:
450
+ #
451
+ # redirect_to :controller => 'messages', :action => 'index'
452
+ # # and
453
+ # <%= link_to "edit this message", :controller => 'messages', :action => 'edit', :id => @message.id %>
454
+ #
455
+ # now become:
456
+ #
457
+ # redirect_to messages_url
458
+ # # and
459
+ # <%= link_to "edit this message", edit_message_url(@message) # calls @message.id automatically
460
+ #
461
+ # Since web browsers don't support the PUT and DELETE verbs, you will need to add a parameter '_method' to your
462
+ # form tags. The form helpers make this a little easier. For an update form with a <tt>@message</tt> object:
463
+ #
464
+ # <%= form_tag message_path(@message), :method => :put %>
465
+ #
466
+ # or
467
+ #
468
+ # <% form_for :message, @message, :url => message_path(@message), :html => {:method => :put} do |f| %>
469
+ #
470
+ # or
471
+ #
472
+ # <% form_for @message do |f| %>
473
+ #
474
+ # which takes into account whether <tt>@message</tt> is a new record or not and generates the
475
+ # path and method accordingly.
476
+ #
477
+ # The +resources+ method accepts the following options to customize the resulting routes:
478
+ # * <tt>:collection</tt> - Add named routes for other actions that operate on the collection.
479
+ # Takes a hash of <tt>#{action} => #{method}</tt>, where method is <tt>:get</tt>/<tt>:post</tt>/<tt>:put</tt>/<tt>:delete</tt>,
480
+ # an array of any of the previous, or <tt>:any</tt> if the method does not matter.
481
+ # These routes map to a URL like /messages/rss, with a route of +rss_messages_url+.
482
+ # * <tt>:member</tt> - Same as <tt>:collection</tt>, but for actions that operate on a specific member.
483
+ # * <tt>:new</tt> - Same as <tt>:collection</tt>, but for actions that operate on the new \resource action.
484
+ # * <tt>:controller</tt> - Specify the controller name for the routes.
485
+ # * <tt>:singular</tt> - Specify the singular name used in the member routes.
486
+ # * <tt>:requirements</tt> - Set custom routing parameter requirements; this is a hash of either
487
+ # regular expressions (which must match for the route to match) or extra parameters. For example:
488
+ #
489
+ # map.resource :profile, :path_prefix => ':name', :requirements => { :name => /[a-zA-Z]+/, :extra => 'value' }
490
+ #
491
+ # will only match if the first part is alphabetic, and will pass the parameter :extra to the controller.
492
+ # * <tt>:conditions</tt> - Specify custom routing recognition conditions. \Resources sets the <tt>:method</tt> value for the method-specific routes.
493
+ # * <tt>:as</tt> - Specify a different \resource name to use in the URL path. For example:
494
+ # # products_path == '/productos'
495
+ # map.resources :products, :as => 'productos' do |product|
496
+ # # product_reviews_path(product) == '/productos/1234/comentarios'
497
+ # product.resources :product_reviews, :as => 'comentarios'
498
+ # end
499
+ #
500
+ # * <tt>:has_one</tt> - Specify nested \resources, this is a shorthand for mapping singleton \resources beneath the current.
501
+ # * <tt>:has_many</tt> - Same has <tt>:has_one</tt>, but for plural \resources.
502
+ #
503
+ # You may directly specify the routing association with +has_one+ and +has_many+ like:
504
+ #
505
+ # map.resources :notes, :has_one => :author, :has_many => [:comments, :attachments]
506
+ #
507
+ # This is the same as:
508
+ #
509
+ # map.resources :notes do |notes|
510
+ # notes.resource :author
511
+ # notes.resources :comments
512
+ # notes.resources :attachments
513
+ # end
514
+ #
515
+ # * <tt>:path_names</tt> - Specify different path names for the actions. For example:
516
+ # # new_products_path == '/productos/nuevo'
517
+ # # bids_product_path(1) == '/productos/1/licitacoes'
518
+ # map.resources :products, :as => 'productos', :member => { :bids => :get }, :path_names => { :new => 'nuevo', :bids => 'licitacoes' }
519
+ #
520
+ # You can also set default action names from an environment, like this:
521
+ # config.action_controller.resources_path_names = { :new => 'nuevo', :edit => 'editar' }
522
+ #
523
+ # * <tt>:path_prefix</tt> - Set a prefix to the routes with required route variables.
524
+ #
525
+ # Weblog comments usually belong to a post, so you might use +resources+ like:
526
+ #
527
+ # map.resources :articles
528
+ # map.resources :comments, :path_prefix => '/articles/:article_id'
529
+ #
530
+ # You can nest +resources+ calls to set this automatically:
531
+ #
532
+ # map.resources :articles do |article|
533
+ # article.resources :comments
534
+ # end
535
+ #
536
+ # The comment \resources work the same, but must now include a value for <tt>:article_id</tt>.
537
+ #
538
+ # article_comments_url(@article)
539
+ # article_comment_url(@article, @comment)
540
+ #
541
+ # article_comments_url(:article_id => @article)
542
+ # article_comment_url(:article_id => @article, :id => @comment)
543
+ #
544
+ # If you don't want to load all objects from the database you might want to use the <tt>article_id</tt> directly:
545
+ #
546
+ # articles_comments_url(@comment.article_id, @comment)
547
+ #
548
+ # * <tt>:name_prefix</tt> - Define a prefix for all generated routes, usually ending in an underscore.
549
+ # Use this if you have named routes that may clash.
550
+ #
551
+ # map.resources :tags, :path_prefix => '/books/:book_id', :name_prefix => 'book_'
552
+ # map.resources :tags, :path_prefix => '/toys/:toy_id', :name_prefix => 'toy_'
553
+ #
554
+ # You may also use <tt>:name_prefix</tt> to override the generic named routes in a nested \resource:
555
+ #
556
+ # map.resources :articles do |article|
557
+ # article.resources :comments, :name_prefix => nil
558
+ # end
559
+ #
560
+ # This will yield named \resources like so:
561
+ #
562
+ # comments_url(@article)
563
+ # comment_url(@article, @comment)
564
+ #
565
+ # * <tt>:shallow</tt> - If true, paths for nested resources which reference a specific member
566
+ # (ie. those with an :id parameter) will not use the parent path prefix or name prefix.
567
+ #
568
+ # The <tt>:shallow</tt> option is inherited by any nested resource(s).
569
+ #
570
+ # For example, 'users', 'posts' and 'comments' all use shallow paths with the following nested resources:
571
+ #
572
+ # map.resources :users, :shallow => true do |user|
573
+ # user.resources :posts do |post|
574
+ # post.resources :comments
575
+ # end
576
+ # end
577
+ # # --> GET /users/1/posts (maps to the PostsController#index action as usual)
578
+ # # also adds the usual named route called "user_posts"
579
+ # # --> GET /posts/2 (maps to the PostsController#show action as if it were not nested)
580
+ # # also adds the named route called "post"
581
+ # # --> GET /posts/2/comments (maps to the CommentsController#index action)
582
+ # # also adds the named route called "post_comments"
583
+ # # --> GET /comments/2 (maps to the CommentsController#show action as if it were not nested)
584
+ # # also adds the named route called "comment"
585
+ #
586
+ # You may also use <tt>:shallow</tt> in combination with the +has_one+ and +has_many+ shorthand notations like:
587
+ #
588
+ # map.resources :users, :has_many => { :posts => :comments }, :shallow => true
589
+ #
590
+ # * <tt>:only</tt> and <tt>:except</tt> - Specify which of the seven default actions should be routed to.
591
+ #
592
+ # <tt>:only</tt> and <tt>:except</tt> may be set to <tt>:all</tt>, <tt>:none</tt>, an action name or a
593
+ # list of action names. By default, routes are generated for all seven actions.
594
+ #
595
+ # For example:
596
+ #
597
+ # map.resources :posts, :only => [:index, :show] do |post|
598
+ # post.resources :comments, :except => [:update, :destroy]
599
+ # end
600
+ # # --> GET /posts (maps to the PostsController#index action)
601
+ # # --> POST /posts (fails)
602
+ # # --> GET /posts/1 (maps to the PostsController#show action)
603
+ # # --> DELETE /posts/1 (fails)
604
+ # # --> POST /posts/1/comments (maps to the CommentsController#create action)
605
+ # # --> PUT /posts/1/comments/1 (fails)
606
+ #
607
+ # If <tt>map.resources</tt> is called with multiple resources, they all get the same options applied.
608
+ #
609
+ # Examples:
610
+ #
611
+ # map.resources :messages, :path_prefix => "/thread/:thread_id"
612
+ # # --> GET /thread/7/messages/1
613
+ #
614
+ # map.resources :messages, :collection => { :rss => :get }
615
+ # # --> GET /messages/rss (maps to the #rss action)
616
+ # # also adds a named route called "rss_messages"
617
+ #
618
+ # map.resources :messages, :member => { :mark => :post }
619
+ # # --> POST /messages/1/mark (maps to the #mark action)
620
+ # # also adds a named route called "mark_message"
621
+ #
622
+ # map.resources :messages, :new => { :preview => :post }
623
+ # # --> POST /messages/new/preview (maps to the #preview action)
624
+ # # also adds a named route called "preview_new_message"
625
+ #
626
+ # map.resources :messages, :new => { :new => :any, :preview => :post }
627
+ # # --> POST /messages/new/preview (maps to the #preview action)
628
+ # # also adds a named route called "preview_new_message"
629
+ # # --> /messages/new can be invoked via any request method
630
+ #
631
+ # map.resources :messages, :controller => "categories",
632
+ # :path_prefix => "/category/:category_id",
633
+ # :name_prefix => "category_"
634
+ # # --> GET /categories/7/messages/1
635
+ # # has named route "category_message"
636
+ #
637
+ # The +resources+ method sets HTTP method restrictions on the routes it generates. For example, making an
638
+ # HTTP POST on <tt>new_message_url</tt> will raise a RoutingError exception. The default route in
639
+ # <tt>config/routes.rb</tt> overrides this and allows invalid HTTP methods for \resource routes.
640
+ def resources(*entities, &block)
641
+ options = entities.extract_options!
642
+ entities.each { |entity| map_resource(entity, options.dup, &block) }
643
+ end
644
+
645
+ # Creates named routes for implementing verb-oriented controllers for a singleton \resource.
646
+ # A singleton \resource is global to its current context. For unnested singleton \resources,
647
+ # the \resource is global to the current user visiting the application, such as a user's
648
+ # <tt>/account</tt> profile. For nested singleton \resources, the \resource is global to its parent
649
+ # \resource, such as a <tt>projects</tt> \resource that <tt>has_one :project_manager</tt>.
650
+ # The <tt>project_manager</tt> should be mapped as a singleton \resource under <tt>projects</tt>:
651
+ #
652
+ # map.resources :projects do |project|
653
+ # project.resource :project_manager
654
+ # end
655
+ #
656
+ # See +resources+ for general conventions. These are the main differences:
657
+ # * A singular name is given to <tt>map.resource</tt>. The default controller name is still taken from the plural name.
658
+ # * To specify a custom plural name, use the <tt>:plural</tt> option. There is no <tt>:singular</tt> option.
659
+ # * No default index route is created for the singleton \resource controller.
660
+ # * When nesting singleton \resources, only the singular name is used as the path prefix (example: 'account/messages/1')
661
+ #
662
+ # For example:
663
+ #
664
+ # map.resource :account
665
+ #
666
+ # maps these actions in the Accounts controller:
667
+ #
668
+ # class AccountsController < ActionController::Base
669
+ # # GET new_account_url
670
+ # def new
671
+ # # return an HTML form for describing the new account
672
+ # end
673
+ #
674
+ # # POST account_url
675
+ # def create
676
+ # # create an account
677
+ # end
678
+ #
679
+ # # GET account_url
680
+ # def show
681
+ # # find and return the account
682
+ # end
683
+ #
684
+ # # GET edit_account_url
685
+ # def edit
686
+ # # return an HTML form for editing the account
687
+ # end
688
+ #
689
+ # # PUT account_url
690
+ # def update
691
+ # # find and update the account
692
+ # end
693
+ #
694
+ # # DELETE account_url
695
+ # def destroy
696
+ # # delete the account
697
+ # end
698
+ # end
699
+ #
700
+ # Along with the routes themselves, +resource+ generates named routes for
701
+ # use in controllers and views. <tt>map.resource :account</tt> produces
702
+ # these named routes and helpers:
703
+ #
704
+ # Named Route Helpers
705
+ # ============ =============================================
706
+ # account account_url, hash_for_account_url,
707
+ # account_path, hash_for_account_path
708
+ #
709
+ # new_account new_account_url, hash_for_new_account_url,
710
+ # new_account_path, hash_for_new_account_path
711
+ #
712
+ # edit_account edit_account_url, hash_for_edit_account_url,
713
+ # edit_account_path, hash_for_edit_account_path
714
+ def resource(*entities, &block)
715
+ options = entities.extract_options!
716
+ entities.each { |entity| map_singleton_resource(entity, options.dup, &block) }
717
+ end
718
+
719
+ private
720
+ def map_resource(entities, options = {}, &block)
721
+ resource = Resource.new(entities, options)
722
+
723
+ with_options :controller => resource.controller do |map|
724
+ map_associations(resource, options)
725
+
726
+ if block_given?
727
+ with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
728
+ end
729
+
730
+ map_collection_actions(map, resource)
731
+ map_default_collection_actions(map, resource)
732
+ map_new_actions(map, resource)
733
+ map_member_actions(map, resource)
734
+ end
735
+ end
736
+
737
+ def map_singleton_resource(entities, options = {}, &block)
738
+ resource = SingletonResource.new(entities, options)
739
+
740
+ with_options :controller => resource.controller do |map|
741
+ map_associations(resource, options)
742
+
743
+ if block_given?
744
+ with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
745
+ end
746
+
747
+ map_collection_actions(map, resource)
748
+ map_new_actions(map, resource)
749
+ map_member_actions(map, resource)
750
+ map_default_singleton_actions(map, resource)
751
+ end
752
+ end
753
+
754
+ def map_associations(resource, options)
755
+ map_has_many_associations(resource, options.delete(:has_many), options) if options[:has_many]
756
+
757
+ path_prefix = "#{options.delete(:path_prefix)}#{resource.nesting_path_prefix}"
758
+ name_prefix = "#{options.delete(:name_prefix)}#{resource.nesting_name_prefix}"
759
+
760
+ Array(options[:has_one]).each do |association|
761
+ resource(association, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => path_prefix, :name_prefix => name_prefix))
762
+ end
763
+ end
764
+
765
+ def map_has_many_associations(resource, associations, options)
766
+ case associations
767
+ when Hash
768
+ associations.each do |association,has_many|
769
+ map_has_many_associations(resource, association, options.merge(:has_many => has_many))
770
+ end
771
+ when Array
772
+ associations.each do |association|
773
+ map_has_many_associations(resource, association, options)
774
+ end
775
+ when Symbol, String
776
+ resources(associations, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :has_many => options[:has_many]))
777
+ else
778
+ end
779
+ end
780
+
781
+ def map_collection_actions(map, resource)
782
+ resource.collection_methods.each do |method, actions|
783
+ actions.each do |action|
784
+ [method].flatten.each do |m|
785
+ action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
786
+ action_path ||= action
787
+
788
+ map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.name_prefix}#{resource.plural}", m)
789
+ end
790
+ end
791
+ end
792
+ end
793
+
794
+ def map_default_collection_actions(map, resource)
795
+ index_route_name = "#{resource.name_prefix}#{resource.plural}"
796
+
797
+ if resource.uncountable?
798
+ index_route_name << "_index"
799
+ end
800
+
801
+ map_resource_routes(map, resource, :index, resource.path, index_route_name)
802
+ map_resource_routes(map, resource, :create, resource.path, index_route_name)
803
+ end
804
+
805
+ def map_default_singleton_actions(map, resource)
806
+ map_resource_routes(map, resource, :create, resource.path, "#{resource.shallow_name_prefix}#{resource.singular}")
807
+ end
808
+
809
+ def map_new_actions(map, resource)
810
+ resource.new_methods.each do |method, actions|
811
+ actions.each do |action|
812
+ route_path = resource.new_path
813
+ route_name = "new_#{resource.name_prefix}#{resource.singular}"
814
+
815
+ unless action == :new
816
+ route_path = "#{route_path}#{resource.action_separator}#{action}"
817
+ route_name = "#{action}_#{route_name}"
818
+ end
819
+
820
+ map_resource_routes(map, resource, action, route_path, route_name, method)
821
+ end
822
+ end
823
+ end
824
+
825
+ def map_member_actions(map, resource)
826
+ resource.member_methods.each do |method, actions|
827
+ actions.each do |action|
828
+ [method].flatten.each do |m|
829
+ action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
830
+ action_path ||= ActionController::Base.resources_path_names[action] || action
831
+
832
+ map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m, { :force_id => true })
833
+ end
834
+ end
835
+ end
836
+
837
+ route_path = "#{resource.shallow_name_prefix}#{resource.singular}"
838
+ map_resource_routes(map, resource, :show, resource.member_path, route_path)
839
+ map_resource_routes(map, resource, :update, resource.member_path, route_path)
840
+ map_resource_routes(map, resource, :destroy, resource.member_path, route_path)
841
+ end
842
+
843
+ def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil, resource_options = {} )
844
+ if resource.has_action?(action)
845
+ action_options = action_options_for(action, resource, method, resource_options)
846
+ formatted_route_path = "#{route_path}.:format"
847
+
848
+ if route_name && @set.named_routes[route_name.to_sym].nil?
849
+ map.named_route(route_name, formatted_route_path, action_options)
850
+ else
851
+ map.connect(formatted_route_path, action_options)
852
+ end
853
+ end
854
+ end
855
+
856
+ def add_conditions_for(conditions, method)
857
+ returning({:conditions => conditions.dup}) do |options|
858
+ options[:conditions][:method] = method unless method == :any
859
+ end
860
+ end
861
+
862
+ def action_options_for(action, resource, method = nil, resource_options = {})
863
+ default_options = { :action => action.to_s }
864
+ require_id = !resource.kind_of?(SingletonResource)
865
+ force_id = resource_options[:force_id] && !resource.kind_of?(SingletonResource)
866
+
867
+ case default_options[:action]
868
+ when "index", "new"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements)
869
+ when "create"; default_options.merge(add_conditions_for(resource.conditions, method || :post)).merge(resource.requirements)
870
+ when "show", "edit"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements(require_id))
871
+ when "update"; default_options.merge(add_conditions_for(resource.conditions, method || :put)).merge(resource.requirements(require_id))
872
+ when "destroy"; default_options.merge(add_conditions_for(resource.conditions, method || :delete)).merge(resource.requirements(require_id))
873
+ else default_options.merge(add_conditions_for(resource.conditions, method)).merge(resource.requirements(force_id))
874
+ end
875
+ end
876
+ end
877
+ end
878
+ end