eactionpack 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (338) hide show
  1. data/CHANGELOG +7 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README +469 -0
  4. data/RUNNING_UNIT_TESTS +24 -0
  5. data/Rakefile +146 -0
  6. data/install.rb +30 -0
  7. data/lib/action_controller.rb +79 -0
  8. data/lib/action_controller/assertions.rb +69 -0
  9. data/lib/action_controller/assertions/dom_assertions.rb +39 -0
  10. data/lib/action_controller/assertions/model_assertions.rb +20 -0
  11. data/lib/action_controller/assertions/response_assertions.rb +172 -0
  12. data/lib/action_controller/assertions/routing_assertions.rb +146 -0
  13. data/lib/action_controller/assertions/selector_assertions.rb +491 -0
  14. data/lib/action_controller/assertions/tag_assertions.rb +130 -0
  15. data/lib/action_controller/base.rb +1288 -0
  16. data/lib/action_controller/benchmarking.rb +94 -0
  17. data/lib/action_controller/caching.rb +72 -0
  18. data/lib/action_controller/caching/actions.rb +144 -0
  19. data/lib/action_controller/caching/fragments.rb +138 -0
  20. data/lib/action_controller/caching/pages.rb +154 -0
  21. data/lib/action_controller/caching/sql_cache.rb +18 -0
  22. data/lib/action_controller/caching/sweeping.rb +97 -0
  23. data/lib/action_controller/cgi_ext.rb +16 -0
  24. data/lib/action_controller/cgi_ext/cookie.rb +110 -0
  25. data/lib/action_controller/cgi_ext/query_extension.rb +22 -0
  26. data/lib/action_controller/cgi_ext/session.rb +73 -0
  27. data/lib/action_controller/cgi_ext/stdinput.rb +24 -0
  28. data/lib/action_controller/cgi_process.rb +223 -0
  29. data/lib/action_controller/components.rb +166 -0
  30. data/lib/action_controller/cookies.rb +96 -0
  31. data/lib/action_controller/dispatcher.rb +162 -0
  32. data/lib/action_controller/filters.rb +642 -0
  33. data/lib/action_controller/flash.rb +172 -0
  34. data/lib/action_controller/headers.rb +31 -0
  35. data/lib/action_controller/helpers.rb +221 -0
  36. data/lib/action_controller/http_authentication.rb +124 -0
  37. data/lib/action_controller/integration.rb +634 -0
  38. data/lib/action_controller/layout.rb +309 -0
  39. data/lib/action_controller/mime_responds.rb +173 -0
  40. data/lib/action_controller/mime_type.rb +186 -0
  41. data/lib/action_controller/mime_types.rb +20 -0
  42. data/lib/action_controller/polymorphic_routes.rb +191 -0
  43. data/lib/action_controller/record_identifier.rb +102 -0
  44. data/lib/action_controller/request.rb +764 -0
  45. data/lib/action_controller/request_forgery_protection.rb +140 -0
  46. data/lib/action_controller/request_profiler.rb +169 -0
  47. data/lib/action_controller/rescue.rb +258 -0
  48. data/lib/action_controller/resources.rb +572 -0
  49. data/lib/action_controller/response.rb +76 -0
  50. data/lib/action_controller/routing.rb +387 -0
  51. data/lib/action_controller/routing/builder.rb +203 -0
  52. data/lib/action_controller/routing/optimisations.rb +120 -0
  53. data/lib/action_controller/routing/recognition_optimisation.rb +162 -0
  54. data/lib/action_controller/routing/route.rb +240 -0
  55. data/lib/action_controller/routing/route_set.rb +436 -0
  56. data/lib/action_controller/routing/routing_ext.rb +46 -0
  57. data/lib/action_controller/routing/segments.rb +283 -0
  58. data/lib/action_controller/session/active_record_store.rb +340 -0
  59. data/lib/action_controller/session/cookie_store.rb +166 -0
  60. data/lib/action_controller/session/drb_server.rb +32 -0
  61. data/lib/action_controller/session/drb_store.rb +35 -0
  62. data/lib/action_controller/session/mem_cache_store.rb +98 -0
  63. data/lib/action_controller/session_management.rb +158 -0
  64. data/lib/action_controller/status_codes.rb +88 -0
  65. data/lib/action_controller/streaming.rb +155 -0
  66. data/lib/action_controller/templates/rescues/_request_and_response.erb +24 -0
  67. data/lib/action_controller/templates/rescues/_trace.erb +26 -0
  68. data/lib/action_controller/templates/rescues/diagnostics.erb +11 -0
  69. data/lib/action_controller/templates/rescues/layout.erb +29 -0
  70. data/lib/action_controller/templates/rescues/missing_template.erb +2 -0
  71. data/lib/action_controller/templates/rescues/routing_error.erb +10 -0
  72. data/lib/action_controller/templates/rescues/template_error.erb +21 -0
  73. data/lib/action_controller/templates/rescues/unknown_action.erb +2 -0
  74. data/lib/action_controller/test_case.rb +83 -0
  75. data/lib/action_controller/test_process.rb +526 -0
  76. data/lib/action_controller/url_rewriter.rb +142 -0
  77. data/lib/action_controller/vendor/html-scanner/html/document.rb +68 -0
  78. data/lib/action_controller/vendor/html-scanner/html/node.rb +537 -0
  79. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +173 -0
  80. data/lib/action_controller/vendor/html-scanner/html/selector.rb +828 -0
  81. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +105 -0
  82. data/lib/action_controller/vendor/html-scanner/html/version.rb +11 -0
  83. data/lib/action_controller/verification.rb +130 -0
  84. data/lib/action_pack.rb +24 -0
  85. data/lib/action_pack/version.rb +9 -0
  86. data/lib/action_view.rb +44 -0
  87. data/lib/action_view/base.rb +335 -0
  88. data/lib/action_view/helpers/active_record_helper.rb +276 -0
  89. data/lib/action_view/helpers/asset_tag_helper.rb +599 -0
  90. data/lib/action_view/helpers/atom_feed_helper.rb +143 -0
  91. data/lib/action_view/helpers/benchmark_helper.rb +33 -0
  92. data/lib/action_view/helpers/cache_helper.rb +40 -0
  93. data/lib/action_view/helpers/capture_helper.rb +161 -0
  94. data/lib/action_view/helpers/date_helper.rb +711 -0
  95. data/lib/action_view/helpers/debug_helper.rb +31 -0
  96. data/lib/action_view/helpers/form_helper.rb +767 -0
  97. data/lib/action_view/helpers/form_options_helper.rb +458 -0
  98. data/lib/action_view/helpers/form_tag_helper.rb +458 -0
  99. data/lib/action_view/helpers/javascript_helper.rb +148 -0
  100. data/lib/action_view/helpers/number_helper.rb +186 -0
  101. data/lib/action_view/helpers/record_identification_helper.rb +20 -0
  102. data/lib/action_view/helpers/record_tag_helper.rb +59 -0
  103. data/lib/action_view/helpers/sanitize_helper.rb +229 -0
  104. data/lib/action_view/helpers/tag_helper.rb +134 -0
  105. data/lib/action_view/helpers/text_helper.rb +507 -0
  106. data/lib/action_view/helpers/url_helper.rb +573 -0
  107. data/lib/action_view/inline_template.rb +20 -0
  108. data/lib/action_view/partial_template.rb +70 -0
  109. data/lib/action_view/partials.rb +158 -0
  110. data/lib/action_view/template.rb +125 -0
  111. data/lib/action_view/template_error.rb +110 -0
  112. data/lib/action_view/template_finder.rb +176 -0
  113. data/lib/action_view/template_handler.rb +34 -0
  114. data/lib/action_view/template_handlers/builder.rb +27 -0
  115. data/lib/action_view/template_handlers/compilable.rb +128 -0
  116. data/lib/action_view/template_handlers/erb.rb +56 -0
  117. data/lib/action_view/test_case.rb +58 -0
  118. data/lib/actionpack.rb +1 -0
  119. data/test/abstract_unit.rb +36 -0
  120. data/test/active_record_unit.rb +105 -0
  121. data/test/activerecord/active_record_store_test.rb +141 -0
  122. data/test/activerecord/render_partial_with_record_identification_test.rb +191 -0
  123. data/test/adv_attr_test.rb +20 -0
  124. data/test/controller/action_pack_assertions_test.rb +543 -0
  125. data/test/controller/addresses_render_test.rb +43 -0
  126. data/test/controller/assert_select_test.rb +331 -0
  127. data/test/controller/base_test.rb +219 -0
  128. data/test/controller/benchmark_test.rb +32 -0
  129. data/test/controller/caching_test.rb +581 -0
  130. data/test/controller/capture_test.rb +89 -0
  131. data/test/controller/cgi_test.rb +116 -0
  132. data/test/controller/components_test.rb +140 -0
  133. data/test/controller/content_type_test.rb +139 -0
  134. data/test/controller/controller_fixtures/app/controllers/admin/user_controller.rb +0 -0
  135. data/test/controller/controller_fixtures/app/controllers/user_controller.rb +0 -0
  136. data/test/controller/controller_fixtures/vendor/plugins/bad_plugin/lib/plugin_controller.rb +0 -0
  137. data/test/controller/cookie_test.rb +146 -0
  138. data/test/controller/custom_handler_test.rb +45 -0
  139. data/test/controller/deprecation/deprecated_base_methods_test.rb +37 -0
  140. data/test/controller/dispatcher_test.rb +105 -0
  141. data/test/controller/fake_controllers.rb +33 -0
  142. data/test/controller/fake_models.rb +11 -0
  143. data/test/controller/filter_params_test.rb +49 -0
  144. data/test/controller/filters_test.rb +881 -0
  145. data/test/controller/flash_test.rb +146 -0
  146. data/test/controller/header_test.rb +14 -0
  147. data/test/controller/helper_test.rb +210 -0
  148. data/test/controller/html-scanner/cdata_node_test.rb +15 -0
  149. data/test/controller/html-scanner/document_test.rb +148 -0
  150. data/test/controller/html-scanner/node_test.rb +89 -0
  151. data/test/controller/html-scanner/sanitizer_test.rb +269 -0
  152. data/test/controller/html-scanner/tag_node_test.rb +238 -0
  153. data/test/controller/html-scanner/text_node_test.rb +50 -0
  154. data/test/controller/html-scanner/tokenizer_test.rb +131 -0
  155. data/test/controller/http_authentication_test.rb +54 -0
  156. data/test/controller/integration_test.rb +252 -0
  157. data/test/controller/integration_upload_test.rb +43 -0
  158. data/test/controller/layout_test.rb +255 -0
  159. data/test/controller/mime_responds_test.rb +514 -0
  160. data/test/controller/mime_type_test.rb +84 -0
  161. data/test/controller/new_render_test.rb +843 -0
  162. data/test/controller/polymorphic_routes_test.rb +174 -0
  163. data/test/controller/record_identifier_test.rb +139 -0
  164. data/test/controller/redirect_test.rb +289 -0
  165. data/test/controller/render_test.rb +484 -0
  166. data/test/controller/request_forgery_protection_test.rb +305 -0
  167. data/test/controller/request_test.rb +928 -0
  168. data/test/controller/rescue_test.rb +517 -0
  169. data/test/controller/resources_test.rb +873 -0
  170. data/test/controller/routing_test.rb +2464 -0
  171. data/test/controller/selector_test.rb +628 -0
  172. data/test/controller/send_file_test.rb +138 -0
  173. data/test/controller/session/cookie_store_test.rb +258 -0
  174. data/test/controller/session/mem_cache_store_test.rb +181 -0
  175. data/test/controller/session_fixation_test.rb +89 -0
  176. data/test/controller/session_management_test.rb +178 -0
  177. data/test/controller/test_test.rb +695 -0
  178. data/test/controller/url_rewriter_test.rb +310 -0
  179. data/test/controller/verification_test.rb +270 -0
  180. data/test/controller/view_paths_test.rb +140 -0
  181. data/test/controller/webservice_test.rb +229 -0
  182. data/test/fixtures/addresses/list.erb +1 -0
  183. data/test/fixtures/bad_customers/_bad_customer.html.erb +1 -0
  184. data/test/fixtures/companies.yml +24 -0
  185. data/test/fixtures/company.rb +10 -0
  186. data/test/fixtures/content_type/render_default_content_types_for_respond_to.rhtml +1 -0
  187. data/test/fixtures/content_type/render_default_for_js.js.erb +1 -0
  188. data/test/fixtures/content_type/render_default_for_rhtml.rhtml +1 -0
  189. data/test/fixtures/content_type/render_default_for_rxml.rxml +1 -0
  190. data/test/fixtures/customers/_customer.html.erb +1 -0
  191. data/test/fixtures/db_definitions/sqlite.sql +49 -0
  192. data/test/fixtures/developer.rb +9 -0
  193. data/test/fixtures/developers.yml +21 -0
  194. data/test/fixtures/developers_projects.yml +13 -0
  195. data/test/fixtures/fun/games/hello_world.erb +1 -0
  196. data/test/fixtures/functional_caching/_partial.erb +3 -0
  197. data/test/fixtures/functional_caching/fragment_cached.html.erb +2 -0
  198. data/test/fixtures/functional_caching/html_fragment_cached_with_partial.html.erb +1 -0
  199. data/test/fixtures/functional_caching/js_fragment_cached_with_partial.js.rjs +1 -0
  200. data/test/fixtures/good_customers/_good_customer.html.erb +1 -0
  201. data/test/fixtures/helpers/abc_helper.rb +5 -0
  202. data/test/fixtures/helpers/fun/games_helper.rb +3 -0
  203. data/test/fixtures/helpers/fun/pdf_helper.rb +3 -0
  204. data/test/fixtures/layout_tests/alt/hello.rhtml +1 -0
  205. data/test/fixtures/layout_tests/layouts/controller_name_space/nested.rhtml +1 -0
  206. data/test/fixtures/layout_tests/layouts/item.rhtml +1 -0
  207. data/test/fixtures/layout_tests/layouts/layout_test.rhtml +1 -0
  208. data/test/fixtures/layout_tests/layouts/multiple_extensions.html.erb +1 -0
  209. data/test/fixtures/layout_tests/layouts/third_party_template_library.mab +1 -0
  210. data/test/fixtures/layout_tests/views/hello.rhtml +1 -0
  211. data/test/fixtures/layouts/block_with_layout.erb +3 -0
  212. data/test/fixtures/layouts/builder.builder +3 -0
  213. data/test/fixtures/layouts/partial_with_layout.erb +3 -0
  214. data/test/fixtures/layouts/standard.erb +1 -0
  215. data/test/fixtures/layouts/talk_from_action.erb +2 -0
  216. data/test/fixtures/layouts/yield.erb +2 -0
  217. data/test/fixtures/mascot.rb +3 -0
  218. data/test/fixtures/mascots.yml +4 -0
  219. data/test/fixtures/mascots/_mascot.html.erb +1 -0
  220. data/test/fixtures/multipart/binary_file +0 -0
  221. data/test/fixtures/multipart/boundary_problem_file +10 -0
  222. data/test/fixtures/multipart/bracketed_param +5 -0
  223. data/test/fixtures/multipart/large_text_file +10 -0
  224. data/test/fixtures/multipart/mixed_files +0 -0
  225. data/test/fixtures/multipart/mona_lisa.jpg +0 -0
  226. data/test/fixtures/multipart/single_parameter +5 -0
  227. data/test/fixtures/multipart/text_file +10 -0
  228. data/test/fixtures/override/test/hello_world.erb +1 -0
  229. data/test/fixtures/override2/layouts/test/sub.erb +1 -0
  230. data/test/fixtures/post_test/layouts/post.html.erb +1 -0
  231. data/test/fixtures/post_test/layouts/super_post.iphone.erb +1 -0
  232. data/test/fixtures/post_test/post/index.html.erb +1 -0
  233. data/test/fixtures/post_test/post/index.iphone.erb +1 -0
  234. data/test/fixtures/post_test/super_post/index.html.erb +1 -0
  235. data/test/fixtures/post_test/super_post/index.iphone.erb +1 -0
  236. data/test/fixtures/project.rb +3 -0
  237. data/test/fixtures/projects.yml +7 -0
  238. data/test/fixtures/public/404.html +1 -0
  239. data/test/fixtures/public/500.html +1 -0
  240. data/test/fixtures/public/images/rails.png +0 -0
  241. data/test/fixtures/public/javascripts/application.js +1 -0
  242. data/test/fixtures/public/javascripts/bank.js +1 -0
  243. data/test/fixtures/public/javascripts/robber.js +1 -0
  244. data/test/fixtures/public/javascripts/version.1.0.js +1 -0
  245. data/test/fixtures/public/stylesheets/bank.css +1 -0
  246. data/test/fixtures/public/stylesheets/robber.css +1 -0
  247. data/test/fixtures/public/stylesheets/version.1.0.css +1 -0
  248. data/test/fixtures/replies.yml +15 -0
  249. data/test/fixtures/reply.rb +7 -0
  250. data/test/fixtures/respond_to/all_types_with_layout.html.erb +1 -0
  251. data/test/fixtures/respond_to/custom_constant_handling_without_block.mobile.erb +1 -0
  252. data/test/fixtures/respond_to/iphone_with_html_response_type.html.erb +1 -0
  253. data/test/fixtures/respond_to/iphone_with_html_response_type.iphone.erb +1 -0
  254. data/test/fixtures/respond_to/layouts/missing.html.erb +1 -0
  255. data/test/fixtures/respond_to/layouts/standard.html.erb +1 -0
  256. data/test/fixtures/respond_to/layouts/standard.iphone.erb +1 -0
  257. data/test/fixtures/respond_to/using_defaults.html.erb +1 -0
  258. data/test/fixtures/respond_to/using_defaults.js.rjs +1 -0
  259. data/test/fixtures/respond_to/using_defaults.xml.builder +1 -0
  260. data/test/fixtures/respond_to/using_defaults_with_type_list.html.erb +1 -0
  261. data/test/fixtures/respond_to/using_defaults_with_type_list.js.rjs +1 -0
  262. data/test/fixtures/respond_to/using_defaults_with_type_list.xml.builder +1 -0
  263. data/test/fixtures/scope/test/modgreet.erb +1 -0
  264. data/test/fixtures/shared.html.erb +1 -0
  265. data/test/fixtures/symlink_parent/symlinked_layout.erb +5 -0
  266. data/test/fixtures/test/_customer.erb +1 -0
  267. data/test/fixtures/test/_customer_counter.erb +1 -0
  268. data/test/fixtures/test/_customer_greeting.erb +1 -0
  269. data/test/fixtures/test/_form.erb +1 -0
  270. data/test/fixtures/test/_hash_greeting.erb +1 -0
  271. data/test/fixtures/test/_hash_object.erb +2 -0
  272. data/test/fixtures/test/_hello.builder +1 -0
  273. data/test/fixtures/test/_labelling_form.erb +1 -0
  274. data/test/fixtures/test/_layout_for_partial.html.erb +3 -0
  275. data/test/fixtures/test/_partial.erb +1 -0
  276. data/test/fixtures/test/_partial.html.erb +1 -0
  277. data/test/fixtures/test/_partial.js.erb +1 -0
  278. data/test/fixtures/test/_partial_for_use_in_layout.html.erb +1 -0
  279. data/test/fixtures/test/_partial_only.erb +1 -0
  280. data/test/fixtures/test/_person.erb +2 -0
  281. data/test/fixtures/test/_raise.html.erb +1 -0
  282. data/test/fixtures/test/action_talk_to_layout.erb +2 -0
  283. data/test/fixtures/test/block_content_for.erb +2 -0
  284. data/test/fixtures/test/calling_partial_with_layout.html.erb +1 -0
  285. data/test/fixtures/test/capturing.erb +4 -0
  286. data/test/fixtures/test/content_for.erb +2 -0
  287. data/test/fixtures/test/content_for_concatenated.erb +3 -0
  288. data/test/fixtures/test/content_for_with_parameter.erb +2 -0
  289. data/test/fixtures/test/delete_with_js.rjs +2 -0
  290. data/test/fixtures/test/dot.directory/render_file_with_ivar.erb +1 -0
  291. data/test/fixtures/test/enum_rjs_test.rjs +6 -0
  292. data/test/fixtures/test/erb_content_for.erb +2 -0
  293. data/test/fixtures/test/formatted_html_erb.html.erb +1 -0
  294. data/test/fixtures/test/formatted_xml_erb.builder +1 -0
  295. data/test/fixtures/test/formatted_xml_erb.html.erb +1 -0
  296. data/test/fixtures/test/formatted_xml_erb.xml.erb +1 -0
  297. data/test/fixtures/test/greeting.erb +1 -0
  298. data/test/fixtures/test/greeting.js.rjs +1 -0
  299. data/test/fixtures/test/hello.builder +4 -0
  300. data/test/fixtures/test/hello_world.erb +1 -0
  301. data/test/fixtures/test/hello_world_container.builder +3 -0
  302. data/test/fixtures/test/hello_world_from_rxml.builder +4 -0
  303. data/test/fixtures/test/hello_world_with_layout_false.erb +1 -0
  304. data/test/fixtures/test/hello_xml_world.builder +11 -0
  305. data/test/fixtures/test/list.erb +1 -0
  306. data/test/fixtures/test/non_erb_block_content_for.builder +4 -0
  307. data/test/fixtures/test/potential_conflicts.erb +4 -0
  308. data/test/fixtures/test/render_file_from_template.html.erb +1 -0
  309. data/test/fixtures/test/render_file_with_ivar.erb +1 -0
  310. data/test/fixtures/test/render_file_with_locals.erb +1 -0
  311. data/test/fixtures/test/render_to_string_test.erb +1 -0
  312. data/test/fixtures/test/update_element_with_capture.erb +9 -0
  313. data/test/fixtures/test/using_layout_around_block.html.erb +1 -0
  314. data/test/fixtures/topic.rb +3 -0
  315. data/test/fixtures/topics.yml +22 -0
  316. data/test/fixtures/topics/_topic.html.erb +1 -0
  317. data/test/template/active_record_helper_test.rb +268 -0
  318. data/test/template/asset_tag_helper_test.rb +514 -0
  319. data/test/template/atom_feed_helper_test.rb +179 -0
  320. data/test/template/benchmark_helper_test.rb +60 -0
  321. data/test/template/date_helper_test.rb +1791 -0
  322. data/test/template/deprecated_erb_variable_test.rb +9 -0
  323. data/test/template/erb_util_test.rb +24 -0
  324. data/test/template/form_helper_test.rb +885 -0
  325. data/test/template/form_options_helper_test.rb +1333 -0
  326. data/test/template/form_tag_helper_test.rb +272 -0
  327. data/test/template/javascript_helper_test.rb +73 -0
  328. data/test/template/number_helper_test.rb +97 -0
  329. data/test/template/record_tag_helper_test.rb +54 -0
  330. data/test/template/sanitize_helper_test.rb +48 -0
  331. data/test/template/tag_helper_test.rb +77 -0
  332. data/test/template/template_finder_test.rb +73 -0
  333. data/test/template/template_object_test.rb +95 -0
  334. data/test/template/test_test.rb +56 -0
  335. data/test/template/text_helper_test.rb +367 -0
  336. data/test/template/url_helper_test.rb +544 -0
  337. data/test/testing_sandbox.rb +15 -0
  338. metadata +469 -0
@@ -0,0 +1,203 @@
1
+ module ActionController
2
+ module Routing
3
+ class RouteBuilder #:nodoc:
4
+ attr_accessor :separators, :optional_separators
5
+
6
+ def initialize
7
+ self.separators = Routing::SEPARATORS
8
+ self.optional_separators = %w( / )
9
+ end
10
+
11
+ def separator_pattern(inverted = false)
12
+ "[#{'^' if inverted}#{Regexp.escape(separators.join)}]"
13
+ end
14
+
15
+ def interval_regexp
16
+ Regexp.new "(.*?)(#{separators.source}|$)"
17
+ end
18
+
19
+ def multiline_regexp?(expression)
20
+ expression.options & Regexp::MULTILINE == Regexp::MULTILINE
21
+ end
22
+
23
+ # Accepts a "route path" (a string defining a route), and returns the array
24
+ # of segments that corresponds to it. Note that the segment array is only
25
+ # partially initialized--the defaults and requirements, for instance, need
26
+ # to be set separately, via the +assign_route_options+ method, and the
27
+ # <tt>optional?</tt> method for each segment will not be reliable until after
28
+ # +assign_route_options+ is called, as well.
29
+ def segments_for_route_path(path)
30
+ rest, segments = path, []
31
+
32
+ until rest.empty?
33
+ segment, rest = segment_for rest
34
+ segments << segment
35
+ end
36
+ segments
37
+ end
38
+
39
+ # A factory method that returns a new segment instance appropriate for the
40
+ # format of the given string.
41
+ def segment_for(string)
42
+ segment = case string
43
+ when /\A:(\w+)/
44
+ key = $1.to_sym
45
+ case key
46
+ when :controller then ControllerSegment.new(key)
47
+ else DynamicSegment.new key
48
+ end
49
+ when /\A\*(\w+)/ then PathSegment.new($1.to_sym, :optional => true)
50
+ when /\A\?(.*?)\?/
51
+ returning segment = StaticSegment.new($1) do
52
+ segment.is_optional = true
53
+ end
54
+ when /\A(#{separator_pattern(:inverted)}+)/ then StaticSegment.new($1)
55
+ when Regexp.new(separator_pattern) then
56
+ returning segment = DividerSegment.new($&) do
57
+ segment.is_optional = (optional_separators.include? $&)
58
+ end
59
+ end
60
+ [segment, $~.post_match]
61
+ end
62
+
63
+ # Split the given hash of options into requirement and default hashes. The
64
+ # segments are passed alongside in order to distinguish between default values
65
+ # and requirements.
66
+ def divide_route_options(segments, options)
67
+ options = options.dup
68
+
69
+ if options[:namespace]
70
+ options[:controller] = "#{options.delete(:namespace).sub(/\/$/, '')}/#{options[:controller]}"
71
+ options.delete(:path_prefix)
72
+ options.delete(:name_prefix)
73
+ end
74
+
75
+ requirements = (options.delete(:requirements) || {}).dup
76
+ defaults = (options.delete(:defaults) || {}).dup
77
+ conditions = (options.delete(:conditions) || {}).dup
78
+
79
+ path_keys = segments.collect { |segment| segment.key if segment.respond_to?(:key) }.compact
80
+ options.each do |key, value|
81
+ hash = (path_keys.include?(key) && ! value.is_a?(Regexp)) ? defaults : requirements
82
+ hash[key] = value
83
+ end
84
+
85
+ [defaults, requirements, conditions]
86
+ end
87
+
88
+ # Takes a hash of defaults and a hash of requirements, and assigns them to
89
+ # the segments. Any unused requirements (which do not correspond to a segment)
90
+ # are returned as a hash.
91
+ def assign_route_options(segments, defaults, requirements)
92
+ route_requirements = {} # Requirements that do not belong to a segment
93
+
94
+ segment_named = Proc.new do |key|
95
+ segments.detect { |segment| segment.key == key if segment.respond_to?(:key) }
96
+ end
97
+
98
+ requirements.each do |key, requirement|
99
+ segment = segment_named[key]
100
+ if segment
101
+ raise TypeError, "#{key}: requirements on a path segment must be regular expressions" unless requirement.is_a?(Regexp)
102
+ if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
103
+ raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
104
+ end
105
+ if multiline_regexp?(requirement)
106
+ raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
107
+ end
108
+ segment.regexp = requirement
109
+ else
110
+ route_requirements[key] = requirement
111
+ end
112
+ end
113
+
114
+ defaults.each do |key, default|
115
+ segment = segment_named[key]
116
+ raise ArgumentError, "#{key}: No matching segment exists; cannot assign default" unless segment
117
+ segment.is_optional = true
118
+ segment.default = default.to_param if default
119
+ end
120
+
121
+ assign_default_route_options(segments)
122
+ ensure_required_segments(segments)
123
+ route_requirements
124
+ end
125
+
126
+ # Assign default options, such as 'index' as a default for <tt>:action</tt>. This
127
+ # method must be run *after* user supplied requirements and defaults have
128
+ # been applied to the segments.
129
+ def assign_default_route_options(segments)
130
+ segments.each do |segment|
131
+ next unless segment.is_a? DynamicSegment
132
+ case segment.key
133
+ when :action
134
+ if segment.regexp.nil? || segment.regexp.match('index').to_s == 'index'
135
+ segment.default ||= 'index'
136
+ segment.is_optional = true
137
+ end
138
+ when :id
139
+ if segment.default.nil? && segment.regexp.nil? || segment.regexp =~ ''
140
+ segment.is_optional = true
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ # Makes sure that there are no optional segments that precede a required
147
+ # segment. If any are found that precede a required segment, they are
148
+ # made required.
149
+ def ensure_required_segments(segments)
150
+ allow_optional = true
151
+ segments.reverse_each do |segment|
152
+ allow_optional &&= segment.optional?
153
+ if !allow_optional && segment.optional?
154
+ unless segment.optionality_implied?
155
+ warn "Route segment \"#{segment.to_s}\" cannot be optional because it precedes a required segment. This segment will be required."
156
+ end
157
+ segment.is_optional = false
158
+ elsif allow_optional && segment.respond_to?(:default) && segment.default
159
+ # if a segment has a default, then it is optional
160
+ segment.is_optional = true
161
+ end
162
+ end
163
+ end
164
+
165
+ # Construct and return a route with the given path and options.
166
+ def build(path, options)
167
+ # Wrap the path with slashes
168
+ path = "/#{path}" unless path[0] == ?/
169
+ path = "#{path}/" unless path[-1] == ?/
170
+
171
+ path = "/#{options[:path_prefix].to_s.gsub(/^\//,'')}#{path}" if options[:path_prefix]
172
+
173
+ segments = segments_for_route_path(path)
174
+ defaults, requirements, conditions = divide_route_options(segments, options)
175
+ requirements = assign_route_options(segments, defaults, requirements)
176
+
177
+ route = Route.new
178
+
179
+ route.segments = segments
180
+ route.requirements = requirements
181
+ route.conditions = conditions
182
+
183
+ if !route.significant_keys.include?(:action) && !route.requirements[:action]
184
+ route.requirements[:action] = "index"
185
+ route.significant_keys << :action
186
+ end
187
+
188
+ # Routes cannot use the current string interpolation method
189
+ # if there are user-supplied <tt>:requirements</tt> as the interpolation
190
+ # code won't raise RoutingErrors when generating
191
+ if options.key?(:requirements) || route.requirements.keys.to_set != Routing::ALLOWED_REQUIREMENTS_FOR_OPTIMISATION
192
+ route.optimise = false
193
+ end
194
+
195
+ if !route.significant_keys.include?(:controller)
196
+ raise ArgumentError, "Illegal route: the :controller must be specified!"
197
+ end
198
+
199
+ route
200
+ end
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,120 @@
1
+ module ActionController
2
+ module Routing
3
+ # Much of the slow performance from routes comes from the
4
+ # complexity of expiry, <tt>:requirements</tt> matching, defaults providing
5
+ # and figuring out which url pattern to use. With named routes
6
+ # we can avoid the expense of finding the right route. So if
7
+ # they've provided the right number of arguments, and have no
8
+ # <tt>:requirements</tt>, we can just build up a string and return it.
9
+ #
10
+ # To support building optimisations for other common cases, the
11
+ # generation code is separated into several classes
12
+ module Optimisation
13
+ def generate_optimisation_block(route, kind)
14
+ return "" unless route.optimise?
15
+ OPTIMISERS.inject("") do |memo, klazz|
16
+ memo << klazz.new(route, kind).source_code
17
+ memo
18
+ end
19
+ end
20
+
21
+ class Optimiser
22
+ attr_reader :route, :kind
23
+ def initialize(route, kind)
24
+ @route = route
25
+ @kind = kind
26
+ end
27
+
28
+ def guard_condition
29
+ 'false'
30
+ end
31
+
32
+ def generation_code
33
+ 'nil'
34
+ end
35
+
36
+ def source_code
37
+ if applicable?
38
+ "return #{generation_code} if #{guard_condition}\n"
39
+ else
40
+ "\n"
41
+ end
42
+ end
43
+
44
+ # Temporarily disabled <tt>:url</tt> optimisation pending proper solution to
45
+ # Issues around request.host etc.
46
+ def applicable?
47
+ true
48
+ end
49
+ end
50
+
51
+ # Given a route
52
+ #
53
+ # map.person '/people/:id'
54
+ #
55
+ # If the user calls <tt>person_url(@person)</tt>, we can simply
56
+ # return a string like "/people/#{@person.to_param}"
57
+ # rather than triggering the expensive logic in +url_for+.
58
+ class PositionalArguments < Optimiser
59
+ def guard_condition
60
+ number_of_arguments = route.segment_keys.size
61
+ # if they're using foo_url(:id=>2) it's one
62
+ # argument, but we don't want to generate /foos/id2
63
+ if number_of_arguments == 1
64
+ "(!defined?(default_url_options) || default_url_options.blank?) && defined?(request) && request && args.size == 1 && !args.first.is_a?(Hash)"
65
+ else
66
+ "(!defined?(default_url_options) || default_url_options.blank?) && defined?(request) && request && args.size == #{number_of_arguments}"
67
+ end
68
+ end
69
+
70
+ def generation_code
71
+ elements = []
72
+ idx = 0
73
+
74
+ if kind == :url
75
+ elements << '#{request.protocol}'
76
+ elements << '#{request.host_with_port}'
77
+ end
78
+
79
+ elements << '#{request.relative_url_root if request.relative_url_root}'
80
+
81
+ # The last entry in <tt>route.segments</tt> appears to *always* be a
82
+ # 'divider segment' for '/' but we have assertions to ensure that
83
+ # we don't include the trailing slashes, so skip them.
84
+ (route.segments.size == 1 ? route.segments : route.segments[0..-2]).each do |segment|
85
+ if segment.is_a?(DynamicSegment)
86
+ elements << segment.interpolation_chunk("args[#{idx}].to_param")
87
+ idx += 1
88
+ else
89
+ elements << segment.interpolation_chunk
90
+ end
91
+ end
92
+ %("#{elements * ''}")
93
+ end
94
+ end
95
+
96
+ # This case is mostly the same as the positional arguments case
97
+ # above, but it supports additional query parameters as the last
98
+ # argument
99
+ class PositionalArgumentsWithAdditionalParams < PositionalArguments
100
+ def guard_condition
101
+ "(!defined?(default_url_options) || default_url_options.blank?) && defined?(request) && request && args.size == #{route.segment_keys.size + 1} && !args.last.has_key?(:anchor) && !args.last.has_key?(:port) && !args.last.has_key?(:host)"
102
+ end
103
+
104
+ # This case uses almost the same code as positional arguments,
105
+ # but add an args.last.to_query on the end
106
+ def generation_code
107
+ super.insert(-2, '?#{args.last.to_query}')
108
+ end
109
+
110
+ # To avoid generating "http://localhost/?host=foo.example.com" we
111
+ # can't use this optimisation on routes without any segments
112
+ def applicable?
113
+ super && route.segment_keys.size > 0
114
+ end
115
+ end
116
+
117
+ OPTIMISERS = [PositionalArguments, PositionalArgumentsWithAdditionalParams]
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,162 @@
1
+ module ActionController
2
+ module Routing
3
+ # BEFORE: 0.191446860631307 ms/url
4
+ # AFTER: 0.029847304022858 ms/url
5
+ # Speed up: 6.4 times
6
+ #
7
+ # Route recognition is slow due to one-by-one iterating over
8
+ # a whole routeset (each map.resources generates at least 14 routes)
9
+ # and matching weird regexps on each step.
10
+ #
11
+ # We optimize this by skipping all URI segments that 100% sure can't
12
+ # be matched, moving deeper in a tree of routes (where node == segment)
13
+ # until first possible match is accured. In such case, we start walking
14
+ # a flat list of routes, matching them with accurate matcher.
15
+ # So, first step: search a segment tree for the first relevant index.
16
+ # Second step: iterate routes starting with that index.
17
+ #
18
+ # How tree is walked? We can do a recursive tests, but it's smarter:
19
+ # We just create a tree of if-s and elsif-s matching segments.
20
+ #
21
+ # We have segments of 3 flavors:
22
+ # 1) nil (no segment, route finished)
23
+ # 2) const-dot-dynamic (like "/posts.:xml", "/preview.:size.jpg")
24
+ # 3) const (like "/posts", "/comments")
25
+ # 4) dynamic ("/:id", "file.:size.:extension")
26
+ #
27
+ # We split incoming string into segments and iterate over them.
28
+ # When segment is nil, we drop immediately, on a current node index.
29
+ # When segment is equal to some const, we step into branch.
30
+ # If none constants matched, we step into 'dynamic' branch (it's a last).
31
+ # If we can't match anything, we drop to last index on a level.
32
+ #
33
+ # Note: we maintain the original routes order, so we finish building
34
+ # steps on a first dynamic segment.
35
+ #
36
+ #
37
+ # Example. Given the routes:
38
+ # 0 /posts/
39
+ # 1 /posts/:id
40
+ # 2 /posts/:id/comments
41
+ # 3 /posts/blah
42
+ # 4 /users/
43
+ # 5 /users/:id
44
+ # 6 /users/:id/profile
45
+ #
46
+ # request_uri = /users/123
47
+ #
48
+ # There will be only 4 iterations:
49
+ # 1) segm test for /posts prefix, skip all /posts/* routes
50
+ # 2) segm test for /users/
51
+ # 3) segm test for /users/:id
52
+ # (jump to list index = 5)
53
+ # 4) full test for /users/:id => here we are!
54
+
55
+ class RouteSet
56
+ def recognize_path(path, environment={})
57
+ result = recognize_optimized(path, environment) and return result
58
+
59
+ # Route was not recognized. Try to find out why (maybe wrong verb).
60
+ allows = HTTP_METHODS.select { |verb| routes.find { |r| r.recognize(path, :method => verb) } }
61
+
62
+ if environment[:method] && !HTTP_METHODS.include?(environment[:method])
63
+ raise NotImplemented.new(*allows)
64
+ elsif !allows.empty?
65
+ raise MethodNotAllowed.new(*allows)
66
+ else
67
+ raise RoutingError, "No route matches #{path.inspect} with #{environment.inspect}"
68
+ end
69
+ end
70
+
71
+ def clear_recognize_optimized!
72
+ instance_eval %{
73
+ def recognize_optimized(path, env)
74
+ write_recognize_optimized!
75
+ recognize_optimized(path, env)
76
+ end
77
+ }, __FILE__, __LINE__
78
+ end
79
+
80
+ def write_recognize_optimized!
81
+ tree = segment_tree(routes)
82
+ body = generate_code(tree)
83
+ instance_eval %{
84
+ def recognize_optimized(path, env)
85
+ segments = to_plain_segments(path)
86
+ index = #{body}
87
+ return nil unless index
88
+ while index < routes.size
89
+ result = routes[index].recognize(path, env) and return result
90
+ index += 1
91
+ end
92
+ nil
93
+ end
94
+ }, __FILE__, __LINE__
95
+ end
96
+
97
+ def segment_tree(routes)
98
+ tree = [0]
99
+
100
+ i = -1
101
+ routes.each do |route|
102
+ i += 1
103
+ # not fast, but runs only once
104
+ segments = to_plain_segments(route.segments.inject("") { |str,s| str << s.to_s })
105
+
106
+ node = tree
107
+ segments.each do |seg|
108
+ seg = :dynamic if seg && seg[0] == ?:
109
+ node << [seg, [i]] if node.empty? || node[node.size - 1][0] != seg
110
+ node = node[node.size - 1][1]
111
+ end
112
+ end
113
+ tree
114
+ end
115
+
116
+ def generate_code(list, padding=' ', level = 0)
117
+ # a digit
118
+ return padding + "#{list[0]}\n" if list.size == 1 && !(Array === list[0])
119
+
120
+ body = padding + "(seg = segments[#{level}]; \n"
121
+
122
+ i = 0
123
+ was_nil = false
124
+ list.each do |item|
125
+ if Array === item
126
+ i += 1
127
+ start = (i == 1)
128
+ final = (i == list.size)
129
+ tag, sub = item
130
+ if tag == :dynamic
131
+ body += padding + "#{start ? 'if' : 'elsif'} true\n"
132
+ body += generate_code(sub, padding + " ", level + 1)
133
+ break
134
+ elsif tag == nil && !was_nil
135
+ was_nil = true
136
+ body += padding + "#{start ? 'if' : 'elsif'} seg.nil?\n"
137
+ body += generate_code(sub, padding + " ", level + 1)
138
+ else
139
+ body += padding + "#{start ? 'if' : 'elsif'} seg == '#{tag}'\n"
140
+ body += generate_code(sub, padding + " ", level + 1)
141
+ end
142
+ end
143
+ end
144
+ body += padding + "else\n"
145
+ body += padding + " #{list[0]}\n"
146
+ body += padding + "end)\n"
147
+ body
148
+ end
149
+
150
+ # this must be really fast
151
+ def to_plain_segments(str)
152
+ str = str.dup
153
+ str.sub!(/^\/+/,'')
154
+ str.sub!(/\/+$/,'')
155
+ segments = str.split(/\.[^\/]+\/+|\/+|\.[^\/]+\Z/) # cut off ".format" also
156
+ segments << nil
157
+ segments
158
+ end
159
+
160
+ end
161
+ end
162
+ end