actionpack 1.13.6 → 2.0.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.
- data/CHANGELOG +1400 -20
- data/MIT-LICENSE +1 -1
- data/README +5 -5
- data/RUNNING_UNIT_TESTS +4 -5
- data/Rakefile +5 -6
- data/install.rb +2 -2
- data/lib/action_controller.rb +11 -15
- data/lib/action_controller/assertions.rb +12 -25
- data/lib/action_controller/assertions/dom_assertions.rb +18 -4
- data/lib/action_controller/assertions/model_assertions.rb +8 -1
- data/lib/action_controller/assertions/response_assertions.rb +35 -12
- data/lib/action_controller/assertions/routing_assertions.rb +56 -12
- data/lib/action_controller/assertions/selector_assertions.rb +105 -38
- data/lib/action_controller/assertions/tag_assertions.rb +28 -15
- data/lib/action_controller/base.rb +318 -250
- data/lib/action_controller/benchmarking.rb +33 -29
- data/lib/action_controller/caching.rb +130 -64
- data/lib/action_controller/cgi_ext.rb +16 -0
- data/lib/action_controller/cgi_ext/{cookie_performance_fix.rb → cookie.rb} +25 -40
- data/lib/action_controller/cgi_ext/query_extension.rb +22 -0
- data/lib/action_controller/cgi_ext/session.rb +73 -0
- data/lib/action_controller/cgi_ext/stdinput.rb +23 -0
- data/lib/action_controller/cgi_process.rb +34 -57
- data/lib/action_controller/components.rb +19 -36
- data/lib/action_controller/cookies.rb +10 -9
- data/lib/action_controller/dispatcher.rb +195 -0
- data/lib/action_controller/filters.rb +35 -34
- data/lib/action_controller/flash.rb +30 -35
- data/lib/action_controller/helpers.rb +121 -47
- data/lib/action_controller/http_authentication.rb +126 -0
- data/lib/action_controller/integration.rb +105 -101
- data/lib/action_controller/layout.rb +59 -47
- data/lib/action_controller/mime_responds.rb +57 -68
- data/lib/action_controller/mime_type.rb +43 -80
- data/lib/action_controller/mime_types.rb +20 -0
- data/lib/action_controller/polymorphic_routes.rb +88 -0
- data/lib/action_controller/record_identifier.rb +91 -0
- data/lib/action_controller/request.rb +553 -88
- data/lib/action_controller/request_forgery_protection.rb +126 -0
- data/lib/action_controller/request_profiler.rb +138 -0
- data/lib/action_controller/rescue.rb +185 -69
- data/lib/action_controller/resources.rb +211 -172
- data/lib/action_controller/response.rb +49 -8
- data/lib/action_controller/routing.rb +359 -236
- data/lib/action_controller/routing_optimisation.rb +119 -0
- data/lib/action_controller/session/active_record_store.rb +3 -2
- data/lib/action_controller/session/cookie_store.rb +161 -0
- data/lib/action_controller/session/mem_cache_store.rb +9 -16
- data/lib/action_controller/session_management.rb +17 -8
- data/lib/action_controller/streaming.rb +6 -3
- data/lib/action_controller/templates/rescues/_request_and_response.erb +24 -0
- data/lib/action_controller/templates/rescues/{_trace.rhtml → _trace.erb} +0 -0
- data/lib/action_controller/templates/rescues/{diagnostics.rhtml → diagnostics.erb} +2 -2
- data/lib/action_controller/templates/rescues/{layout.rhtml → layout.erb} +0 -0
- data/lib/action_controller/templates/rescues/{missing_template.rhtml → missing_template.erb} +0 -0
- data/lib/action_controller/templates/rescues/{routing_error.rhtml → routing_error.erb} +0 -0
- data/lib/action_controller/templates/rescues/{template_error.rhtml → template_error.erb} +2 -2
- data/lib/action_controller/templates/rescues/{unknown_action.rhtml → unknown_action.erb} +0 -0
- data/lib/action_controller/test_case.rb +53 -0
- data/lib/action_controller/test_process.rb +59 -46
- data/lib/action_controller/url_rewriter.rb +48 -24
- data/lib/action_controller/vendor/html-scanner/html/document.rb +7 -4
- data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +173 -0
- data/lib/action_controller/vendor/html-scanner/html/selector.rb +11 -6
- data/lib/action_controller/verification.rb +27 -21
- data/lib/action_pack.rb +1 -1
- data/lib/action_pack/version.rb +4 -4
- data/lib/action_view.rb +2 -3
- data/lib/action_view/base.rb +218 -63
- data/lib/action_view/compiled_templates.rb +1 -2
- data/lib/action_view/helpers/active_record_helper.rb +35 -17
- data/lib/action_view/helpers/asset_tag_helper.rb +395 -87
- data/lib/action_view/helpers/atom_feed_helper.rb +111 -0
- data/lib/action_view/helpers/benchmark_helper.rb +12 -5
- data/lib/action_view/helpers/cache_helper.rb +29 -0
- data/lib/action_view/helpers/capture_helper.rb +97 -63
- data/lib/action_view/helpers/date_helper.rb +295 -35
- data/lib/action_view/helpers/debug_helper.rb +6 -2
- data/lib/action_view/helpers/form_helper.rb +354 -111
- data/lib/action_view/helpers/form_options_helper.rb +171 -109
- data/lib/action_view/helpers/form_tag_helper.rb +332 -76
- data/lib/action_view/helpers/javascript_helper.rb +35 -11
- data/lib/action_view/helpers/javascripts/controls.js +484 -354
- data/lib/action_view/helpers/javascripts/dragdrop.js +88 -58
- data/lib/action_view/helpers/javascripts/effects.js +396 -364
- data/lib/action_view/helpers/javascripts/prototype.js +2817 -1107
- data/lib/action_view/helpers/number_helper.rb +84 -60
- data/lib/action_view/helpers/prototype_helper.rb +419 -43
- data/lib/action_view/helpers/record_identification_helper.rb +20 -0
- data/lib/action_view/helpers/record_tag_helper.rb +59 -0
- data/lib/action_view/helpers/sanitize_helper.rb +223 -0
- data/lib/action_view/helpers/scriptaculous_helper.rb +63 -4
- data/lib/action_view/helpers/tag_helper.rb +69 -39
- data/lib/action_view/helpers/text_helper.rb +221 -148
- data/lib/action_view/helpers/url_helper.rb +283 -165
- data/lib/action_view/partials.rb +134 -62
- data/lib/action_view/template_error.rb +4 -12
- data/lib/actionpack.rb +1 -0
- data/test/abstract_unit.rb +21 -1
- data/test/action_view_test.rb +26 -0
- data/test/active_record_unit.rb +12 -20
- data/test/activerecord/active_record_store_test.rb +2 -2
- data/test/activerecord/render_partial_with_record_identification_test.rb +74 -0
- data/test/controller/action_pack_assertions_test.rb +21 -152
- data/test/controller/addresses_render_test.rb +2 -7
- data/test/controller/assert_select_test.rb +120 -14
- data/test/controller/base_test.rb +11 -13
- data/test/controller/caching_test.rb +125 -5
- data/test/controller/capture_test.rb +23 -16
- data/test/controller/cgi_test.rb +66 -391
- data/test/controller/components_test.rb +31 -42
- data/test/controller/content_type_test.rb +1 -1
- data/test/controller/cookie_test.rb +42 -14
- data/test/controller/deprecation/deprecated_base_methods_test.rb +1 -42
- data/test/controller/dispatcher_test.rb +123 -0
- data/test/controller/fake_models.rb +5 -0
- data/test/controller/filters_test.rb +44 -7
- data/test/controller/flash_test.rb +46 -2
- data/test/controller/fragment_store_setting_test.rb +10 -8
- data/test/controller/helper_test.rb +19 -2
- data/test/controller/html-scanner/document_test.rb +124 -0
- data/test/controller/html-scanner/node_test.rb +69 -0
- data/test/controller/html-scanner/sanitizer_test.rb +250 -0
- data/test/controller/html-scanner/tag_node_test.rb +239 -0
- data/test/controller/html-scanner/text_node_test.rb +51 -0
- data/test/controller/html-scanner/tokenizer_test.rb +125 -0
- data/test/controller/http_authentication_test.rb +54 -0
- data/test/controller/integration_test.rb +12 -26
- data/test/controller/layout_test.rb +64 -12
- data/test/controller/mime_responds_test.rb +193 -38
- data/test/controller/mime_type_test.rb +30 -8
- data/test/controller/new_render_test.rb +104 -22
- data/test/controller/polymorphic_routes_test.rb +98 -0
- data/test/controller/record_identifier_test.rb +103 -0
- data/test/controller/redirect_test.rb +120 -18
- data/test/controller/render_test.rb +195 -45
- data/test/controller/request_forgery_protection_test.rb +217 -0
- data/test/controller/request_test.rb +545 -27
- data/test/controller/rescue_test.rb +501 -0
- data/test/controller/resources_test.rb +258 -132
- data/test/controller/routing_test.rb +502 -106
- data/test/controller/selector_test.rb +5 -5
- data/test/controller/send_file_test.rb +17 -7
- data/test/controller/session/cookie_store_test.rb +246 -0
- data/test/controller/session/mem_cache_store_test.rb +182 -0
- data/test/controller/session_fixation_test.rb +8 -11
- data/test/controller/session_management_test.rb +7 -7
- data/test/controller/test_test.rb +150 -38
- data/test/controller/url_rewriter_test.rb +87 -12
- data/test/controller/verification_test.rb +11 -0
- data/test/controller/view_paths_test.rb +137 -0
- data/test/controller/webservice_test.rb +11 -75
- data/test/fixtures/addresses/{list.rhtml → list.erb} +0 -0
- data/test/fixtures/db_definitions/sqlite.sql +2 -1
- data/test/fixtures/developer.rb +2 -0
- data/test/fixtures/fun/games/{hello_world.rhtml → hello_world.erb} +0 -0
- data/test/fixtures/helpers/fun/pdf_helper.rb +1 -1
- data/test/fixtures/layout_tests/alt/hello.rhtml +1 -0
- data/test/fixtures/layout_tests/layouts/multiple_extensions.html.erb +1 -0
- data/test/fixtures/layouts/{builder.rxml → builder.builder} +0 -0
- data/test/fixtures/layouts/{standard.rhtml → standard.erb} +0 -0
- data/test/fixtures/layouts/{talk_from_action.rhtml → talk_from_action.erb} +0 -0
- data/test/fixtures/layouts/{yield.rhtml → yield.erb} +0 -0
- data/test/fixtures/multipart/binary_file +0 -0
- data/test/fixtures/multipart/bracketed_param +5 -0
- data/test/fixtures/override/test/hello_world.erb +1 -0
- data/test/fixtures/override2/layouts/test/sub.erb +1 -0
- data/test/fixtures/post_test/layouts/post.html.erb +1 -0
- data/test/fixtures/post_test/layouts/super_post.iphone.erb +1 -0
- data/test/fixtures/post_test/post/index.html.erb +1 -0
- data/test/fixtures/post_test/post/index.iphone.erb +1 -0
- data/test/fixtures/post_test/super_post/index.html.erb +1 -0
- data/test/fixtures/post_test/super_post/index.iphone.erb +1 -0
- data/test/fixtures/public/404.html +1 -0
- data/test/fixtures/public/500.html +1 -0
- data/test/fixtures/public/javascripts/application.js +0 -1
- data/test/fixtures/public/javascripts/bank.js +1 -0
- data/test/fixtures/public/javascripts/robber.js +1 -0
- data/test/fixtures/public/stylesheets/bank.css +1 -0
- data/test/fixtures/public/stylesheets/robber.css +1 -0
- data/test/fixtures/replies.yml +2 -0
- data/test/fixtures/reply.rb +2 -1
- data/test/fixtures/respond_to/{all_types_with_layout.rhtml → all_types_with_layout.html.erb} +0 -0
- data/test/fixtures/respond_to/{all_types_with_layout.rjs → all_types_with_layout.js.rjs} +0 -0
- data/test/fixtures/respond_to/custom_constant_handling_without_block.mobile.erb +1 -0
- data/test/fixtures/respond_to/iphone_with_html_response_type.html.erb +1 -0
- data/test/fixtures/respond_to/iphone_with_html_response_type.iphone.erb +1 -0
- data/test/fixtures/respond_to/layouts/missing.html.erb +1 -0
- data/test/fixtures/respond_to/layouts/standard.html.erb +1 -0
- data/test/fixtures/respond_to/layouts/standard.iphone.erb +1 -0
- data/test/fixtures/respond_to/{using_defaults.rhtml → using_defaults.html.erb} +0 -0
- data/test/fixtures/respond_to/{using_defaults.rjs → using_defaults.js.rjs} +0 -0
- data/test/fixtures/respond_to/{using_defaults.rxml → using_defaults.xml.builder} +0 -0
- data/test/fixtures/respond_to/{using_defaults_with_type_list.rhtml → using_defaults_with_type_list.html.erb} +0 -0
- data/test/fixtures/respond_to/{using_defaults_with_type_list.rjs → using_defaults_with_type_list.js.rjs} +0 -0
- data/test/fixtures/respond_to/{using_defaults_with_type_list.rxml → using_defaults_with_type_list.xml.builder} +0 -0
- data/test/fixtures/scope/test/{modgreet.rhtml → modgreet.erb} +0 -0
- data/test/fixtures/test/{_customer.rhtml → _customer.erb} +0 -0
- data/test/fixtures/test/{_customer_greeting.rhtml → _customer_greeting.erb} +0 -0
- data/test/fixtures/test/_hash_greeting.erb +1 -0
- data/test/fixtures/test/_hash_object.erb +2 -0
- data/test/fixtures/test/{_hello.rxml → _hello.builder} +0 -0
- data/test/fixtures/test/_layout_for_partial.html.erb +3 -0
- data/test/fixtures/test/_partial.erb +1 -0
- data/test/fixtures/test/_partial.html.erb +1 -0
- data/test/fixtures/test/_partial.js.erb +1 -0
- data/test/fixtures/test/_partial_for_use_in_layout.html.erb +1 -0
- data/test/fixtures/test/{_partial_only.rhtml → _partial_only.erb} +0 -0
- data/test/fixtures/test/{_person.rhtml → _person.erb} +0 -0
- data/test/fixtures/test/{action_talk_to_layout.rhtml → action_talk_to_layout.erb} +0 -0
- data/test/fixtures/test/{block_content_for.rhtml → block_content_for.erb} +0 -0
- data/test/fixtures/test/calling_partial_with_layout.html.erb +1 -0
- data/test/fixtures/test/{capturing.rhtml → capturing.erb} +0 -0
- data/test/fixtures/test/{content_for.rhtml → content_for.erb} +0 -0
- data/test/fixtures/test/content_for_concatenated.erb +3 -0
- data/test/fixtures/test/content_for_with_parameter.erb +2 -0
- data/test/fixtures/test/dot.directory/{render_file_with_ivar.rhtml → render_file_with_ivar.erb} +0 -0
- data/test/fixtures/test/{erb_content_for.rhtml → erb_content_for.erb} +0 -0
- data/test/fixtures/test/formatted_html_erb.html.erb +1 -0
- data/test/fixtures/test/formatted_xml_erb.builder +1 -0
- data/test/fixtures/test/formatted_xml_erb.html.erb +1 -0
- data/test/fixtures/test/formatted_xml_erb.xml.erb +1 -0
- data/test/fixtures/test/{greeting.rhtml → greeting.erb} +0 -0
- data/test/fixtures/test/{hello.rxml → hello.builder} +0 -0
- data/test/fixtures/test/{hello_world.rxml → hello_world.builder} +0 -0
- data/test/fixtures/test/{hello_world.rhtml → hello_world.erb} +0 -0
- data/test/fixtures/test/{hello_world_container.rxml → hello_world_container.builder} +0 -0
- data/test/fixtures/test/{hello_world_with_layout_false.rhtml → hello_world_with_layout_false.erb} +0 -0
- data/test/fixtures/test/{hello_xml_world.rxml → hello_xml_world.builder} +0 -0
- data/test/fixtures/test/list.erb +1 -0
- data/test/fixtures/test/{non_erb_block_content_for.rxml → non_erb_block_content_for.builder} +0 -0
- data/test/fixtures/test/{potential_conflicts.rhtml → potential_conflicts.erb} +0 -0
- data/test/fixtures/test/{render_file_with_ivar.rhtml → render_file_with_ivar.erb} +0 -0
- data/test/fixtures/test/{render_file_with_locals.rhtml → render_file_with_locals.erb} +0 -0
- data/test/fixtures/test/{render_to_string_test.rhtml → render_to_string_test.erb} +0 -0
- data/test/fixtures/test/{update_element_with_capture.rhtml → update_element_with_capture.erb} +0 -0
- data/test/fixtures/test/using_layout_around_block.html.erb +1 -0
- data/test/fixtures/topic.rb +1 -1
- data/test/template/active_record_helper_test.rb +67 -20
- data/test/template/asset_tag_helper_test.rb +222 -54
- data/test/template/atom_feed_helper_test.rb +101 -0
- data/test/template/benchmark_helper_test.rb +2 -2
- data/test/template/compiled_templates_test.rb +76 -32
- data/test/template/date_helper_test.rb +125 -9
- data/test/template/form_helper_test.rb +326 -33
- data/test/template/form_options_helper_test.rb +822 -15
- data/test/template/form_tag_helper_test.rb +96 -30
- data/test/template/javascript_helper_test.rb +61 -13
- data/test/template/number_helper_test.rb +12 -11
- data/test/template/prototype_helper_test.rb +185 -24
- data/test/template/sanitize_helper_test.rb +49 -0
- data/test/template/scriptaculous_helper_test.rb +9 -3
- data/test/template/tag_helper_test.rb +13 -2
- data/test/template/text_helper_test.rb +38 -52
- data/test/template/url_helper_test.rb +216 -46
- metadata +144 -116
- data/examples/.htaccess +0 -24
- data/examples/address_book/index.rhtml +0 -33
- data/examples/address_book/layout.rhtml +0 -8
- data/examples/address_book_controller.cgi +0 -9
- data/examples/address_book_controller.fcgi +0 -6
- data/examples/address_book_controller.rb +0 -52
- data/examples/address_book_controller.rbx +0 -4
- data/examples/benchmark.rb +0 -52
- data/examples/benchmark_with_ar.fcgi +0 -89
- data/examples/blog_controller.cgi +0 -53
- data/examples/debate/index.rhtml +0 -14
- data/examples/debate/new_topic.rhtml +0 -22
- data/examples/debate/topic.rhtml +0 -32
- data/examples/debate_controller.cgi +0 -57
- data/lib/action_controller/assertions/deprecated_assertions.rb +0 -228
- data/lib/action_controller/cgi_ext/cgi_ext.rb +0 -36
- data/lib/action_controller/cgi_ext/cgi_methods.rb +0 -211
- data/lib/action_controller/cgi_ext/pstore_performance_fix.rb +0 -30
- data/lib/action_controller/cgi_ext/raw_post_data_fix.rb +0 -95
- data/lib/action_controller/cgi_ext/session_performance_fix.rb +0 -30
- data/lib/action_controller/deprecated_dependencies.rb +0 -65
- data/lib/action_controller/deprecated_redirects.rb +0 -17
- data/lib/action_controller/deprecated_request_methods.rb +0 -34
- data/lib/action_controller/macros/auto_complete.rb +0 -53
- data/lib/action_controller/macros/in_place_editing.rb +0 -33
- data/lib/action_controller/pagination.rb +0 -408
- data/lib/action_controller/scaffolding.rb +0 -189
- data/lib/action_controller/templates/rescues/_request_and_response.rhtml +0 -44
- data/lib/action_controller/templates/scaffolds/edit.rhtml +0 -7
- data/lib/action_controller/templates/scaffolds/layout.rhtml +0 -69
- data/lib/action_controller/templates/scaffolds/list.rhtml +0 -27
- data/lib/action_controller/templates/scaffolds/new.rhtml +0 -6
- data/lib/action_controller/templates/scaffolds/show.rhtml +0 -9
- data/lib/action_controller/vendor/xml_node.rb +0 -97
- data/lib/action_view/helpers/deprecated_helper.rb +0 -37
- data/lib/action_view/helpers/java_script_macros_helper.rb +0 -233
- data/lib/action_view/helpers/pagination_helper.rb +0 -86
- data/test/activerecord/active_record_assertions_test.rb +0 -92
- data/test/activerecord/pagination_test.rb +0 -165
- data/test/controller/deprecated_instance_variables_test.rb +0 -48
- data/test/controller/raw_post_test.rb +0 -68
- data/test/fixtures/deprecated_instance_variables/_cookies_ivar.rhtml +0 -1
- data/test/fixtures/deprecated_instance_variables/_cookies_method.rhtml +0 -1
- data/test/fixtures/deprecated_instance_variables/_flash_ivar.rhtml +0 -1
- data/test/fixtures/deprecated_instance_variables/_flash_method.rhtml +0 -1
- data/test/fixtures/deprecated_instance_variables/_headers_ivar.rhtml +0 -1
- data/test/fixtures/deprecated_instance_variables/_headers_method.rhtml +0 -1
- data/test/fixtures/deprecated_instance_variables/_params_ivar.rhtml +0 -1
- data/test/fixtures/deprecated_instance_variables/_params_method.rhtml +0 -1
- data/test/fixtures/deprecated_instance_variables/_request_ivar.rhtml +0 -1
- data/test/fixtures/deprecated_instance_variables/_request_method.rhtml +0 -1
- data/test/fixtures/deprecated_instance_variables/_response_ivar.rhtml +0 -1
- data/test/fixtures/deprecated_instance_variables/_response_method.rhtml +0 -1
- data/test/fixtures/deprecated_instance_variables/_session_ivar.rhtml +0 -1
- data/test/fixtures/deprecated_instance_variables/_session_method.rhtml +0 -1
- data/test/fixtures/respond_to/layouts/standard.rhtml +0 -1
- data/test/fixtures/test/_hash_object.rhtml +0 -1
- data/test/fixtures/test/list.rhtml +0 -1
- data/test/template/deprecated_helper_test.rb +0 -36
- data/test/template/deprecated_instance_variables_test.rb +0 -43
- data/test/template/java_script_macros_helper_test.rb +0 -109
@@ -1,6 +1,9 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
|
1
3
|
module ActionController
|
2
4
|
class AbstractResponse #:nodoc:
|
3
5
|
DEFAULT_HEADERS = { "Cache-Control" => "no-cache" }
|
6
|
+
attr_accessor :request
|
4
7
|
attr_accessor :body, :headers, :session, :cookies, :assigns, :template, :redirected_to, :redirected_to_method_params, :layout
|
5
8
|
|
6
9
|
def initialize
|
@@ -8,28 +11,66 @@ module ActionController
|
|
8
11
|
end
|
9
12
|
|
10
13
|
def content_type=(mime_type)
|
11
|
-
|
14
|
+
self.headers["Content-Type"] = charset ? "#{mime_type}; charset=#{charset}" : mime_type
|
12
15
|
end
|
13
16
|
|
14
17
|
def content_type
|
15
|
-
content_type = String(
|
18
|
+
content_type = String(headers["Content-Type"] || headers["type"]).split(";")[0]
|
16
19
|
content_type.blank? ? nil : content_type
|
17
20
|
end
|
18
21
|
|
19
22
|
def charset=(encoding)
|
20
|
-
|
23
|
+
self.headers["Content-Type"] = "#{content_type || Mime::HTML}; charset=#{encoding}"
|
21
24
|
end
|
22
25
|
|
23
26
|
def charset
|
24
|
-
charset = String(
|
27
|
+
charset = String(headers["Content-Type"] || headers["type"]).split(";")[1]
|
25
28
|
charset.blank? ? nil : charset.strip.split("=")[1]
|
26
29
|
end
|
27
30
|
|
28
|
-
def redirect(to_url,
|
29
|
-
|
30
|
-
|
31
|
+
def redirect(to_url, response_status)
|
32
|
+
self.headers["Status"] = response_status
|
33
|
+
self.headers["Location"] = to_url
|
34
|
+
|
35
|
+
self.body = "<html><body>You are being <a href=\"#{to_url}\">redirected</a>.</body></html>"
|
36
|
+
end
|
31
37
|
|
32
|
-
|
38
|
+
def prepare!
|
39
|
+
handle_conditional_get!
|
40
|
+
convert_content_type!
|
41
|
+
set_content_length!
|
33
42
|
end
|
43
|
+
|
44
|
+
|
45
|
+
private
|
46
|
+
def handle_conditional_get!
|
47
|
+
if body.is_a?(String) && (headers['Status'] ? headers['Status'][0..2] == '200' : true) && !body.empty?
|
48
|
+
self.headers['ETag'] ||= %("#{Digest::MD5.hexdigest(body)}")
|
49
|
+
self.headers['Cache-Control'] = 'private, max-age=0, must-revalidate' if headers['Cache-Control'] == DEFAULT_HEADERS['Cache-Control']
|
50
|
+
|
51
|
+
if request.headers['HTTP_IF_NONE_MATCH'] == headers['ETag']
|
52
|
+
self.headers['Status'] = '304 Not Modified'
|
53
|
+
self.body = ''
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def convert_content_type!
|
59
|
+
if content_type = headers.delete("Content-Type")
|
60
|
+
self.headers["type"] = content_type
|
61
|
+
end
|
62
|
+
if content_type = headers.delete("Content-type")
|
63
|
+
self.headers["type"] = content_type
|
64
|
+
end
|
65
|
+
if content_type = headers.delete("content-type")
|
66
|
+
self.headers["type"] = content_type
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Don't set the Content-Length for block-based bodies as that would mean reading it all into memory. Not nice
|
71
|
+
# for, say, a 2GB streaming file.
|
72
|
+
def set_content_length!
|
73
|
+
self.headers["Content-Length"] = body.size unless body.respond_to?(:call)
|
74
|
+
end
|
34
75
|
end
|
35
76
|
end
|
@@ -1,4 +1,7 @@
|
|
1
1
|
require 'cgi'
|
2
|
+
require 'uri'
|
3
|
+
require 'action_controller/polymorphic_routes'
|
4
|
+
require 'action_controller/routing_optimisation'
|
2
5
|
|
3
6
|
class Object
|
4
7
|
def to_param
|
@@ -28,7 +31,7 @@ class Regexp #:nodoc:
|
|
28
31
|
def number_of_captures
|
29
32
|
Regexp.new("|#{source}").match('').captures.length
|
30
33
|
end
|
31
|
-
|
34
|
+
|
32
35
|
class << self
|
33
36
|
def optionalize(pattern)
|
34
37
|
case unoptionalize(pattern)
|
@@ -36,7 +39,7 @@ class Regexp #:nodoc:
|
|
36
39
|
else "(?:#{pattern})?"
|
37
40
|
end
|
38
41
|
end
|
39
|
-
|
42
|
+
|
40
43
|
def unoptionalize(pattern)
|
41
44
|
[/\A\(\?:(.*)\)\?\Z/, /\A(.|\(.*\))\?\Z/].each do |regexp|
|
42
45
|
return $1 if regexp =~ pattern
|
@@ -47,30 +50,30 @@ class Regexp #:nodoc:
|
|
47
50
|
end
|
48
51
|
|
49
52
|
module ActionController
|
50
|
-
# == Routing
|
53
|
+
# == Routing
|
51
54
|
#
|
52
55
|
# The routing module provides URL rewriting in native Ruby. It's a way to
|
53
56
|
# redirect incoming requests to controllers and actions. This replaces
|
54
|
-
# mod_rewrite rules. Best of all Rails' Routing works with any web server.
|
57
|
+
# mod_rewrite rules. Best of all, Rails' Routing works with any web server.
|
55
58
|
# Routes are defined in routes.rb in your RAILS_ROOT/config directory.
|
56
59
|
#
|
57
|
-
# Consider the following route, installed by Rails when you generate your
|
60
|
+
# Consider the following route, installed by Rails when you generate your
|
58
61
|
# application:
|
59
62
|
#
|
60
63
|
# map.connect ':controller/:action/:id'
|
61
64
|
#
|
62
|
-
# This route states that it expects requests to consist of a
|
63
|
-
# :controller followed by an :action that in
|
65
|
+
# This route states that it expects requests to consist of a
|
66
|
+
# :controller followed by an :action that in turn is fed some :id.
|
64
67
|
#
|
65
|
-
# Suppose you get an incoming request for <tt>/blog/edit/22</tt>, you'll end up
|
68
|
+
# Suppose you get an incoming request for <tt>/blog/edit/22</tt>, you'll end up
|
66
69
|
# with:
|
67
70
|
#
|
68
71
|
# params = { :controller => 'blog',
|
69
|
-
# :action => 'edit'
|
72
|
+
# :action => 'edit',
|
70
73
|
# :id => '22'
|
71
74
|
# }
|
72
75
|
#
|
73
|
-
# Think of creating routes as drawing a map for your requests. The map tells
|
76
|
+
# Think of creating routes as drawing a map for your requests. The map tells
|
74
77
|
# them where to go based on some predefined pattern:
|
75
78
|
#
|
76
79
|
# ActionController::Routing::Routes.draw do |map|
|
@@ -83,45 +86,45 @@ module ActionController
|
|
83
86
|
#
|
84
87
|
# :controller maps to your controller name
|
85
88
|
# :action maps to an action with your controllers
|
86
|
-
#
|
89
|
+
#
|
87
90
|
# Other names simply map to a parameter as in the case of +:id+.
|
88
|
-
#
|
91
|
+
#
|
89
92
|
# == Route priority
|
90
93
|
#
|
91
|
-
# Not all routes are created equally. Routes have priority defined by the
|
94
|
+
# Not all routes are created equally. Routes have priority defined by the
|
92
95
|
# order of appearance of the routes in the routes.rb file. The priority goes
|
93
96
|
# from top to bottom. The last route in that file is at the lowest priority
|
94
|
-
# will be applied last. If no route matches, 404 is returned.
|
97
|
+
# and will be applied last. If no route matches, 404 is returned.
|
95
98
|
#
|
96
|
-
# Within blocks, the empty pattern
|
99
|
+
# Within blocks, the empty pattern is at the highest priority.
|
97
100
|
# In practice this works out nicely:
|
98
101
|
#
|
99
|
-
# ActionController::Routing::Routes.draw do |map|
|
102
|
+
# ActionController::Routing::Routes.draw do |map|
|
100
103
|
# map.with_options :controller => 'blog' do |blog|
|
101
|
-
# blog.show
|
104
|
+
# blog.show '', :action => 'list'
|
102
105
|
# end
|
103
|
-
# map.connect ':controller/:action/:view
|
106
|
+
# map.connect ':controller/:action/:view'
|
104
107
|
# end
|
105
108
|
#
|
106
|
-
# In this case, invoking blog controller (with an URL like '/blog/')
|
109
|
+
# In this case, invoking blog controller (with an URL like '/blog/')
|
107
110
|
# without parameters will activate the 'list' action by default.
|
108
111
|
#
|
109
112
|
# == Defaults routes and default parameters
|
110
113
|
#
|
111
|
-
# Setting a default route is straightforward in Rails
|
112
|
-
# Hash
|
114
|
+
# Setting a default route is straightforward in Rails - you simply append a
|
115
|
+
# Hash at the end of your mapping to set any default parameters.
|
113
116
|
#
|
114
117
|
# Example:
|
115
118
|
# ActionController::Routing:Routes.draw do |map|
|
116
119
|
# map.connect ':controller/:action/:id', :controller => 'blog'
|
117
120
|
# end
|
118
121
|
#
|
119
|
-
# This sets up
|
122
|
+
# This sets up +blog+ as the default controller if no other is specified.
|
120
123
|
# This means visiting '/' would invoke the blog controller.
|
121
124
|
#
|
122
125
|
# More formally, you can define defaults in a route with the +:defaults+ key.
|
123
|
-
#
|
124
|
-
# map.connect ':controller/:id
|
126
|
+
#
|
127
|
+
# map.connect ':controller/:action/:id', :action => 'show', :defaults => { :page => 'Dashboard' }
|
125
128
|
#
|
126
129
|
# == Named routes
|
127
130
|
#
|
@@ -140,7 +143,7 @@ module ActionController
|
|
140
143
|
#
|
141
144
|
# redirect_to show_item_path(:id => 25)
|
142
145
|
#
|
143
|
-
# Use <tt>map.root</tt> as a shorthand to name a route for the root path ""
|
146
|
+
# Use <tt>map.root</tt> as a shorthand to name a route for the root path "".
|
144
147
|
#
|
145
148
|
# # In routes.rb
|
146
149
|
# map.root :controller => 'blogs'
|
@@ -163,73 +166,93 @@ module ActionController
|
|
163
166
|
# end
|
164
167
|
#
|
165
168
|
# # provides named routes for show, delete, and edit
|
166
|
-
# link_to @article.title, show_path(:id => @article.id)
|
169
|
+
# link_to @article.title, show_path(:id => @article.id)
|
167
170
|
#
|
168
171
|
# == Pretty URLs
|
169
172
|
#
|
170
173
|
# Routes can generate pretty URLs. For example:
|
171
174
|
#
|
172
175
|
# map.connect 'articles/:year/:month/:day',
|
173
|
-
# :controller => 'articles',
|
176
|
+
# :controller => 'articles',
|
174
177
|
# :action => 'find_by_date',
|
175
178
|
# :year => /\d{4}/,
|
176
|
-
# :month
|
177
|
-
# :day
|
178
|
-
#
|
179
|
+
# :month => /\d{1,2}/,
|
180
|
+
# :day => /\d{1,2}/
|
181
|
+
#
|
179
182
|
# # Using the route above, the url below maps to:
|
180
183
|
# # params = {:year => '2005', :month => '11', :day => '06'}
|
181
184
|
# # http://localhost:3000/articles/2005/11/06
|
182
185
|
#
|
183
186
|
# == Regular Expressions and parameters
|
184
|
-
# You can specify a
|
187
|
+
# You can specify a regular expression to define a format for a parameter.
|
185
188
|
#
|
186
189
|
# map.geocode 'geocode/:postalcode', :controller => 'geocode',
|
187
190
|
# :action => 'show', :postalcode => /\d{5}(-\d{4})?/
|
188
191
|
#
|
189
|
-
# or
|
192
|
+
# or, more formally:
|
190
193
|
#
|
191
|
-
# map.geocode 'geocode/:postalcode', :controller => 'geocode',
|
192
|
-
#
|
193
|
-
# :requirements { :postalcode => /\d{5}(-\d{4})?/ }
|
194
|
+
# map.geocode 'geocode/:postalcode', :controller => 'geocode',
|
195
|
+
# :action => 'show', :requirements => { :postalcode => /\d{5}(-\d{4})?/ }
|
194
196
|
#
|
195
197
|
# == Route globbing
|
196
198
|
#
|
197
|
-
# Specifying <tt>*[string]</tt> as part of a rule like
|
199
|
+
# Specifying <tt>*[string]</tt> as part of a rule like:
|
198
200
|
#
|
199
201
|
# map.connect '*path' , :controller => 'blog' , :action => 'unrecognized?'
|
200
202
|
#
|
201
|
-
# will glob all remaining parts of the route that were not recognized earlier. This idiom
|
203
|
+
# will glob all remaining parts of the route that were not recognized earlier. This idiom
|
204
|
+
# must appear at the end of the path. The globbed values are in <tt>params[:path]</tt> in
|
205
|
+
# this case.
|
206
|
+
#
|
207
|
+
# == Route conditions
|
208
|
+
#
|
209
|
+
# With conditions you can define restrictions on routes. Currently the only valid condition is <tt>:method</tt>.
|
210
|
+
#
|
211
|
+
# * <tt>:method</tt> - Allows you to specify which method can access the route. Possible values are <tt>:post</tt>,
|
212
|
+
# <tt>:get</tt>, <tt>:put</tt>, <tt>:delete</tt> and <tt>:any</tt>. The default value is <tt>:any</tt>,
|
213
|
+
# <tt>:any</tt> means that any method can access the route.
|
214
|
+
#
|
215
|
+
# Example:
|
216
|
+
#
|
217
|
+
# map.connect 'post/:id', :controller => 'posts', :action => 'show',
|
218
|
+
# :conditions => { :method => :get }
|
219
|
+
# map.connect 'post/:id', :controller => 'posts', :action => 'create_comment',
|
220
|
+
# :conditions => { :method => :post }
|
202
221
|
#
|
222
|
+
# Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same
|
223
|
+
# URL will route to the <tt>show</tt> action.
|
224
|
+
#
|
203
225
|
# == Reloading routes
|
204
226
|
#
|
205
227
|
# You can reload routes if you feel you must:
|
206
228
|
#
|
207
|
-
#
|
229
|
+
# ActionController::Routing::Routes.reload
|
208
230
|
#
|
209
|
-
# This will clear all named routes and reload routes.rb
|
231
|
+
# This will clear all named routes and reload routes.rb if the file has been modified from
|
232
|
+
# last load. To absolutely force reloading, use +reload!+.
|
210
233
|
#
|
211
234
|
# == Testing Routes
|
212
235
|
#
|
213
236
|
# The two main methods for testing your routes:
|
214
237
|
#
|
215
238
|
# === +assert_routing+
|
216
|
-
#
|
239
|
+
#
|
217
240
|
# def test_movie_route_properly_splits
|
218
241
|
# opts = {:controller => "plugin", :action => "checkout", :id => "2"}
|
219
242
|
# assert_routing "plugin/checkout/2", opts
|
220
243
|
# end
|
221
|
-
#
|
244
|
+
#
|
222
245
|
# +assert_routing+ lets you test whether or not the route properly resolves into options.
|
223
246
|
#
|
224
247
|
# === +assert_recognizes+
|
225
248
|
#
|
226
249
|
# def test_route_has_options
|
227
250
|
# opts = {:controller => "plugin", :action => "show", :id => "12"}
|
228
|
-
# assert_recognizes opts, "/plugins/show/12"
|
251
|
+
# assert_recognizes opts, "/plugins/show/12"
|
229
252
|
# end
|
230
|
-
#
|
253
|
+
#
|
231
254
|
# Note the subtle difference between the two: +assert_routing+ tests that
|
232
|
-
#
|
255
|
+
# a URL fits options while +assert_recognizes+ tests that a URL
|
233
256
|
# breaks into parameters properly.
|
234
257
|
#
|
235
258
|
# In tests you can simply pass the URL or named route to +get+ or +post+.
|
@@ -246,12 +269,21 @@ module ActionController
|
|
246
269
|
# end
|
247
270
|
#
|
248
271
|
module Routing
|
249
|
-
SEPARATORS = %w( /
|
272
|
+
SEPARATORS = %w( / . ? )
|
273
|
+
|
274
|
+
HTTP_METHODS = [:get, :head, :post, :put, :delete]
|
275
|
+
|
276
|
+
ALLOWED_REQUIREMENTS_FOR_OPTIMISATION = [:controller, :action].to_set
|
250
277
|
|
251
278
|
# The root paths which may contain controller files
|
252
279
|
mattr_accessor :controller_paths
|
253
280
|
self.controller_paths = []
|
254
281
|
|
282
|
+
# A helper module to hold URL related helpers.
|
283
|
+
module Helpers
|
284
|
+
include PolymorphicRoutes
|
285
|
+
end
|
286
|
+
|
255
287
|
class << self
|
256
288
|
def with_controllers(names)
|
257
289
|
prior_controllers = @possible_controllers
|
@@ -282,16 +314,16 @@ module ActionController
|
|
282
314
|
def possible_controllers
|
283
315
|
unless @possible_controllers
|
284
316
|
@possible_controllers = []
|
285
|
-
|
317
|
+
|
286
318
|
paths = controller_paths.select { |path| File.directory?(path) && path != "." }
|
287
319
|
|
288
320
|
seen_paths = Hash.new {|h, k| h[k] = true; false}
|
289
321
|
normalize_paths(paths).each do |load_path|
|
290
322
|
Dir["#{load_path}/**/*_controller.rb"].collect do |path|
|
291
323
|
next if seen_paths[path.gsub(%r{^\.[/\\]}, "")]
|
292
|
-
|
324
|
+
|
293
325
|
controller_name = path[(load_path.length + 1)..-1]
|
294
|
-
|
326
|
+
|
295
327
|
controller_name.gsub!(/_controller\.rb\Z/, '')
|
296
328
|
@possible_controllers << controller_name
|
297
329
|
end
|
@@ -312,24 +344,37 @@ module ActionController
|
|
312
344
|
elsif controller[0] == ?/ then controller[1..-1]
|
313
345
|
elsif %r{^(.*)/} =~ previous then "#{$1}/#{controller}"
|
314
346
|
else controller
|
315
|
-
end
|
316
|
-
end
|
347
|
+
end
|
348
|
+
end
|
317
349
|
end
|
318
|
-
|
350
|
+
|
319
351
|
class Route #:nodoc:
|
320
|
-
attr_accessor :segments, :requirements, :conditions
|
321
|
-
|
352
|
+
attr_accessor :segments, :requirements, :conditions, :optimise
|
353
|
+
|
322
354
|
def initialize
|
323
355
|
@segments = []
|
324
356
|
@requirements = {}
|
325
357
|
@conditions = {}
|
358
|
+
@optimise = true
|
326
359
|
end
|
327
|
-
|
360
|
+
|
361
|
+
# Indicates whether the routes should be optimised with the string interpolation
|
362
|
+
# version of the named routes methods.
|
363
|
+
def optimise?
|
364
|
+
@optimise && ActionController::Base::optimise_named_routes
|
365
|
+
end
|
366
|
+
|
367
|
+
def segment_keys
|
368
|
+
segments.collect do |segment|
|
369
|
+
segment.key if segment.respond_to? :key
|
370
|
+
end.compact
|
371
|
+
end
|
372
|
+
|
328
373
|
# Write and compile a +generate+ method for this Route.
|
329
374
|
def write_generation
|
330
375
|
# Build the main body of the generation
|
331
376
|
body = "expired = false\n#{generation_extraction}\n#{generation_structure}"
|
332
|
-
|
377
|
+
|
333
378
|
# If we have conditions that must be tested first, nest the body inside an if
|
334
379
|
body = "if #{generation_requirements}\n#{body}\nend" if generation_requirements
|
335
380
|
args = "options, hash, expire_on = {}"
|
@@ -351,7 +396,7 @@ module ActionController
|
|
351
396
|
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
|
352
397
|
raw_method
|
353
398
|
end
|
354
|
-
|
399
|
+
|
355
400
|
# Build several lines of code that extract values from the options hash. If any
|
356
401
|
# of the values are missing or rejected then a return will be executed.
|
357
402
|
def generation_extraction
|
@@ -359,7 +404,7 @@ module ActionController
|
|
359
404
|
segment.extraction_code
|
360
405
|
end.compact * "\n"
|
361
406
|
end
|
362
|
-
|
407
|
+
|
363
408
|
# Produce a condition expression that will check the requirements of this route
|
364
409
|
# upon generation.
|
365
410
|
def generation_requirements
|
@@ -373,16 +418,17 @@ module ActionController
|
|
373
418
|
end
|
374
419
|
requirement_conditions * ' && ' unless requirement_conditions.empty?
|
375
420
|
end
|
421
|
+
|
376
422
|
def generation_structure
|
377
423
|
segments.last.string_structure segments[0..-2]
|
378
424
|
end
|
379
|
-
|
425
|
+
|
380
426
|
# Write and compile a +recognize+ method for this Route.
|
381
427
|
def write_recognition
|
382
428
|
# Create an if structure to extract the params from a match if it occurs.
|
383
429
|
body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams"
|
384
430
|
body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend"
|
385
|
-
|
431
|
+
|
386
432
|
# Build the method declaration and compile it
|
387
433
|
method_decl = "def recognize(path, env={})\n#{body}\nend"
|
388
434
|
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
|
@@ -406,18 +452,18 @@ module ActionController
|
|
406
452
|
end
|
407
453
|
wrap ? ("\\A" + pattern + "\\Z") : pattern
|
408
454
|
end
|
409
|
-
|
455
|
+
|
410
456
|
# Write the code to extract the parameters from a matched route.
|
411
457
|
def recognition_extraction
|
412
458
|
next_capture = 1
|
413
459
|
extraction = segments.collect do |segment|
|
414
|
-
x = segment.match_extraction
|
460
|
+
x = segment.match_extraction(next_capture)
|
415
461
|
next_capture += Regexp.new(segment.regexp_chunk).number_of_captures
|
416
462
|
x
|
417
463
|
end
|
418
464
|
extraction.compact
|
419
465
|
end
|
420
|
-
|
466
|
+
|
421
467
|
# Write the real generation implementation and then resend the message.
|
422
468
|
def generate(options, hash, expire_on = {})
|
423
469
|
write_generation
|
@@ -453,12 +499,13 @@ module ActionController
|
|
453
499
|
# values.
|
454
500
|
def build_query_string(hash, only_keys = nil)
|
455
501
|
elements = []
|
456
|
-
|
502
|
+
|
457
503
|
(only_keys || hash.keys).each do |key|
|
458
504
|
if value = hash[key]
|
459
505
|
elements << value.to_query(key)
|
460
506
|
end
|
461
507
|
end
|
508
|
+
|
462
509
|
elements.empty? ? '' : "?#{elements.sort * '&'}"
|
463
510
|
end
|
464
511
|
|
@@ -467,14 +514,14 @@ module ActionController
|
|
467
514
|
write_recognition
|
468
515
|
recognize path, environment
|
469
516
|
end
|
470
|
-
|
517
|
+
|
471
518
|
# A route's parameter shell contains parameter values that are not in the
|
472
519
|
# route's path, but should be placed in the recognized hash.
|
473
|
-
#
|
520
|
+
#
|
474
521
|
# For example, +{:controller => 'pages', :action => 'show'} is the shell for the route:
|
475
|
-
#
|
522
|
+
#
|
476
523
|
# map.connect '/page/:id', :controller => 'pages', :action => 'show', :id => /\d+/
|
477
|
-
#
|
524
|
+
#
|
478
525
|
def parameter_shell
|
479
526
|
@parameter_shell ||= returning({}) do |shell|
|
480
527
|
requirements.each do |key, requirement|
|
@@ -482,7 +529,7 @@ module ActionController
|
|
482
529
|
end
|
483
530
|
end
|
484
531
|
end
|
485
|
-
|
532
|
+
|
486
533
|
# Return an array containing all the keys that are used in this route. This
|
487
534
|
# includes keys that appear inside the path, and keys that have requirements
|
488
535
|
# placed upon them.
|
@@ -508,9 +555,9 @@ module ActionController
|
|
508
555
|
end
|
509
556
|
end
|
510
557
|
end
|
511
|
-
|
558
|
+
|
512
559
|
def matches_controller_and_action?(controller, action)
|
513
|
-
unless @matching_prepared
|
560
|
+
unless defined? @matching_prepared
|
514
561
|
@controller_requirement = requirement_for(:controller)
|
515
562
|
@action_requirement = requirement_for(:action)
|
516
563
|
@matching_prepared = true
|
@@ -526,7 +573,7 @@ module ActionController
|
|
526
573
|
"%-6s %-40s %s" % [(conditions[:method] || :any).to_s.upcase, segs, requirements.inspect]
|
527
574
|
end
|
528
575
|
end
|
529
|
-
|
576
|
+
|
530
577
|
protected
|
531
578
|
def requirement_for(key)
|
532
579
|
return requirements[key] if requirements.key? key
|
@@ -535,10 +582,13 @@ module ActionController
|
|
535
582
|
end
|
536
583
|
nil
|
537
584
|
end
|
538
|
-
|
585
|
+
|
539
586
|
end
|
540
587
|
|
541
588
|
class Segment #:nodoc:
|
589
|
+
RESERVED_PCHAR = ':@&=+$,;'
|
590
|
+
UNSAFE_PCHAR = Regexp.new("[^#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}]", false, 'N').freeze
|
591
|
+
|
542
592
|
attr_accessor :is_optional
|
543
593
|
alias_method :optional?, :is_optional
|
544
594
|
|
@@ -549,7 +599,7 @@ module ActionController
|
|
549
599
|
def extraction_code
|
550
600
|
nil
|
551
601
|
end
|
552
|
-
|
602
|
+
|
553
603
|
# Continue generating string for the prior segments.
|
554
604
|
def continue_string_structure(prior_segments)
|
555
605
|
if prior_segments.empty?
|
@@ -559,33 +609,37 @@ module ActionController
|
|
559
609
|
prior_segments.last.string_structure(new_priors)
|
560
610
|
end
|
561
611
|
end
|
562
|
-
|
612
|
+
|
613
|
+
def interpolation_chunk
|
614
|
+
URI.escape(value, UNSAFE_PCHAR)
|
615
|
+
end
|
616
|
+
|
563
617
|
# Return a string interpolation statement for this segment and those before it.
|
564
618
|
def interpolation_statement(prior_segments)
|
565
619
|
chunks = prior_segments.collect { |s| s.interpolation_chunk }
|
566
620
|
chunks << interpolation_chunk
|
567
621
|
"\"#{chunks * ''}\"#{all_optionals_available_condition(prior_segments)}"
|
568
622
|
end
|
569
|
-
|
623
|
+
|
570
624
|
def string_structure(prior_segments)
|
571
625
|
optional? ? continue_string_structure(prior_segments) : interpolation_statement(prior_segments)
|
572
626
|
end
|
573
|
-
|
627
|
+
|
574
628
|
# Return an if condition that is true if all the prior segments can be generated.
|
575
629
|
# If there are no optional segments before this one, then nil is returned.
|
576
630
|
def all_optionals_available_condition(prior_segments)
|
577
631
|
optional_locals = prior_segments.collect { |s| s.local_name if s.optional? && s.respond_to?(:local_name) }.compact
|
578
632
|
optional_locals.empty? ? nil : " if #{optional_locals * ' && '}"
|
579
633
|
end
|
580
|
-
|
634
|
+
|
581
635
|
# Recognition
|
582
|
-
|
636
|
+
|
583
637
|
def match_extraction(next_capture)
|
584
638
|
nil
|
585
639
|
end
|
586
|
-
|
640
|
+
|
587
641
|
# Warning
|
588
|
-
|
642
|
+
|
589
643
|
# Returns true if this segment is optional? because of a default. If so, then
|
590
644
|
# no warning will be emitted regarding this segment.
|
591
645
|
def optionality_implied?
|
@@ -596,21 +650,21 @@ module ActionController
|
|
596
650
|
class StaticSegment < Segment #:nodoc:
|
597
651
|
attr_accessor :value, :raw
|
598
652
|
alias_method :raw?, :raw
|
599
|
-
|
653
|
+
|
600
654
|
def initialize(value = nil)
|
601
655
|
super()
|
602
656
|
self.value = value
|
603
657
|
end
|
604
|
-
|
658
|
+
|
605
659
|
def interpolation_chunk
|
606
|
-
raw? ? value :
|
660
|
+
raw? ? value : super
|
607
661
|
end
|
608
|
-
|
662
|
+
|
609
663
|
def regexp_chunk
|
610
|
-
chunk = Regexp.escape
|
664
|
+
chunk = Regexp.escape(value)
|
611
665
|
optional? ? Regexp.optionalize(chunk) : chunk
|
612
666
|
end
|
613
|
-
|
667
|
+
|
614
668
|
def build_pattern(pattern)
|
615
669
|
escaped = Regexp.escape(value)
|
616
670
|
if optional? && ! pattern.empty?
|
@@ -621,7 +675,7 @@ module ActionController
|
|
621
675
|
escaped + pattern
|
622
676
|
end
|
623
677
|
end
|
624
|
-
|
678
|
+
|
625
679
|
def to_s
|
626
680
|
value
|
627
681
|
end
|
@@ -633,7 +687,7 @@ module ActionController
|
|
633
687
|
self.raw = true
|
634
688
|
self.is_optional = true
|
635
689
|
end
|
636
|
-
|
690
|
+
|
637
691
|
def optionality_implied?
|
638
692
|
true
|
639
693
|
end
|
@@ -641,23 +695,23 @@ module ActionController
|
|
641
695
|
|
642
696
|
class DynamicSegment < Segment #:nodoc:
|
643
697
|
attr_accessor :key, :default, :regexp
|
644
|
-
|
698
|
+
|
645
699
|
def initialize(key = nil, options = {})
|
646
700
|
super()
|
647
701
|
self.key = key
|
648
702
|
self.default = options[:default] if options.key? :default
|
649
703
|
self.is_optional = true if options[:optional] || options.key?(:default)
|
650
704
|
end
|
651
|
-
|
705
|
+
|
652
706
|
def to_s
|
653
707
|
":#{key}"
|
654
708
|
end
|
655
|
-
|
709
|
+
|
656
710
|
# The local variable name that the value of this segment will be extracted to.
|
657
711
|
def local_name
|
658
712
|
"#{key}_value"
|
659
713
|
end
|
660
|
-
|
714
|
+
|
661
715
|
def extract_value
|
662
716
|
"#{local_name} = hash[:#{key}] && hash[:#{key}].to_param #{"|| #{default.inspect}" if default}"
|
663
717
|
end
|
@@ -675,39 +729,39 @@ module ActionController
|
|
675
729
|
def expiry_statement
|
676
730
|
"expired, hash = true, options if !expired && expire_on[:#{key}]"
|
677
731
|
end
|
678
|
-
|
732
|
+
|
679
733
|
def extraction_code
|
680
734
|
s = extract_value
|
681
735
|
vc = value_check
|
682
736
|
s << "\nreturn [nil,nil] unless #{vc}" if vc
|
683
737
|
s << "\n#{expiry_statement}"
|
684
738
|
end
|
685
|
-
|
686
|
-
def interpolation_chunk
|
687
|
-
"\#{
|
739
|
+
|
740
|
+
def interpolation_chunk(value_code = "#{local_name}")
|
741
|
+
"\#{URI.escape(#{value_code}.to_s, ActionController::Routing::Segment::UNSAFE_PCHAR)}"
|
688
742
|
end
|
689
|
-
|
743
|
+
|
690
744
|
def string_structure(prior_segments)
|
691
745
|
if optional? # We have a conditional to do...
|
692
746
|
# If we should not appear in the url, just write the code for the prior
|
693
747
|
# segments. This occurs if our value is the default value, or, if we are
|
694
748
|
# optional, if we have nil as our value.
|
695
|
-
"if #{local_name} == #{default.inspect}\n" +
|
696
|
-
continue_string_structure(prior_segments) +
|
749
|
+
"if #{local_name} == #{default.inspect}\n" +
|
750
|
+
continue_string_structure(prior_segments) +
|
697
751
|
"\nelse\n" + # Otherwise, write the code up to here
|
698
752
|
"#{interpolation_statement(prior_segments)}\nend"
|
699
753
|
else
|
700
754
|
interpolation_statement(prior_segments)
|
701
755
|
end
|
702
756
|
end
|
703
|
-
|
757
|
+
|
704
758
|
def value_regexp
|
705
759
|
Regexp.new "\\A#{regexp.source}\\Z" if regexp
|
706
760
|
end
|
707
761
|
def regexp_chunk
|
708
762
|
regexp ? "(#{regexp.source})" : "([^#{Routing::SEPARATORS.join}]+)"
|
709
763
|
end
|
710
|
-
|
764
|
+
|
711
765
|
def build_pattern(pattern)
|
712
766
|
chunk = regexp_chunk
|
713
767
|
chunk = "(#{chunk})" if Regexp.new(chunk).number_of_captures == 0
|
@@ -715,16 +769,23 @@ module ActionController
|
|
715
769
|
optional? ? Regexp.optionalize(pattern) : pattern
|
716
770
|
end
|
717
771
|
def match_extraction(next_capture)
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
772
|
+
# All non code-related keys (such as :id, :slug) are URI-unescaped as
|
773
|
+
# path parameters.
|
774
|
+
default_value = default ? default.inspect : nil
|
775
|
+
%[
|
776
|
+
value = if (m = match[#{next_capture}])
|
777
|
+
URI.unescape(m)
|
778
|
+
else
|
779
|
+
#{default_value}
|
780
|
+
end
|
781
|
+
params[:#{key}] = value if value
|
782
|
+
]
|
722
783
|
end
|
723
|
-
|
784
|
+
|
724
785
|
def optionality_implied?
|
725
786
|
[:action, :id].include? key
|
726
787
|
end
|
727
|
-
|
788
|
+
|
728
789
|
end
|
729
790
|
|
730
791
|
class ControllerSegment < DynamicSegment #:nodoc:
|
@@ -733,10 +794,9 @@ module ActionController
|
|
733
794
|
"(?i-:(#{(regexp || Regexp.union(*possible_names)).source}))"
|
734
795
|
end
|
735
796
|
|
736
|
-
# Don't
|
737
|
-
|
738
|
-
|
739
|
-
"\#{#{local_name}.to_s}"
|
797
|
+
# Don't URI.escape the controller name since it may contain slashes.
|
798
|
+
def interpolation_chunk(value_code = "#{local_name}")
|
799
|
+
"\#{#{value_code}.to_s}"
|
740
800
|
end
|
741
801
|
|
742
802
|
# Make sure controller names like Admin/Content are correctly normalized to
|
@@ -755,9 +815,11 @@ module ActionController
|
|
755
815
|
end
|
756
816
|
|
757
817
|
class PathSegment < DynamicSegment #:nodoc:
|
758
|
-
|
759
|
-
|
760
|
-
|
818
|
+
RESERVED_PCHAR = "#{Segment::RESERVED_PCHAR}/"
|
819
|
+
UNSAFE_PCHAR = Regexp.new("[^#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}]", false, 'N').freeze
|
820
|
+
|
821
|
+
def interpolation_chunk(value_code = "#{local_name}")
|
822
|
+
"\#{URI.escape(#{value_code}.to_s, ActionController::Routing::PathSegment::UNSAFE_PCHAR)}"
|
761
823
|
end
|
762
824
|
|
763
825
|
def default
|
@@ -776,30 +838,34 @@ module ActionController
|
|
776
838
|
regexp || "(.*)"
|
777
839
|
end
|
778
840
|
|
841
|
+
def optionality_implied?
|
842
|
+
true
|
843
|
+
end
|
844
|
+
|
779
845
|
class Result < ::Array #:nodoc:
|
780
|
-
def to_s() join '/' end
|
846
|
+
def to_s() join '/' end
|
781
847
|
def self.new_escaped(strings)
|
782
|
-
new strings.collect {|str|
|
783
|
-
end
|
784
|
-
end
|
848
|
+
new strings.collect {|str| URI.unescape str}
|
849
|
+
end
|
850
|
+
end
|
785
851
|
end
|
786
852
|
|
787
853
|
class RouteBuilder #:nodoc:
|
788
854
|
attr_accessor :separators, :optional_separators
|
789
|
-
|
855
|
+
|
790
856
|
def initialize
|
791
857
|
self.separators = Routing::SEPARATORS
|
792
858
|
self.optional_separators = %w( / )
|
793
859
|
end
|
794
|
-
|
860
|
+
|
795
861
|
def separator_pattern(inverted = false)
|
796
862
|
"[#{'^' if inverted}#{Regexp.escape(separators.join)}]"
|
797
863
|
end
|
798
|
-
|
864
|
+
|
799
865
|
def interval_regexp
|
800
866
|
Regexp.new "(.*?)(#{separators.source}|$)"
|
801
867
|
end
|
802
|
-
|
868
|
+
|
803
869
|
# Accepts a "route path" (a string defining a route), and returns the array
|
804
870
|
# of segments that corresponds to it. Note that the segment array is only
|
805
871
|
# partially initialized--the defaults and requirements, for instance, need
|
@@ -808,7 +874,7 @@ module ActionController
|
|
808
874
|
# #assign_route_options is called, as well.
|
809
875
|
def segments_for_route_path(path)
|
810
876
|
rest, segments = path, []
|
811
|
-
|
877
|
+
|
812
878
|
until rest.empty?
|
813
879
|
segment, rest = segment_for rest
|
814
880
|
segments << segment
|
@@ -839,12 +905,20 @@ module ActionController
|
|
839
905
|
end
|
840
906
|
[segment, $~.post_match]
|
841
907
|
end
|
842
|
-
|
908
|
+
|
843
909
|
# Split the given hash of options into requirement and default hashes. The
|
844
910
|
# segments are passed alongside in order to distinguish between default values
|
845
911
|
# and requirements.
|
846
912
|
def divide_route_options(segments, options)
|
847
913
|
options = options.dup
|
914
|
+
|
915
|
+
if options[:namespace]
|
916
|
+
options[:controller] = "#{options[:path_prefix]}/#{options[:controller]}"
|
917
|
+
options.delete(:path_prefix)
|
918
|
+
options.delete(:name_prefix)
|
919
|
+
options.delete(:namespace)
|
920
|
+
end
|
921
|
+
|
848
922
|
requirements = (options.delete(:requirements) || {}).dup
|
849
923
|
defaults = (options.delete(:defaults) || {}).dup
|
850
924
|
conditions = (options.delete(:conditions) || {}).dup
|
@@ -854,20 +928,20 @@ module ActionController
|
|
854
928
|
hash = (path_keys.include?(key) && ! value.is_a?(Regexp)) ? defaults : requirements
|
855
929
|
hash[key] = value
|
856
930
|
end
|
857
|
-
|
931
|
+
|
858
932
|
[defaults, requirements, conditions]
|
859
933
|
end
|
860
|
-
|
934
|
+
|
861
935
|
# Takes a hash of defaults and a hash of requirements, and assigns them to
|
862
936
|
# the segments. Any unused requirements (which do not correspond to a segment)
|
863
937
|
# are returned as a hash.
|
864
938
|
def assign_route_options(segments, defaults, requirements)
|
865
939
|
route_requirements = {} # Requirements that do not belong to a segment
|
866
|
-
|
940
|
+
|
867
941
|
segment_named = Proc.new do |key|
|
868
942
|
segments.detect { |segment| segment.key == key if segment.respond_to?(:key) }
|
869
943
|
end
|
870
|
-
|
944
|
+
|
871
945
|
requirements.each do |key, requirement|
|
872
946
|
segment = segment_named[key]
|
873
947
|
if segment
|
@@ -880,19 +954,19 @@ module ActionController
|
|
880
954
|
route_requirements[key] = requirement
|
881
955
|
end
|
882
956
|
end
|
883
|
-
|
957
|
+
|
884
958
|
defaults.each do |key, default|
|
885
959
|
segment = segment_named[key]
|
886
960
|
raise ArgumentError, "#{key}: No matching segment exists; cannot assign default" unless segment
|
887
961
|
segment.is_optional = true
|
888
962
|
segment.default = default.to_param if default
|
889
963
|
end
|
890
|
-
|
964
|
+
|
891
965
|
assign_default_route_options(segments)
|
892
966
|
ensure_required_segments(segments)
|
893
967
|
route_requirements
|
894
968
|
end
|
895
|
-
|
969
|
+
|
896
970
|
# Assign default options, such as 'index' as a default for :action. This
|
897
971
|
# method must be run *after* user supplied requirements and defaults have
|
898
972
|
# been applied to the segments.
|
@@ -912,7 +986,7 @@ module ActionController
|
|
912
986
|
end
|
913
987
|
end
|
914
988
|
end
|
915
|
-
|
989
|
+
|
916
990
|
# Makes sure that there are no optional segments that precede a required
|
917
991
|
# segment. If any are found that precede a required segment, they are
|
918
992
|
# made required.
|
@@ -925,24 +999,27 @@ module ActionController
|
|
925
999
|
warn "Route segment \"#{segment.to_s}\" cannot be optional because it precedes a required segment. This segment will be required."
|
926
1000
|
end
|
927
1001
|
segment.is_optional = false
|
928
|
-
elsif allow_optional
|
1002
|
+
elsif allow_optional && segment.respond_to?(:default) && segment.default
|
929
1003
|
# if a segment has a default, then it is optional
|
930
1004
|
segment.is_optional = true
|
931
1005
|
end
|
932
1006
|
end
|
933
1007
|
end
|
934
|
-
|
1008
|
+
|
935
1009
|
# Construct and return a route with the given path and options.
|
936
1010
|
def build(path, options)
|
937
1011
|
# Wrap the path with slashes
|
938
1012
|
path = "/#{path}" unless path[0] == ?/
|
939
1013
|
path = "#{path}/" unless path[-1] == ?/
|
940
|
-
|
1014
|
+
|
1015
|
+
path = "/#{options[:path_prefix].to_s.gsub(/^\//,'')}#{path}" if options[:path_prefix]
|
1016
|
+
|
941
1017
|
segments = segments_for_route_path(path)
|
942
1018
|
defaults, requirements, conditions = divide_route_options(segments, options)
|
943
1019
|
requirements = assign_route_options(segments, defaults, requirements)
|
944
1020
|
|
945
1021
|
route = Route.new
|
1022
|
+
|
946
1023
|
route.segments = segments
|
947
1024
|
route.requirements = requirements
|
948
1025
|
route.conditions = conditions
|
@@ -952,6 +1029,13 @@ module ActionController
|
|
952
1029
|
route.significant_keys << :action
|
953
1030
|
end
|
954
1031
|
|
1032
|
+
# Routes cannot use the current string interpolation method
|
1033
|
+
# if there are user-supplied :requirements as the interpolation
|
1034
|
+
# code won't raise RoutingErrors when generating
|
1035
|
+
if options.key?(:requirements) || route.requirements.keys.to_set != Routing::ALLOWED_REQUIREMENTS_FOR_OPTIMISATION
|
1036
|
+
route.optimise = false
|
1037
|
+
end
|
1038
|
+
|
955
1039
|
if !route.significant_keys.include?(:controller)
|
956
1040
|
raise ArgumentError, "Illegal route: the :controller must be specified!"
|
957
1041
|
end
|
@@ -963,35 +1047,48 @@ module ActionController
|
|
963
1047
|
class RouteSet #:nodoc:
|
964
1048
|
# Mapper instances are used to build routes. The object passed to the draw
|
965
1049
|
# block in config/routes.rb is a Mapper instance.
|
966
|
-
#
|
1050
|
+
#
|
967
1051
|
# Mapper instances have relatively few instance methods, in order to avoid
|
968
1052
|
# clashes with named routes.
|
969
1053
|
class Mapper #:nodoc:
|
970
1054
|
def initialize(set)
|
971
1055
|
@set = set
|
972
1056
|
end
|
973
|
-
|
974
|
-
# Create an unnamed route with the provided +path+ and +options+. See
|
1057
|
+
|
1058
|
+
# Create an unnamed route with the provided +path+ and +options+. See
|
975
1059
|
# SomeHelpfulUrl for an introduction to routes.
|
976
1060
|
def connect(path, options = {})
|
977
1061
|
@set.add_route(path, options)
|
978
1062
|
end
|
979
1063
|
|
1064
|
+
# Creates a named route called "root" for matching the root level request.
|
1065
|
+
def root(options = {})
|
1066
|
+
named_route("root", '', options)
|
1067
|
+
end
|
1068
|
+
|
980
1069
|
def named_route(name, path, options = {})
|
981
1070
|
@set.add_named_route(name, path, options)
|
982
1071
|
end
|
983
|
-
|
984
|
-
def deprecated_named_route(name, deprecated_name, options = {})
|
985
|
-
@set.add_deprecated_named_route(name, deprecated_name)
|
986
|
-
end
|
987
1072
|
|
988
|
-
#
|
989
|
-
#
|
990
|
-
|
991
|
-
|
992
|
-
|
1073
|
+
# Enables the use of resources in a module by setting the name_prefix, path_prefix, and namespace for the model.
|
1074
|
+
# Example:
|
1075
|
+
#
|
1076
|
+
# map.namespace(:admin) do |admin|
|
1077
|
+
# admin.resources :products,
|
1078
|
+
# :has_many => [ :tags, :images, :variants ]
|
1079
|
+
# end
|
1080
|
+
#
|
1081
|
+
# This will create admin_products_url pointing to "admin/products", which will look for an Admin::ProductsController.
|
1082
|
+
# It'll also create admin_product_tags_url pointing to "admin/products/#{product_id}/tags", which will look for
|
1083
|
+
# Admin::TagsController.
|
1084
|
+
def namespace(name, options = {}, &block)
|
1085
|
+
if options[:namespace]
|
1086
|
+
with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}", :name_prefix => "#{options.delete(:name_prefix)}#{name}_", :namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block)
|
1087
|
+
else
|
1088
|
+
with_options({:path_prefix => name, :name_prefix => "#{name}_", :namespace => "#{name}/" }.merge(options), &block)
|
1089
|
+
end
|
993
1090
|
end
|
994
|
-
|
1091
|
+
|
995
1092
|
|
996
1093
|
def method_missing(route_name, *args, &proc)
|
997
1094
|
super unless args.length >= 1 && proc.nil?
|
@@ -1004,7 +1101,7 @@ module ActionController
|
|
1004
1101
|
# named routes.
|
1005
1102
|
class NamedRouteCollection #:nodoc:
|
1006
1103
|
include Enumerable
|
1007
|
-
|
1104
|
+
include ActionController::Routing::Optimisation
|
1008
1105
|
attr_reader :routes, :helpers
|
1009
1106
|
|
1010
1107
|
def initialize
|
@@ -1017,7 +1114,7 @@ module ActionController
|
|
1017
1114
|
|
1018
1115
|
@module ||= Module.new
|
1019
1116
|
@module.instance_methods.each do |selector|
|
1020
|
-
@module.
|
1117
|
+
@module.class_eval { remove_method selector }
|
1021
1118
|
end
|
1022
1119
|
end
|
1023
1120
|
|
@@ -1047,40 +1144,19 @@ module ActionController
|
|
1047
1144
|
routes.length
|
1048
1145
|
end
|
1049
1146
|
|
1050
|
-
def
|
1051
|
-
|
1147
|
+
def reset!
|
1148
|
+
old_routes = routes.dup
|
1149
|
+
clear!
|
1150
|
+
old_routes.each do |name, route|
|
1151
|
+
add(name, route)
|
1152
|
+
end
|
1052
1153
|
end
|
1053
|
-
|
1054
|
-
def define_deprecated_named_route_methods(name, deprecated_name)
|
1055
|
-
|
1056
|
-
[:url, :path].each do |kind|
|
1057
|
-
@module.send :module_eval, <<-end_eval # We use module_eval to avoid leaks
|
1058
|
-
|
1059
|
-
def #{url_helper_name(deprecated_name, kind)}(*args)
|
1060
|
-
|
1061
|
-
ActiveSupport::Deprecation.warn(
|
1062
|
-
'The named route "#{url_helper_name(deprecated_name, kind)}" uses a format that has been deprecated. ' +
|
1063
|
-
'You should use "#{url_helper_name(name, kind)}" instead.', caller
|
1064
|
-
)
|
1065
|
-
|
1066
|
-
send :#{url_helper_name(name, kind)}, *args
|
1067
|
-
|
1068
|
-
end
|
1069
|
-
|
1070
|
-
def #{hash_access_name(deprecated_name, kind)}(*args)
|
1071
|
-
|
1072
|
-
ActiveSupport::Deprecation.warn(
|
1073
|
-
'The named route "#{hash_access_name(deprecated_name, kind)}" uses a format that has been deprecated. ' +
|
1074
|
-
'You should use "#{hash_access_name(name, kind)}" instead.', caller
|
1075
|
-
)
|
1076
|
-
|
1077
|
-
send :#{hash_access_name(name, kind)}, *args
|
1078
|
-
|
1079
|
-
end
|
1080
1154
|
|
1081
|
-
|
1155
|
+
def install(destinations = [ActionController::Base, ActionView::Base], regenerate = false)
|
1156
|
+
reset! if regenerate
|
1157
|
+
Array(destinations).each do |dest|
|
1158
|
+
dest.send! :include, @module
|
1082
1159
|
end
|
1083
|
-
|
1084
1160
|
end
|
1085
1161
|
|
1086
1162
|
private
|
@@ -1099,57 +1175,62 @@ module ActionController
|
|
1099
1175
|
define_url_helper route, name, kind, hash
|
1100
1176
|
end
|
1101
1177
|
end
|
1102
|
-
|
1178
|
+
|
1103
1179
|
def define_hash_access(route, name, kind, options)
|
1104
1180
|
selector = hash_access_name(name, kind)
|
1105
|
-
@module.
|
1181
|
+
@module.module_eval <<-end_eval # We use module_eval to avoid leaks
|
1106
1182
|
def #{selector}(options = nil)
|
1107
1183
|
options ? #{options.inspect}.merge(options) : #{options.inspect}
|
1108
1184
|
end
|
1185
|
+
protected :#{selector}
|
1109
1186
|
end_eval
|
1110
|
-
@module.send(:protected, selector)
|
1111
1187
|
helpers << selector
|
1112
1188
|
end
|
1113
|
-
|
1189
|
+
|
1114
1190
|
def define_url_helper(route, name, kind, options)
|
1115
1191
|
selector = url_helper_name(name, kind)
|
1116
|
-
|
1117
1192
|
# The segment keys used for positional paramters
|
1118
|
-
|
1119
|
-
segment.key if segment.respond_to? :key
|
1120
|
-
end.compact
|
1193
|
+
|
1121
1194
|
hash_access_method = hash_access_name(name, kind)
|
1122
|
-
|
1123
|
-
|
1195
|
+
|
1196
|
+
# allow ordered parameters to be associated with corresponding
|
1197
|
+
# dynamic segments, so you can do
|
1198
|
+
#
|
1199
|
+
# foo_url(bar, baz, bang)
|
1200
|
+
#
|
1201
|
+
# instead of
|
1202
|
+
#
|
1203
|
+
# foo_url(:bar => bar, :baz => baz, :bang => bang)
|
1204
|
+
#
|
1205
|
+
# Also allow options hash, so you can do
|
1206
|
+
#
|
1207
|
+
# foo_url(bar, baz, bang, :sort_by => 'baz')
|
1208
|
+
#
|
1209
|
+
@module.module_eval <<-end_eval # We use module_eval to avoid leaks
|
1124
1210
|
def #{selector}(*args)
|
1211
|
+
#{generate_optimisation_block(route, kind)}
|
1212
|
+
|
1125
1213
|
opts = if args.empty? || Hash === args.first
|
1126
1214
|
args.first || {}
|
1127
1215
|
else
|
1128
|
-
|
1129
|
-
#
|
1130
|
-
#
|
1131
|
-
# foo_url(bar, baz, bang)
|
1132
|
-
#
|
1133
|
-
# instead of
|
1134
|
-
#
|
1135
|
-
# foo_url(:bar => bar, :baz => baz, :bang => bang)
|
1136
|
-
args.zip(#{segment_keys.inspect}).inject({}) do |h, (v, k)|
|
1216
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
1217
|
+
args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)|
|
1137
1218
|
h[k] = v
|
1138
1219
|
h
|
1139
1220
|
end
|
1221
|
+
options.merge(args)
|
1140
1222
|
end
|
1141
|
-
|
1223
|
+
|
1142
1224
|
url_for(#{hash_access_method}(opts))
|
1143
1225
|
end
|
1226
|
+
protected :#{selector}
|
1144
1227
|
end_eval
|
1145
|
-
@module.send(:protected, selector)
|
1146
1228
|
helpers << selector
|
1147
1229
|
end
|
1148
|
-
|
1149
1230
|
end
|
1150
|
-
|
1231
|
+
|
1151
1232
|
attr_accessor :routes, :named_routes
|
1152
|
-
|
1233
|
+
|
1153
1234
|
def initialize
|
1154
1235
|
self.routes = []
|
1155
1236
|
self.named_routes = NamedRouteCollection.new
|
@@ -1164,9 +1245,9 @@ module ActionController
|
|
1164
1245
|
def draw
|
1165
1246
|
clear!
|
1166
1247
|
yield Mapper.new(self)
|
1167
|
-
|
1248
|
+
install_helpers
|
1168
1249
|
end
|
1169
|
-
|
1250
|
+
|
1170
1251
|
def clear!
|
1171
1252
|
routes.clear
|
1172
1253
|
named_routes.clear
|
@@ -1174,41 +1255,57 @@ module ActionController
|
|
1174
1255
|
@routes_by_controller = nil
|
1175
1256
|
end
|
1176
1257
|
|
1258
|
+
def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false)
|
1259
|
+
Array(destinations).each { |d| d.module_eval { include Helpers } }
|
1260
|
+
named_routes.install(destinations, regenerate_code)
|
1261
|
+
end
|
1262
|
+
|
1177
1263
|
def empty?
|
1178
1264
|
routes.empty?
|
1179
1265
|
end
|
1180
|
-
|
1266
|
+
|
1181
1267
|
def load!
|
1182
1268
|
Routing.use_controllers! nil # Clear the controller cache so we may discover new ones
|
1183
1269
|
clear!
|
1184
1270
|
load_routes!
|
1185
|
-
|
1271
|
+
install_helpers
|
1186
1272
|
end
|
1187
1273
|
|
1188
|
-
|
1274
|
+
# reload! will always force a reload whereas load checks the timestamp first
|
1275
|
+
alias reload! load!
|
1276
|
+
|
1277
|
+
def reload
|
1278
|
+
if @routes_last_modified && defined?(RAILS_ROOT)
|
1279
|
+
mtime = File.stat("#{RAILS_ROOT}/config/routes.rb").mtime
|
1280
|
+
# if it hasn't been changed, then just return
|
1281
|
+
return if mtime == @routes_last_modified
|
1282
|
+
# if it has changed then record the new time and fall to the load! below
|
1283
|
+
@routes_last_modified = mtime
|
1284
|
+
end
|
1285
|
+
load!
|
1286
|
+
end
|
1189
1287
|
|
1190
1288
|
def load_routes!
|
1191
1289
|
if defined?(RAILS_ROOT) && defined?(::ActionController::Routing::Routes) && self == ::ActionController::Routing::Routes
|
1192
1290
|
load File.join("#{RAILS_ROOT}/config/routes.rb")
|
1291
|
+
@routes_last_modified = File.stat("#{RAILS_ROOT}/config/routes.rb").mtime
|
1193
1292
|
else
|
1194
1293
|
add_route ":controller/:action/:id"
|
1195
1294
|
end
|
1196
1295
|
end
|
1197
|
-
|
1296
|
+
|
1198
1297
|
def add_route(path, options = {})
|
1199
1298
|
route = builder.build(path, options)
|
1200
1299
|
routes << route
|
1201
1300
|
route
|
1202
1301
|
end
|
1203
|
-
|
1302
|
+
|
1204
1303
|
def add_named_route(name, path, options = {})
|
1205
|
-
|
1206
|
-
|
1207
|
-
|
1208
|
-
def add_deprecated_named_route(name, deprecated_name)
|
1209
|
-
named_routes.define_deprecated_named_route_methods(name, deprecated_name)
|
1304
|
+
# TODO - is options EVER used?
|
1305
|
+
name = options[:name_prefix] + name.to_s if options[:name_prefix]
|
1306
|
+
named_routes[name.to_sym] = add_route(path, options)
|
1210
1307
|
end
|
1211
|
-
|
1308
|
+
|
1212
1309
|
def options_as_params(options)
|
1213
1310
|
# If an explicit :controller was given, always make :action explicit
|
1214
1311
|
# too, so that action expiry works as expected for things like
|
@@ -1226,10 +1323,10 @@ module ActionController
|
|
1226
1323
|
options_as_params[:action] = options_as_params[:action].to_s if options_as_params[:action]
|
1227
1324
|
options_as_params
|
1228
1325
|
end
|
1229
|
-
|
1326
|
+
|
1230
1327
|
def build_expiry(options, recall)
|
1231
1328
|
recall.inject({}) do |expiry, (key, recalled_value)|
|
1232
|
-
expiry[key] = (options.key?(key) && options[key] != recalled_value)
|
1329
|
+
expiry[key] = (options.key?(key) && options[key].to_param != recalled_value.to_param)
|
1233
1330
|
expiry
|
1234
1331
|
end
|
1235
1332
|
end
|
@@ -1246,6 +1343,7 @@ module ActionController
|
|
1246
1343
|
|
1247
1344
|
def generate(options, recall = {}, method=:generate)
|
1248
1345
|
named_route_name = options.delete(:use_route)
|
1346
|
+
generate_all = options.delete(:generate_all)
|
1249
1347
|
if named_route_name
|
1250
1348
|
named_route = named_routes[named_route_name]
|
1251
1349
|
options = named_route.parameter_shell.merge(options)
|
@@ -1274,7 +1372,7 @@ module ActionController
|
|
1274
1372
|
|
1275
1373
|
if named_route
|
1276
1374
|
path = named_route.generate(options, merged, expire_on)
|
1277
|
-
if path.nil?
|
1375
|
+
if path.nil?
|
1278
1376
|
raise_named_route_error(options, named_route, named_route_name)
|
1279
1377
|
else
|
1280
1378
|
return path
|
@@ -1282,23 +1380,31 @@ module ActionController
|
|
1282
1380
|
else
|
1283
1381
|
merged[:action] ||= 'index'
|
1284
1382
|
options[:action] ||= 'index'
|
1285
|
-
|
1383
|
+
|
1286
1384
|
controller = merged[:controller]
|
1287
1385
|
action = merged[:action]
|
1288
1386
|
|
1289
1387
|
raise RoutingError, "Need controller and action!" unless controller && action
|
1388
|
+
|
1389
|
+
if generate_all
|
1390
|
+
# Used by caching to expire all paths for a resource
|
1391
|
+
return routes.collect do |route|
|
1392
|
+
route.send!(method, options, merged, expire_on)
|
1393
|
+
end.compact
|
1394
|
+
end
|
1395
|
+
|
1290
1396
|
# don't use the recalled keys when determining which routes to check
|
1291
1397
|
routes = routes_by_controller[controller][action][options.keys.sort_by { |x| x.object_id }]
|
1292
1398
|
|
1293
1399
|
routes.each do |route|
|
1294
|
-
results = route.send(method, options, merged, expire_on)
|
1400
|
+
results = route.send!(method, options, merged, expire_on)
|
1295
1401
|
return results if results && (!results.is_a?(Array) || results.first)
|
1296
1402
|
end
|
1297
1403
|
end
|
1298
|
-
|
1404
|
+
|
1299
1405
|
raise RoutingError, "No route matches #{options.inspect}"
|
1300
1406
|
end
|
1301
|
-
|
1407
|
+
|
1302
1408
|
# try to give a helpful error message when named route generation fails
|
1303
1409
|
def raise_named_route_error(options, named_route, named_route_name)
|
1304
1410
|
diff = named_route.requirements.diff(options)
|
@@ -1307,24 +1413,32 @@ module ActionController
|
|
1307
1413
|
else
|
1308
1414
|
required_segments = named_route.segments.select {|seg| (!seg.optional?) && (!seg.is_a?(DividerSegment)) }
|
1309
1415
|
required_keys_or_values = required_segments.map { |seg| seg.key rescue seg.value } # we want either the key or the value from the segment
|
1310
|
-
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
|
1416
|
+
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?"
|
1311
1417
|
end
|
1312
1418
|
end
|
1313
|
-
|
1419
|
+
|
1314
1420
|
def recognize(request)
|
1315
1421
|
params = recognize_path(request.path, extract_request_environment(request))
|
1316
1422
|
request.path_parameters = params.with_indifferent_access
|
1317
1423
|
"#{params[:controller].camelize}Controller".constantize
|
1318
1424
|
end
|
1319
|
-
|
1425
|
+
|
1320
1426
|
def recognize_path(path, environment={})
|
1321
|
-
path = CGI.unescape(path)
|
1322
1427
|
routes.each do |route|
|
1323
1428
|
result = route.recognize(path, environment) and return result
|
1324
1429
|
end
|
1325
|
-
|
1430
|
+
|
1431
|
+
allows = HTTP_METHODS.select { |verb| routes.find { |r| r.recognize(path, :method => verb) } }
|
1432
|
+
|
1433
|
+
if environment[:method] && !HTTP_METHODS.include?(environment[:method])
|
1434
|
+
raise NotImplemented.new(*allows)
|
1435
|
+
elsif !allows.empty?
|
1436
|
+
raise MethodNotAllowed.new(*allows)
|
1437
|
+
else
|
1438
|
+
raise RoutingError, "No route matches #{path.inspect} with #{environment.inspect}"
|
1439
|
+
end
|
1326
1440
|
end
|
1327
|
-
|
1441
|
+
|
1328
1442
|
def routes_by_controller
|
1329
1443
|
@routes_by_controller ||= Hash.new do |controller_hash, controller|
|
1330
1444
|
controller_hash[controller] = Hash.new do |action_hash, action|
|
@@ -1334,23 +1448,23 @@ module ActionController
|
|
1334
1448
|
end
|
1335
1449
|
end
|
1336
1450
|
end
|
1337
|
-
|
1451
|
+
|
1338
1452
|
def routes_for(options, merged, expire_on)
|
1339
1453
|
raise "Need controller and action!" unless controller && action
|
1340
1454
|
controller = merged[:controller]
|
1341
1455
|
merged = options if expire_on[:controller]
|
1342
1456
|
action = merged[:action] || 'index'
|
1343
|
-
|
1457
|
+
|
1344
1458
|
routes_by_controller[controller][action][merged.keys]
|
1345
1459
|
end
|
1346
|
-
|
1460
|
+
|
1347
1461
|
def routes_for_controller_and_action(controller, action)
|
1348
1462
|
selected = routes.select do |route|
|
1349
1463
|
route.matches_controller_and_action? controller, action
|
1350
1464
|
end
|
1351
1465
|
(selected.length == routes.length) ? routes : selected
|
1352
1466
|
end
|
1353
|
-
|
1467
|
+
|
1354
1468
|
def routes_for_controller_and_action_and_keys(controller, action, keys)
|
1355
1469
|
selected = routes.select do |route|
|
1356
1470
|
route.matches_controller_and_action? controller, action
|
@@ -1368,6 +1482,15 @@ module ActionController
|
|
1368
1482
|
end
|
1369
1483
|
|
1370
1484
|
Routes = RouteSet.new
|
1485
|
+
|
1486
|
+
::Inflector.module_eval do
|
1487
|
+
def inflections_with_route_reloading(&block)
|
1488
|
+
returning(inflections_without_route_reloading(&block)) {
|
1489
|
+
ActionController::Routing::Routes.reload! if block_given?
|
1490
|
+
}
|
1491
|
+
end
|
1492
|
+
|
1493
|
+
alias_method_chain :inflections, :route_reloading
|
1494
|
+
end
|
1371
1495
|
end
|
1372
1496
|
end
|
1373
|
-
|