actionpack 2.0.5 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (210) hide show
  1. data/CHANGELOG +149 -7
  2. data/MIT-LICENSE +1 -1
  3. data/README +1 -1
  4. data/Rakefile +5 -6
  5. data/lib/action_controller.rb +2 -2
  6. data/lib/action_controller/assertions/model_assertions.rb +2 -1
  7. data/lib/action_controller/assertions/response_assertions.rb +4 -2
  8. data/lib/action_controller/assertions/routing_assertions.rb +3 -3
  9. data/lib/action_controller/assertions/selector_assertions.rb +30 -27
  10. data/lib/action_controller/assertions/tag_assertions.rb +3 -3
  11. data/lib/action_controller/base.rb +103 -129
  12. data/lib/action_controller/benchmarking.rb +3 -3
  13. data/lib/action_controller/caching.rb +41 -652
  14. data/lib/action_controller/caching/actions.rb +144 -0
  15. data/lib/action_controller/caching/fragments.rb +138 -0
  16. data/lib/action_controller/caching/pages.rb +154 -0
  17. data/lib/action_controller/caching/sql_cache.rb +18 -0
  18. data/lib/action_controller/caching/sweeping.rb +97 -0
  19. data/lib/action_controller/cgi_ext/cookie.rb +27 -23
  20. data/lib/action_controller/cgi_ext/stdinput.rb +1 -0
  21. data/lib/action_controller/cgi_process.rb +6 -4
  22. data/lib/action_controller/components.rb +7 -6
  23. data/lib/action_controller/cookies.rb +31 -19
  24. data/lib/action_controller/dispatcher.rb +51 -84
  25. data/lib/action_controller/filters.rb +295 -421
  26. data/lib/action_controller/flash.rb +1 -6
  27. data/lib/action_controller/headers.rb +31 -0
  28. data/lib/action_controller/helpers.rb +26 -9
  29. data/lib/action_controller/http_authentication.rb +1 -1
  30. data/lib/action_controller/integration.rb +65 -13
  31. data/lib/action_controller/layout.rb +24 -39
  32. data/lib/action_controller/mime_responds.rb +7 -3
  33. data/lib/action_controller/mime_type.rb +25 -9
  34. data/lib/action_controller/mime_types.rb +1 -1
  35. data/lib/action_controller/polymorphic_routes.rb +32 -17
  36. data/lib/action_controller/record_identifier.rb +10 -4
  37. data/lib/action_controller/request.rb +46 -30
  38. data/lib/action_controller/request_forgery_protection.rb +10 -9
  39. data/lib/action_controller/request_profiler.rb +29 -8
  40. data/lib/action_controller/rescue.rb +24 -24
  41. data/lib/action_controller/resources.rb +66 -23
  42. data/lib/action_controller/response.rb +2 -2
  43. data/lib/action_controller/routing.rb +113 -1229
  44. data/lib/action_controller/routing/builder.rb +204 -0
  45. data/lib/action_controller/{routing_optimisation.rb → routing/optimisations.rb} +13 -12
  46. data/lib/action_controller/routing/recognition_optimisation.rb +158 -0
  47. data/lib/action_controller/routing/route.rb +240 -0
  48. data/lib/action_controller/routing/route_set.rb +435 -0
  49. data/lib/action_controller/routing/routing_ext.rb +46 -0
  50. data/lib/action_controller/routing/segments.rb +283 -0
  51. data/lib/action_controller/session/active_record_store.rb +13 -8
  52. data/lib/action_controller/session/cookie_store.rb +20 -17
  53. data/lib/action_controller/session_management.rb +10 -3
  54. data/lib/action_controller/streaming.rb +45 -31
  55. data/lib/action_controller/test_case.rb +33 -23
  56. data/lib/action_controller/test_process.rb +39 -35
  57. data/lib/action_controller/url_rewriter.rb +18 -12
  58. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +1 -1
  59. data/lib/action_pack.rb +1 -1
  60. data/lib/action_pack/version.rb +2 -2
  61. data/lib/action_view.rb +11 -3
  62. data/lib/action_view/base.rb +73 -390
  63. data/lib/action_view/helpers/active_record_helper.rb +83 -62
  64. data/lib/action_view/helpers/asset_tag_helper.rb +101 -44
  65. data/lib/action_view/helpers/atom_feed_helper.rb +35 -7
  66. data/lib/action_view/helpers/benchmark_helper.rb +5 -3
  67. data/lib/action_view/helpers/cache_helper.rb +3 -2
  68. data/lib/action_view/helpers/capture_helper.rb +1 -2
  69. data/lib/action_view/helpers/date_helper.rb +104 -82
  70. data/lib/action_view/helpers/form_helper.rb +148 -75
  71. data/lib/action_view/helpers/form_options_helper.rb +44 -23
  72. data/lib/action_view/helpers/form_tag_helper.rb +22 -13
  73. data/lib/action_view/helpers/javascripts/controls.js +1 -1
  74. data/lib/action_view/helpers/javascripts/dragdrop.js +1 -1
  75. data/lib/action_view/helpers/javascripts/effects.js +1 -1
  76. data/lib/action_view/helpers/number_helper.rb +10 -3
  77. data/lib/action_view/helpers/prototype_helper.rb +61 -29
  78. data/lib/action_view/helpers/record_tag_helper.rb +3 -3
  79. data/lib/action_view/helpers/sanitize_helper.rb +23 -17
  80. data/lib/action_view/helpers/scriptaculous_helper.rb +86 -60
  81. data/lib/action_view/helpers/text_helper.rb +153 -125
  82. data/lib/action_view/helpers/url_helper.rb +83 -28
  83. data/lib/action_view/inline_template.rb +20 -0
  84. data/lib/action_view/partial_template.rb +70 -0
  85. data/lib/action_view/partials.rb +31 -73
  86. data/lib/action_view/template.rb +127 -0
  87. data/lib/action_view/template_error.rb +8 -7
  88. data/lib/action_view/template_finder.rb +177 -0
  89. data/lib/action_view/template_handler.rb +18 -1
  90. data/lib/action_view/template_handlers/builder.rb +10 -2
  91. data/lib/action_view/template_handlers/compilable.rb +128 -0
  92. data/lib/action_view/template_handlers/erb.rb +37 -2
  93. data/lib/action_view/template_handlers/rjs.rb +14 -1
  94. data/lib/action_view/test_case.rb +58 -0
  95. data/test/abstract_unit.rb +1 -1
  96. data/test/active_record_unit.rb +3 -6
  97. data/test/activerecord/active_record_store_test.rb +1 -2
  98. data/test/activerecord/render_partial_with_record_identification_test.rb +158 -41
  99. data/test/adv_attr_test.rb +20 -0
  100. data/test/controller/action_pack_assertions_test.rb +16 -19
  101. data/test/controller/addresses_render_test.rb +1 -1
  102. data/test/controller/assert_select_test.rb +13 -6
  103. data/test/controller/base_test.rb +48 -2
  104. data/test/controller/benchmark_test.rb +1 -2
  105. data/test/controller/caching_test.rb +282 -21
  106. data/test/controller/capture_test.rb +1 -1
  107. data/test/controller/cgi_test.rb +1 -1
  108. data/test/controller/components_test.rb +1 -1
  109. data/test/controller/content_type_test.rb +2 -2
  110. data/test/controller/cookie_test.rb +13 -2
  111. data/test/controller/custom_handler_test.rb +14 -10
  112. data/test/controller/deprecation/deprecated_base_methods_test.rb +1 -1
  113. data/test/controller/dispatcher_test.rb +31 -49
  114. data/test/controller/fake_controllers.rb +17 -0
  115. data/test/controller/fake_models.rb +6 -0
  116. data/test/controller/filter_params_test.rb +14 -8
  117. data/test/controller/filters_test.rb +44 -16
  118. data/test/controller/flash_test.rb +2 -2
  119. data/test/controller/header_test.rb +14 -0
  120. data/test/controller/helper_test.rb +19 -15
  121. data/test/controller/html-scanner/document_test.rb +1 -2
  122. data/test/controller/html-scanner/node_test.rb +1 -2
  123. data/test/controller/html-scanner/sanitizer_test.rb +8 -5
  124. data/test/controller/html-scanner/tag_node_test.rb +1 -2
  125. data/test/controller/html-scanner/text_node_test.rb +2 -3
  126. data/test/controller/html-scanner/tokenizer_test.rb +8 -2
  127. data/test/controller/http_authentication_test.rb +1 -1
  128. data/test/controller/integration_test.rb +14 -16
  129. data/test/controller/integration_upload_test.rb +43 -0
  130. data/test/controller/layout_test.rb +26 -6
  131. data/test/controller/mime_responds_test.rb +39 -7
  132. data/test/controller/mime_type_test.rb +29 -5
  133. data/test/controller/new_render_test.rb +105 -34
  134. data/test/controller/polymorphic_routes_test.rb +32 -20
  135. data/test/controller/record_identifier_test.rb +38 -2
  136. data/test/controller/redirect_test.rb +21 -1
  137. data/test/controller/render_test.rb +59 -15
  138. data/test/controller/request_forgery_protection_test.rb +92 -5
  139. data/test/controller/request_test.rb +64 -6
  140. data/test/controller/rescue_test.rb +22 -6
  141. data/test/controller/resources_test.rb +102 -14
  142. data/test/controller/routing_test.rb +231 -19
  143. data/test/controller/selector_test.rb +2 -2
  144. data/test/controller/send_file_test.rb +14 -3
  145. data/test/controller/session/cookie_store_test.rb +16 -4
  146. data/test/controller/session/mem_cache_store_test.rb +3 -4
  147. data/test/controller/session_fixation_test.rb +1 -1
  148. data/test/controller/session_management_test.rb +23 -1
  149. data/test/controller/test_test.rb +39 -18
  150. data/test/controller/url_rewriter_test.rb +35 -1
  151. data/test/controller/verification_test.rb +1 -1
  152. data/test/controller/view_paths_test.rb +15 -12
  153. data/test/controller/webservice_test.rb +48 -3
  154. data/test/fixtures/bad_customers/_bad_customer.html.erb +1 -0
  155. data/test/fixtures/company.rb +1 -0
  156. data/test/fixtures/customers/_customer.html.erb +1 -0
  157. data/test/fixtures/db_definitions/sqlite.sql +6 -0
  158. data/test/fixtures/functional_caching/_partial.erb +3 -0
  159. data/test/fixtures/functional_caching/fragment_cached.html.erb +2 -0
  160. data/test/fixtures/functional_caching/html_fragment_cached_with_partial.html.erb +1 -0
  161. data/test/fixtures/functional_caching/js_fragment_cached_with_partial.js.rjs +1 -0
  162. data/test/fixtures/good_customers/_good_customer.html.erb +1 -0
  163. data/test/fixtures/mascot.rb +3 -0
  164. data/test/fixtures/mascots.yml +4 -0
  165. data/test/fixtures/mascots/_mascot.html.erb +1 -0
  166. data/test/fixtures/multipart/boundary_problem_file +10 -0
  167. data/test/fixtures/public/javascripts/application.js +1 -0
  168. data/test/fixtures/public/javascripts/controls.js +1 -0
  169. data/test/fixtures/public/javascripts/dragdrop.js +1 -0
  170. data/test/fixtures/public/javascripts/effects.js +1 -0
  171. data/test/fixtures/public/javascripts/prototype.js +1 -0
  172. data/test/fixtures/public/javascripts/version.1.0.js +1 -0
  173. data/test/fixtures/public/stylesheets/version.1.0.css +1 -0
  174. data/test/fixtures/reply.rb +1 -0
  175. data/test/fixtures/shared.html.erb +1 -0
  176. data/test/fixtures/symlink_parent/symlinked_layout.erb +5 -0
  177. data/test/fixtures/test/_customer_counter.erb +1 -0
  178. data/test/fixtures/test/_form.erb +1 -0
  179. data/test/fixtures/test/_labelling_form.erb +1 -0
  180. data/test/fixtures/test/_raise.html.erb +1 -0
  181. data/test/fixtures/test/greeting.js.rjs +1 -0
  182. data/test/fixtures/topics/_topic.html.erb +1 -0
  183. data/test/template/active_record_helper_test.rb +25 -8
  184. data/test/template/asset_tag_helper_test.rb +100 -17
  185. data/test/template/atom_feed_helper_test.rb +29 -1
  186. data/test/template/benchmark_helper_test.rb +10 -22
  187. data/test/template/date_helper_test.rb +455 -153
  188. data/test/template/erb_util_test.rb +10 -42
  189. data/test/template/form_helper_test.rb +192 -66
  190. data/test/template/form_options_helper_test.rb +19 -8
  191. data/test/template/form_tag_helper_test.rb +11 -8
  192. data/test/template/javascript_helper_test.rb +3 -9
  193. data/test/template/number_helper_test.rb +6 -3
  194. data/test/template/prototype_helper_test.rb +27 -40
  195. data/test/template/record_tag_helper_test.rb +54 -0
  196. data/test/template/sanitize_helper_test.rb +5 -6
  197. data/test/template/scriptaculous_helper_test.rb +7 -13
  198. data/test/template/tag_helper_test.rb +3 -6
  199. data/test/template/template_finder_test.rb +73 -0
  200. data/test/template/template_object_test.rb +95 -0
  201. data/test/template/test_test.rb +56 -0
  202. data/test/template/text_helper_test.rb +46 -33
  203. data/test/template/url_helper_test.rb +8 -10
  204. metadata +65 -12
  205. data/lib/action_view/compiled_templates.rb +0 -69
  206. data/test/action_view_test.rb +0 -44
  207. data/test/activerecord/fixtures_test.rb +0 -24
  208. data/test/controller/fragment_store_setting_test.rb +0 -47
  209. data/test/template/compiled_templates_test.rb +0 -197
  210. data/test/template/deprecate_ivars_test.rb +0 -51
@@ -0,0 +1,435 @@
1
+ module ActionController
2
+ module Routing
3
+ class RouteSet #:nodoc:
4
+ # Mapper instances are used to build routes. The object passed to the draw
5
+ # block in config/routes.rb is a Mapper instance.
6
+ #
7
+ # Mapper instances have relatively few instance methods, in order to avoid
8
+ # clashes with named routes.
9
+ class Mapper #:doc:
10
+ def initialize(set) #:nodoc:
11
+ @set = set
12
+ end
13
+
14
+ # Create an unnamed route with the provided +path+ and +options+. See
15
+ # ActionController::Routing for an introduction to routes.
16
+ def connect(path, options = {})
17
+ @set.add_route(path, options)
18
+ end
19
+
20
+ # Creates a named route called "root" for matching the root level request.
21
+ def root(options = {})
22
+ if options.is_a?(Symbol)
23
+ if source_route = @set.named_routes.routes[options]
24
+ options = source_route.defaults.merge({ :conditions => source_route.conditions })
25
+ end
26
+ end
27
+ named_route("root", '', options)
28
+ end
29
+
30
+ def named_route(name, path, options = {}) #:nodoc:
31
+ @set.add_named_route(name, path, options)
32
+ end
33
+
34
+ # Enables the use of resources in a module by setting the name_prefix, path_prefix, and namespace for the model.
35
+ # Example:
36
+ #
37
+ # map.namespace(:admin) do |admin|
38
+ # admin.resources :products,
39
+ # :has_many => [ :tags, :images, :variants ]
40
+ # end
41
+ #
42
+ # This will create +admin_products_url+ pointing to "admin/products", which will look for an Admin::ProductsController.
43
+ # It'll also create +admin_product_tags_url+ pointing to "admin/products/#{product_id}/tags", which will look for
44
+ # Admin::TagsController.
45
+ def namespace(name, options = {}, &block)
46
+ if options[:namespace]
47
+ with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}", :name_prefix => "#{options.delete(:name_prefix)}#{name}_", :namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block)
48
+ else
49
+ with_options({:path_prefix => name, :name_prefix => "#{name}_", :namespace => "#{name}/" }.merge(options), &block)
50
+ end
51
+ end
52
+
53
+ def method_missing(route_name, *args, &proc) #:nodoc:
54
+ super unless args.length >= 1 && proc.nil?
55
+ @set.add_named_route(route_name, *args)
56
+ end
57
+ end
58
+
59
+ # A NamedRouteCollection instance is a collection of named routes, and also
60
+ # maintains an anonymous module that can be used to install helpers for the
61
+ # named routes.
62
+ class NamedRouteCollection #:nodoc:
63
+ include Enumerable
64
+ include ActionController::Routing::Optimisation
65
+ attr_reader :routes, :helpers
66
+
67
+ def initialize
68
+ clear!
69
+ end
70
+
71
+ def clear!
72
+ @routes = {}
73
+ @helpers = []
74
+
75
+ @module ||= Module.new
76
+ @module.instance_methods.each do |selector|
77
+ @module.class_eval { remove_method selector }
78
+ end
79
+ end
80
+
81
+ def add(name, route)
82
+ routes[name.to_sym] = route
83
+ define_named_route_methods(name, route)
84
+ end
85
+
86
+ def get(name)
87
+ routes[name.to_sym]
88
+ end
89
+
90
+ alias []= add
91
+ alias [] get
92
+ alias clear clear!
93
+
94
+ def each
95
+ routes.each { |name, route| yield name, route }
96
+ self
97
+ end
98
+
99
+ def names
100
+ routes.keys
101
+ end
102
+
103
+ def length
104
+ routes.length
105
+ end
106
+
107
+ def reset!
108
+ old_routes = routes.dup
109
+ clear!
110
+ old_routes.each do |name, route|
111
+ add(name, route)
112
+ end
113
+ end
114
+
115
+ def install(destinations = [ActionController::Base, ActionView::Base], regenerate = false)
116
+ reset! if regenerate
117
+ Array(destinations).each do |dest|
118
+ dest.send! :include, @module
119
+ end
120
+ end
121
+
122
+ private
123
+ def url_helper_name(name, kind = :url)
124
+ :"#{name}_#{kind}"
125
+ end
126
+
127
+ def hash_access_name(name, kind = :url)
128
+ :"hash_for_#{name}_#{kind}"
129
+ end
130
+
131
+ def define_named_route_methods(name, route)
132
+ {:url => {:only_path => false}, :path => {:only_path => true}}.each do |kind, opts|
133
+ hash = route.defaults.merge(:use_route => name).merge(opts)
134
+ define_hash_access route, name, kind, hash
135
+ define_url_helper route, name, kind, hash
136
+ end
137
+ end
138
+
139
+ def define_hash_access(route, name, kind, options)
140
+ selector = hash_access_name(name, kind)
141
+ @module.module_eval <<-end_eval # We use module_eval to avoid leaks
142
+ def #{selector}(options = nil)
143
+ options ? #{options.inspect}.merge(options) : #{options.inspect}
144
+ end
145
+ protected :#{selector}
146
+ end_eval
147
+ helpers << selector
148
+ end
149
+
150
+ def define_url_helper(route, name, kind, options)
151
+ selector = url_helper_name(name, kind)
152
+ # The segment keys used for positional paramters
153
+
154
+ hash_access_method = hash_access_name(name, kind)
155
+
156
+ # allow ordered parameters to be associated with corresponding
157
+ # dynamic segments, so you can do
158
+ #
159
+ # foo_url(bar, baz, bang)
160
+ #
161
+ # instead of
162
+ #
163
+ # foo_url(:bar => bar, :baz => baz, :bang => bang)
164
+ #
165
+ # Also allow options hash, so you can do
166
+ #
167
+ # foo_url(bar, baz, bang, :sort_by => 'baz')
168
+ #
169
+ @module.module_eval <<-end_eval # We use module_eval to avoid leaks
170
+ def #{selector}(*args)
171
+ #{generate_optimisation_block(route, kind)}
172
+
173
+ opts = if args.empty? || Hash === args.first
174
+ args.first || {}
175
+ else
176
+ options = args.extract_options!
177
+ args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)|
178
+ h[k] = v
179
+ h
180
+ end
181
+ options.merge(args)
182
+ end
183
+
184
+ url_for(#{hash_access_method}(opts))
185
+ end
186
+ protected :#{selector}
187
+ end_eval
188
+ helpers << selector
189
+ end
190
+ end
191
+
192
+ attr_accessor :routes, :named_routes, :configuration_file
193
+
194
+ def initialize
195
+ self.routes = []
196
+ self.named_routes = NamedRouteCollection.new
197
+ end
198
+
199
+ # Subclasses and plugins may override this method to specify a different
200
+ # RouteBuilder instance, so that other route DSL's can be created.
201
+ def builder
202
+ @builder ||= RouteBuilder.new
203
+ end
204
+
205
+ def draw
206
+ clear!
207
+ yield Mapper.new(self)
208
+ install_helpers
209
+ end
210
+
211
+ def clear!
212
+ routes.clear
213
+ named_routes.clear
214
+ @combined_regexp = nil
215
+ @routes_by_controller = nil
216
+ # This will force routing/recognition_optimization.rb
217
+ # to refresh optimisations.
218
+ @compiled_recognize_optimized = nil
219
+ end
220
+
221
+ def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false)
222
+ Array(destinations).each { |d| d.module_eval { include Helpers } }
223
+ named_routes.install(destinations, regenerate_code)
224
+ end
225
+
226
+ def empty?
227
+ routes.empty?
228
+ end
229
+
230
+ def load!
231
+ Routing.use_controllers! nil # Clear the controller cache so we may discover new ones
232
+ clear!
233
+ load_routes!
234
+ install_helpers
235
+ end
236
+
237
+ # reload! will always force a reload whereas load checks the timestamp first
238
+ alias reload! load!
239
+
240
+ def reload
241
+ if @routes_last_modified && configuration_file
242
+ mtime = File.stat(configuration_file).mtime
243
+ # if it hasn't been changed, then just return
244
+ return if mtime == @routes_last_modified
245
+ # if it has changed then record the new time and fall to the load! below
246
+ @routes_last_modified = mtime
247
+ end
248
+ load!
249
+ end
250
+
251
+ def load_routes!
252
+ if configuration_file
253
+ load configuration_file
254
+ @routes_last_modified = File.stat(configuration_file).mtime
255
+ else
256
+ add_route ":controller/:action/:id"
257
+ end
258
+ end
259
+
260
+ def add_route(path, options = {})
261
+ route = builder.build(path, options)
262
+ routes << route
263
+ route
264
+ end
265
+
266
+ def add_named_route(name, path, options = {})
267
+ # TODO - is options EVER used?
268
+ name = options[:name_prefix] + name.to_s if options[:name_prefix]
269
+ named_routes[name.to_sym] = add_route(path, options)
270
+ end
271
+
272
+ def options_as_params(options)
273
+ # If an explicit :controller was given, always make :action explicit
274
+ # too, so that action expiry works as expected for things like
275
+ #
276
+ # generate({:controller => 'content'}, {:controller => 'content', :action => 'show'})
277
+ #
278
+ # (the above is from the unit tests). In the above case, because the
279
+ # controller was explicitly given, but no action, the action is implied to
280
+ # be "index", not the recalled action of "show".
281
+ #
282
+ # great fun, eh?
283
+
284
+ options_as_params = options.clone
285
+ options_as_params[:action] ||= 'index' if options[:controller]
286
+ options_as_params[:action] = options_as_params[:action].to_s if options_as_params[:action]
287
+ options_as_params
288
+ end
289
+
290
+ def build_expiry(options, recall)
291
+ recall.inject({}) do |expiry, (key, recalled_value)|
292
+ expiry[key] = (options.key?(key) && options[key].to_param != recalled_value.to_param)
293
+ expiry
294
+ end
295
+ end
296
+
297
+ # Generate the path indicated by the arguments, and return an array of
298
+ # the keys that were not used to generate it.
299
+ def extra_keys(options, recall={})
300
+ generate_extras(options, recall).last
301
+ end
302
+
303
+ def generate_extras(options, recall={})
304
+ generate(options, recall, :generate_extras)
305
+ end
306
+
307
+ def generate(options, recall = {}, method=:generate)
308
+ named_route_name = options.delete(:use_route)
309
+ generate_all = options.delete(:generate_all)
310
+ if named_route_name
311
+ named_route = named_routes[named_route_name]
312
+ options = named_route.parameter_shell.merge(options)
313
+ end
314
+
315
+ options = options_as_params(options)
316
+ expire_on = build_expiry(options, recall)
317
+
318
+ if options[:controller]
319
+ options[:controller] = options[:controller].to_s
320
+ end
321
+ # if the controller has changed, make sure it changes relative to the
322
+ # current controller module, if any. In other words, if we're currently
323
+ # on admin/get, and the new controller is 'set', the new controller
324
+ # should really be admin/set.
325
+ if !named_route && expire_on[:controller] && options[:controller] && options[:controller][0] != ?/
326
+ old_parts = recall[:controller].split('/')
327
+ new_parts = options[:controller].split('/')
328
+ parts = old_parts[0..-(new_parts.length + 1)] + new_parts
329
+ options[:controller] = parts.join('/')
330
+ end
331
+
332
+ # drop the leading '/' on the controller name
333
+ options[:controller] = options[:controller][1..-1] if options[:controller] && options[:controller][0] == ?/
334
+ merged = recall.merge(options)
335
+
336
+ if named_route
337
+ path = named_route.generate(options, merged, expire_on)
338
+ if path.nil?
339
+ raise_named_route_error(options, named_route, named_route_name)
340
+ else
341
+ return path
342
+ end
343
+ else
344
+ merged[:action] ||= 'index'
345
+ options[:action] ||= 'index'
346
+
347
+ controller = merged[:controller]
348
+ action = merged[:action]
349
+
350
+ raise RoutingError, "Need controller and action!" unless controller && action
351
+
352
+ if generate_all
353
+ # Used by caching to expire all paths for a resource
354
+ return routes.collect do |route|
355
+ route.send!(method, options, merged, expire_on)
356
+ end.compact
357
+ end
358
+
359
+ # don't use the recalled keys when determining which routes to check
360
+ routes = routes_by_controller[controller][action][options.keys.sort_by { |x| x.object_id }]
361
+
362
+ routes.each do |route|
363
+ results = route.send!(method, options, merged, expire_on)
364
+ return results if results && (!results.is_a?(Array) || results.first)
365
+ end
366
+ end
367
+
368
+ raise RoutingError, "No route matches #{options.inspect}"
369
+ end
370
+
371
+ # try to give a helpful error message when named route generation fails
372
+ def raise_named_route_error(options, named_route, named_route_name)
373
+ diff = named_route.requirements.diff(options)
374
+ unless diff.empty?
375
+ raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect}, expected: #{named_route.requirements.inspect}, diff: #{named_route.requirements.diff(options).inspect}"
376
+ else
377
+ required_segments = named_route.segments.select {|seg| (!seg.optional?) && (!seg.is_a?(DividerSegment)) }
378
+ required_keys_or_values = required_segments.map { |seg| seg.key rescue seg.value } # we want either the key or the value from the segment
379
+ raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect} - you may have ambiguous routes, or you may need to supply additional parameters for this route. content_url has the following required parameters: #{required_keys_or_values.inspect} - are they all satisfied?"
380
+ end
381
+ end
382
+
383
+ def recognize(request)
384
+ params = recognize_path(request.path, extract_request_environment(request))
385
+ request.path_parameters = params.with_indifferent_access
386
+ "#{params[:controller].camelize}Controller".constantize
387
+ end
388
+
389
+ def recognize_path(path, environment={})
390
+ raise "Not optimized! Check that routing/recognition_optimisation overrides RouteSet#recognize_path."
391
+ end
392
+
393
+ def routes_by_controller
394
+ @routes_by_controller ||= Hash.new do |controller_hash, controller|
395
+ controller_hash[controller] = Hash.new do |action_hash, action|
396
+ action_hash[action] = Hash.new do |key_hash, keys|
397
+ key_hash[keys] = routes_for_controller_and_action_and_keys(controller, action, keys)
398
+ end
399
+ end
400
+ end
401
+ end
402
+
403
+ def routes_for(options, merged, expire_on)
404
+ raise "Need controller and action!" unless controller && action
405
+ controller = merged[:controller]
406
+ merged = options if expire_on[:controller]
407
+ action = merged[:action] || 'index'
408
+
409
+ routes_by_controller[controller][action][merged.keys]
410
+ end
411
+
412
+ def routes_for_controller_and_action(controller, action)
413
+ selected = routes.select do |route|
414
+ route.matches_controller_and_action? controller, action
415
+ end
416
+ (selected.length == routes.length) ? routes : selected
417
+ end
418
+
419
+ def routes_for_controller_and_action_and_keys(controller, action, keys)
420
+ selected = routes.select do |route|
421
+ route.matches_controller_and_action? controller, action
422
+ end
423
+ selected.sort_by do |route|
424
+ (keys - route.significant_keys).length
425
+ end
426
+ end
427
+
428
+ # Subclasses and plugins may override this method to extract further attributes
429
+ # from the request, for use by route conditions and such.
430
+ def extract_request_environment(request)
431
+ { :method => request.method }
432
+ end
433
+ end
434
+ end
435
+ end
@@ -0,0 +1,46 @@
1
+
2
+ class Object
3
+ def to_param
4
+ to_s
5
+ end
6
+ end
7
+
8
+ class TrueClass
9
+ def to_param
10
+ self
11
+ end
12
+ end
13
+
14
+ class FalseClass
15
+ def to_param
16
+ self
17
+ end
18
+ end
19
+
20
+ class NilClass
21
+ def to_param
22
+ self
23
+ end
24
+ end
25
+
26
+ class Regexp #:nodoc:
27
+ def number_of_captures
28
+ Regexp.new("|#{source}").match('').captures.length
29
+ end
30
+
31
+ class << self
32
+ def optionalize(pattern)
33
+ case unoptionalize(pattern)
34
+ when /\A(.|\(.*\))\Z/ then "#{pattern}?"
35
+ else "(?:#{pattern})?"
36
+ end
37
+ end
38
+
39
+ def unoptionalize(pattern)
40
+ [/\A\(\?:(.*)\)\?\Z/, /\A(.|\(.*\))\?\Z/].each do |regexp|
41
+ return $1 if regexp =~ pattern
42
+ end
43
+ return pattern
44
+ end
45
+ end
46
+ end