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,240 @@
1
+ module ActionController
2
+ module Routing
3
+ class Route #:nodoc:
4
+ attr_accessor :segments, :requirements, :conditions, :optimise
5
+
6
+ def initialize
7
+ @segments = []
8
+ @requirements = {}
9
+ @conditions = {}
10
+ @optimise = true
11
+ end
12
+
13
+ # Indicates whether the routes should be optimised with the string interpolation
14
+ # version of the named routes methods.
15
+ def optimise?
16
+ @optimise && ActionController::Base::optimise_named_routes
17
+ end
18
+
19
+ def segment_keys
20
+ segments.collect do |segment|
21
+ segment.key if segment.respond_to? :key
22
+ end.compact
23
+ end
24
+
25
+ # Write and compile a +generate+ method for this Route.
26
+ def write_generation
27
+ # Build the main body of the generation
28
+ body = "expired = false\n#{generation_extraction}\n#{generation_structure}"
29
+
30
+ # If we have conditions that must be tested first, nest the body inside an if
31
+ body = "if #{generation_requirements}\n#{body}\nend" if generation_requirements
32
+ args = "options, hash, expire_on = {}"
33
+
34
+ # Nest the body inside of a def block, and then compile it.
35
+ raw_method = method_decl = "def generate_raw(#{args})\npath = begin\n#{body}\nend\n[path, hash]\nend"
36
+ instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
37
+
38
+ # expire_on.keys == recall.keys; in other words, the keys in the expire_on hash
39
+ # are the same as the keys that were recalled from the previous request. Thus,
40
+ # we can use the expire_on.keys to determine which keys ought to be used to build
41
+ # the query string. (Never use keys from the recalled request when building the
42
+ # query string.)
43
+
44
+ method_decl = "def generate(#{args})\npath, hash = generate_raw(options, hash, expire_on)\nappend_query_string(path, hash, extra_keys(options))\nend"
45
+ instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
46
+
47
+ method_decl = "def generate_extras(#{args})\npath, hash = generate_raw(options, hash, expire_on)\n[path, extra_keys(options)]\nend"
48
+ instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
49
+ raw_method
50
+ end
51
+
52
+ # Build several lines of code that extract values from the options hash. If any
53
+ # of the values are missing or rejected then a return will be executed.
54
+ def generation_extraction
55
+ segments.collect do |segment|
56
+ segment.extraction_code
57
+ end.compact * "\n"
58
+ end
59
+
60
+ # Produce a condition expression that will check the requirements of this route
61
+ # upon generation.
62
+ def generation_requirements
63
+ requirement_conditions = requirements.collect do |key, req|
64
+ if req.is_a? Regexp
65
+ value_regexp = Regexp.new "\\A#{req.to_s}\\Z"
66
+ "hash[:#{key}] && #{value_regexp.inspect} =~ options[:#{key}]"
67
+ else
68
+ "hash[:#{key}] == #{req.inspect}"
69
+ end
70
+ end
71
+ requirement_conditions * ' && ' unless requirement_conditions.empty?
72
+ end
73
+
74
+ def generation_structure
75
+ segments.last.string_structure segments[0..-2]
76
+ end
77
+
78
+ # Write and compile a +recognize+ method for this Route.
79
+ def write_recognition
80
+ # Create an if structure to extract the params from a match if it occurs.
81
+ body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams"
82
+ body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend"
83
+
84
+ # Build the method declaration and compile it
85
+ method_decl = "def recognize(path, env={})\n#{body}\nend"
86
+ instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
87
+ method_decl
88
+ end
89
+
90
+ # Plugins may override this method to add other conditions, like checks on
91
+ # host, subdomain, and so forth. Note that changes here only affect route
92
+ # recognition, not generation.
93
+ def recognition_conditions
94
+ result = ["(match = #{Regexp.new(recognition_pattern).inspect}.match(path))"]
95
+ result << "conditions[:method] === env[:method]" if conditions[:method]
96
+ result
97
+ end
98
+
99
+ # Build the regular expression pattern that will match this route.
100
+ def recognition_pattern(wrap = true)
101
+ pattern = ''
102
+ segments.reverse_each do |segment|
103
+ pattern = segment.build_pattern pattern
104
+ end
105
+ wrap ? ("\\A" + pattern + "\\Z") : pattern
106
+ end
107
+
108
+ # Write the code to extract the parameters from a matched route.
109
+ def recognition_extraction
110
+ next_capture = 1
111
+ extraction = segments.collect do |segment|
112
+ x = segment.match_extraction(next_capture)
113
+ next_capture += Regexp.new(segment.regexp_chunk).number_of_captures
114
+ x
115
+ end
116
+ extraction.compact
117
+ end
118
+
119
+ # Write the real generation implementation and then resend the message.
120
+ def generate(options, hash, expire_on = {})
121
+ write_generation
122
+ generate options, hash, expire_on
123
+ end
124
+
125
+ def generate_extras(options, hash, expire_on = {})
126
+ write_generation
127
+ generate_extras options, hash, expire_on
128
+ end
129
+
130
+ # Generate the query string with any extra keys in the hash and append
131
+ # it to the given path, returning the new path.
132
+ def append_query_string(path, hash, query_keys=nil)
133
+ return nil unless path
134
+ query_keys ||= extra_keys(hash)
135
+ "#{path}#{build_query_string(hash, query_keys)}"
136
+ end
137
+
138
+ # Determine which keys in the given hash are "extra". Extra keys are
139
+ # those that were not used to generate a particular route. The extra
140
+ # keys also do not include those recalled from the prior request, nor
141
+ # do they include any keys that were implied in the route (like a
142
+ # <tt>:controller</tt> that is required, but not explicitly used in the
143
+ # text of the route.)
144
+ def extra_keys(hash, recall={})
145
+ (hash || {}).keys.map { |k| k.to_sym } - (recall || {}).keys - significant_keys
146
+ end
147
+
148
+ # Build a query string from the keys of the given hash. If +only_keys+
149
+ # is given (as an array), only the keys indicated will be used to build
150
+ # the query string. The query string will correctly build array parameter
151
+ # values.
152
+ def build_query_string(hash, only_keys = nil)
153
+ elements = []
154
+
155
+ (only_keys || hash.keys).each do |key|
156
+ if value = hash[key]
157
+ elements << value.to_query(key)
158
+ end
159
+ end
160
+
161
+ elements.empty? ? '' : "?#{elements.sort * '&'}"
162
+ end
163
+
164
+ # Write the real recognition implementation and then resend the message.
165
+ def recognize(path, environment={})
166
+ write_recognition
167
+ recognize path, environment
168
+ end
169
+
170
+ # A route's parameter shell contains parameter values that are not in the
171
+ # route's path, but should be placed in the recognized hash.
172
+ #
173
+ # For example, +{:controller => 'pages', :action => 'show'} is the shell for the route:
174
+ #
175
+ # map.connect '/page/:id', :controller => 'pages', :action => 'show', :id => /\d+/
176
+ #
177
+ def parameter_shell
178
+ @parameter_shell ||= returning({}) do |shell|
179
+ requirements.each do |key, requirement|
180
+ shell[key] = requirement unless requirement.is_a? Regexp
181
+ end
182
+ end
183
+ end
184
+
185
+ # Return an array containing all the keys that are used in this route. This
186
+ # includes keys that appear inside the path, and keys that have requirements
187
+ # placed upon them.
188
+ def significant_keys
189
+ @significant_keys ||= returning [] do |sk|
190
+ segments.each { |segment| sk << segment.key if segment.respond_to? :key }
191
+ sk.concat requirements.keys
192
+ sk.uniq!
193
+ end
194
+ end
195
+
196
+ # Return a hash of key/value pairs representing the keys in the route that
197
+ # have defaults, or which are specified by non-regexp requirements.
198
+ def defaults
199
+ @defaults ||= returning({}) do |hash|
200
+ segments.each do |segment|
201
+ next unless segment.respond_to? :default
202
+ hash[segment.key] = segment.default unless segment.default.nil?
203
+ end
204
+ requirements.each do |key,req|
205
+ next if Regexp === req || req.nil?
206
+ hash[key] = req
207
+ end
208
+ end
209
+ end
210
+
211
+ def matches_controller_and_action?(controller, action)
212
+ unless defined? @matching_prepared
213
+ @controller_requirement = requirement_for(:controller)
214
+ @action_requirement = requirement_for(:action)
215
+ @matching_prepared = true
216
+ end
217
+
218
+ (@controller_requirement.nil? || @controller_requirement === controller) &&
219
+ (@action_requirement.nil? || @action_requirement === action)
220
+ end
221
+
222
+ def to_s
223
+ @to_s ||= begin
224
+ segs = segments.inject("") { |str,s| str << s.to_s }
225
+ "%-6s %-40s %s" % [(conditions[:method] || :any).to_s.upcase, segs, requirements.inspect]
226
+ end
227
+ end
228
+
229
+ protected
230
+ def requirement_for(key)
231
+ return requirements[key] if requirements.key? key
232
+ segments.each do |segment|
233
+ return segment.regexp if segment.respond_to?(:key) && segment.key == key
234
+ end
235
+ nil
236
+ end
237
+
238
+ end
239
+ end
240
+ end
@@ -0,0 +1,436 @@
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
+ clear_recognize_optimized!
198
+ end
199
+
200
+ # Subclasses and plugins may override this method to specify a different
201
+ # RouteBuilder instance, so that other route DSL's can be created.
202
+ def builder
203
+ @builder ||= RouteBuilder.new
204
+ end
205
+
206
+ def draw
207
+ clear!
208
+ yield Mapper.new(self)
209
+ install_helpers
210
+ end
211
+
212
+ def clear!
213
+ routes.clear
214
+ named_routes.clear
215
+ @combined_regexp = nil
216
+ @routes_by_controller = nil
217
+ # This will force routing/recognition_optimisation.rb
218
+ # to refresh optimisations.
219
+ clear_recognize_optimized!
220
+ end
221
+
222
+ def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false)
223
+ Array(destinations).each { |d| d.module_eval { include Helpers } }
224
+ named_routes.install(destinations, regenerate_code)
225
+ end
226
+
227
+ def empty?
228
+ routes.empty?
229
+ end
230
+
231
+ def load!
232
+ Routing.use_controllers! nil # Clear the controller cache so we may discover new ones
233
+ clear!
234
+ load_routes!
235
+ install_helpers
236
+ end
237
+
238
+ # reload! will always force a reload whereas load checks the timestamp first
239
+ alias reload! load!
240
+
241
+ def reload
242
+ if @routes_last_modified && configuration_file
243
+ mtime = File.stat(configuration_file).mtime
244
+ # if it hasn't been changed, then just return
245
+ return if mtime == @routes_last_modified
246
+ # if it has changed then record the new time and fall to the load! below
247
+ @routes_last_modified = mtime
248
+ end
249
+ load!
250
+ end
251
+
252
+ def load_routes!
253
+ if configuration_file
254
+ load configuration_file
255
+ @routes_last_modified = File.stat(configuration_file).mtime
256
+ else
257
+ add_route ":controller/:action/:id"
258
+ end
259
+ end
260
+
261
+ def add_route(path, options = {})
262
+ route = builder.build(path, options)
263
+ routes << route
264
+ route
265
+ end
266
+
267
+ def add_named_route(name, path, options = {})
268
+ # TODO - is options EVER used?
269
+ name = options[:name_prefix] + name.to_s if options[:name_prefix]
270
+ named_routes[name.to_sym] = add_route(path, options)
271
+ end
272
+
273
+ def options_as_params(options)
274
+ # If an explicit :controller was given, always make :action explicit
275
+ # too, so that action expiry works as expected for things like
276
+ #
277
+ # generate({:controller => 'content'}, {:controller => 'content', :action => 'show'})
278
+ #
279
+ # (the above is from the unit tests). In the above case, because the
280
+ # controller was explicitly given, but no action, the action is implied to
281
+ # be "index", not the recalled action of "show".
282
+ #
283
+ # great fun, eh?
284
+
285
+ options_as_params = options.clone
286
+ options_as_params[:action] ||= 'index' if options[:controller]
287
+ options_as_params[:action] = options_as_params[:action].to_s if options_as_params[:action]
288
+ options_as_params
289
+ end
290
+
291
+ def build_expiry(options, recall)
292
+ recall.inject({}) do |expiry, (key, recalled_value)|
293
+ expiry[key] = (options.key?(key) && options[key].to_param != recalled_value.to_param)
294
+ expiry
295
+ end
296
+ end
297
+
298
+ # Generate the path indicated by the arguments, and return an array of
299
+ # the keys that were not used to generate it.
300
+ def extra_keys(options, recall={})
301
+ generate_extras(options, recall).last
302
+ end
303
+
304
+ def generate_extras(options, recall={})
305
+ generate(options, recall, :generate_extras)
306
+ end
307
+
308
+ def generate(options, recall = {}, method=:generate)
309
+ named_route_name = options.delete(:use_route)
310
+ generate_all = options.delete(:generate_all)
311
+ if named_route_name
312
+ named_route = named_routes[named_route_name]
313
+ options = named_route.parameter_shell.merge(options)
314
+ end
315
+
316
+ options = options_as_params(options)
317
+ expire_on = build_expiry(options, recall)
318
+
319
+ if options[:controller]
320
+ options[:controller] = options[:controller].to_s
321
+ end
322
+ # if the controller has changed, make sure it changes relative to the
323
+ # current controller module, if any. In other words, if we're currently
324
+ # on admin/get, and the new controller is 'set', the new controller
325
+ # should really be admin/set.
326
+ if !named_route && expire_on[:controller] && options[:controller] && options[:controller][0] != ?/
327
+ old_parts = recall[:controller].split('/')
328
+ new_parts = options[:controller].split('/')
329
+ parts = old_parts[0..-(new_parts.length + 1)] + new_parts
330
+ options[:controller] = parts.join('/')
331
+ end
332
+
333
+ # drop the leading '/' on the controller name
334
+ options[:controller] = options[:controller][1..-1] if options[:controller] && options[:controller][0] == ?/
335
+ merged = recall.merge(options)
336
+
337
+ if named_route
338
+ path = named_route.generate(options, merged, expire_on)
339
+ if path.nil?
340
+ raise_named_route_error(options, named_route, named_route_name)
341
+ else
342
+ return path
343
+ end
344
+ else
345
+ merged[:action] ||= 'index'
346
+ options[:action] ||= 'index'
347
+
348
+ controller = merged[:controller]
349
+ action = merged[:action]
350
+
351
+ raise RoutingError, "Need controller and action!" unless controller && action
352
+
353
+ if generate_all
354
+ # Used by caching to expire all paths for a resource
355
+ return routes.collect do |route|
356
+ route.send!(method, options, merged, expire_on)
357
+ end.compact
358
+ end
359
+
360
+ # don't use the recalled keys when determining which routes to check
361
+ routes = routes_by_controller[controller][action][options.keys.sort_by { |x| x.object_id }]
362
+
363
+ routes.each do |route|
364
+ results = route.send!(method, options, merged, expire_on)
365
+ return results if results && (!results.is_a?(Array) || results.first)
366
+ end
367
+ end
368
+
369
+ raise RoutingError, "No route matches #{options.inspect}"
370
+ end
371
+
372
+ # try to give a helpful error message when named route generation fails
373
+ def raise_named_route_error(options, named_route, named_route_name)
374
+ diff = named_route.requirements.diff(options)
375
+ unless diff.empty?
376
+ raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect}, expected: #{named_route.requirements.inspect}, diff: #{named_route.requirements.diff(options).inspect}"
377
+ else
378
+ required_segments = named_route.segments.select {|seg| (!seg.optional?) && (!seg.is_a?(DividerSegment)) }
379
+ required_keys_or_values = required_segments.map { |seg| seg.key rescue seg.value } # we want either the key or the value from the segment
380
+ 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?"
381
+ end
382
+ end
383
+
384
+ def recognize(request)
385
+ params = recognize_path(request.path, extract_request_environment(request))
386
+ request.path_parameters = params.with_indifferent_access
387
+ "#{params[:controller].camelize}Controller".constantize
388
+ end
389
+
390
+ def recognize_path(path, environment={})
391
+ raise "Not optimized! Check that routing/recognition_optimisation overrides RouteSet#recognize_path."
392
+ end
393
+
394
+ def routes_by_controller
395
+ @routes_by_controller ||= Hash.new do |controller_hash, controller|
396
+ controller_hash[controller] = Hash.new do |action_hash, action|
397
+ action_hash[action] = Hash.new do |key_hash, keys|
398
+ key_hash[keys] = routes_for_controller_and_action_and_keys(controller, action, keys)
399
+ end
400
+ end
401
+ end
402
+ end
403
+
404
+ def routes_for(options, merged, expire_on)
405
+ raise "Need controller and action!" unless controller && action
406
+ controller = merged[:controller]
407
+ merged = options if expire_on[:controller]
408
+ action = merged[:action] || 'index'
409
+
410
+ routes_by_controller[controller][action][merged.keys]
411
+ end
412
+
413
+ def routes_for_controller_and_action(controller, action)
414
+ selected = routes.select do |route|
415
+ route.matches_controller_and_action? controller, action
416
+ end
417
+ (selected.length == routes.length) ? routes : selected
418
+ end
419
+
420
+ def routes_for_controller_and_action_and_keys(controller, action, keys)
421
+ selected = routes.select do |route|
422
+ route.matches_controller_and_action? controller, action
423
+ end
424
+ selected.sort_by do |route|
425
+ (keys - route.significant_keys).length
426
+ end
427
+ end
428
+
429
+ # Subclasses and plugins may override this method to extract further attributes
430
+ # from the request, for use by route conditions and such.
431
+ def extract_request_environment(request)
432
+ { :method => request.method }
433
+ end
434
+ end
435
+ end
436
+ end