actionpack 2.0.5 → 2.1.0
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.
- data/CHANGELOG +149 -7
- data/MIT-LICENSE +1 -1
- data/README +1 -1
- data/Rakefile +5 -6
- data/lib/action_controller.rb +2 -2
- data/lib/action_controller/assertions/model_assertions.rb +2 -1
- data/lib/action_controller/assertions/response_assertions.rb +4 -2
- data/lib/action_controller/assertions/routing_assertions.rb +3 -3
- data/lib/action_controller/assertions/selector_assertions.rb +30 -27
- data/lib/action_controller/assertions/tag_assertions.rb +3 -3
- data/lib/action_controller/base.rb +103 -129
- data/lib/action_controller/benchmarking.rb +3 -3
- data/lib/action_controller/caching.rb +41 -652
- data/lib/action_controller/caching/actions.rb +144 -0
- data/lib/action_controller/caching/fragments.rb +138 -0
- data/lib/action_controller/caching/pages.rb +154 -0
- data/lib/action_controller/caching/sql_cache.rb +18 -0
- data/lib/action_controller/caching/sweeping.rb +97 -0
- data/lib/action_controller/cgi_ext/cookie.rb +27 -23
- data/lib/action_controller/cgi_ext/stdinput.rb +1 -0
- data/lib/action_controller/cgi_process.rb +6 -4
- data/lib/action_controller/components.rb +7 -6
- data/lib/action_controller/cookies.rb +31 -19
- data/lib/action_controller/dispatcher.rb +51 -84
- data/lib/action_controller/filters.rb +295 -421
- data/lib/action_controller/flash.rb +1 -6
- data/lib/action_controller/headers.rb +31 -0
- data/lib/action_controller/helpers.rb +26 -9
- data/lib/action_controller/http_authentication.rb +1 -1
- data/lib/action_controller/integration.rb +65 -13
- data/lib/action_controller/layout.rb +24 -39
- data/lib/action_controller/mime_responds.rb +7 -3
- data/lib/action_controller/mime_type.rb +25 -9
- data/lib/action_controller/mime_types.rb +1 -1
- data/lib/action_controller/polymorphic_routes.rb +32 -17
- data/lib/action_controller/record_identifier.rb +10 -4
- data/lib/action_controller/request.rb +46 -30
- data/lib/action_controller/request_forgery_protection.rb +10 -9
- data/lib/action_controller/request_profiler.rb +29 -8
- data/lib/action_controller/rescue.rb +24 -24
- data/lib/action_controller/resources.rb +66 -23
- data/lib/action_controller/response.rb +2 -2
- data/lib/action_controller/routing.rb +113 -1229
- data/lib/action_controller/routing/builder.rb +204 -0
- data/lib/action_controller/{routing_optimisation.rb → routing/optimisations.rb} +13 -12
- data/lib/action_controller/routing/recognition_optimisation.rb +158 -0
- data/lib/action_controller/routing/route.rb +240 -0
- data/lib/action_controller/routing/route_set.rb +435 -0
- data/lib/action_controller/routing/routing_ext.rb +46 -0
- data/lib/action_controller/routing/segments.rb +283 -0
- data/lib/action_controller/session/active_record_store.rb +13 -8
- data/lib/action_controller/session/cookie_store.rb +20 -17
- data/lib/action_controller/session_management.rb +10 -3
- data/lib/action_controller/streaming.rb +45 -31
- data/lib/action_controller/test_case.rb +33 -23
- data/lib/action_controller/test_process.rb +39 -35
- data/lib/action_controller/url_rewriter.rb +18 -12
- data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +1 -1
- data/lib/action_pack.rb +1 -1
- data/lib/action_pack/version.rb +2 -2
- data/lib/action_view.rb +11 -3
- data/lib/action_view/base.rb +73 -390
- data/lib/action_view/helpers/active_record_helper.rb +83 -62
- data/lib/action_view/helpers/asset_tag_helper.rb +101 -44
- data/lib/action_view/helpers/atom_feed_helper.rb +35 -7
- data/lib/action_view/helpers/benchmark_helper.rb +5 -3
- data/lib/action_view/helpers/cache_helper.rb +3 -2
- data/lib/action_view/helpers/capture_helper.rb +1 -2
- data/lib/action_view/helpers/date_helper.rb +104 -82
- data/lib/action_view/helpers/form_helper.rb +148 -75
- data/lib/action_view/helpers/form_options_helper.rb +44 -23
- data/lib/action_view/helpers/form_tag_helper.rb +22 -13
- data/lib/action_view/helpers/javascripts/controls.js +1 -1
- data/lib/action_view/helpers/javascripts/dragdrop.js +1 -1
- data/lib/action_view/helpers/javascripts/effects.js +1 -1
- data/lib/action_view/helpers/number_helper.rb +10 -3
- data/lib/action_view/helpers/prototype_helper.rb +61 -29
- data/lib/action_view/helpers/record_tag_helper.rb +3 -3
- data/lib/action_view/helpers/sanitize_helper.rb +23 -17
- data/lib/action_view/helpers/scriptaculous_helper.rb +86 -60
- data/lib/action_view/helpers/text_helper.rb +153 -125
- data/lib/action_view/helpers/url_helper.rb +83 -28
- data/lib/action_view/inline_template.rb +20 -0
- data/lib/action_view/partial_template.rb +70 -0
- data/lib/action_view/partials.rb +31 -73
- data/lib/action_view/template.rb +127 -0
- data/lib/action_view/template_error.rb +8 -7
- data/lib/action_view/template_finder.rb +177 -0
- data/lib/action_view/template_handler.rb +18 -1
- data/lib/action_view/template_handlers/builder.rb +10 -2
- data/lib/action_view/template_handlers/compilable.rb +128 -0
- data/lib/action_view/template_handlers/erb.rb +37 -2
- data/lib/action_view/template_handlers/rjs.rb +14 -1
- data/lib/action_view/test_case.rb +58 -0
- data/test/abstract_unit.rb +1 -1
- data/test/active_record_unit.rb +3 -6
- data/test/activerecord/active_record_store_test.rb +1 -2
- data/test/activerecord/render_partial_with_record_identification_test.rb +158 -41
- data/test/adv_attr_test.rb +20 -0
- data/test/controller/action_pack_assertions_test.rb +16 -19
- data/test/controller/addresses_render_test.rb +1 -1
- data/test/controller/assert_select_test.rb +13 -6
- data/test/controller/base_test.rb +48 -2
- data/test/controller/benchmark_test.rb +1 -2
- data/test/controller/caching_test.rb +282 -21
- data/test/controller/capture_test.rb +1 -1
- data/test/controller/cgi_test.rb +1 -1
- data/test/controller/components_test.rb +1 -1
- data/test/controller/content_type_test.rb +2 -2
- data/test/controller/cookie_test.rb +13 -2
- data/test/controller/custom_handler_test.rb +14 -10
- data/test/controller/deprecation/deprecated_base_methods_test.rb +1 -1
- data/test/controller/dispatcher_test.rb +31 -49
- data/test/controller/fake_controllers.rb +17 -0
- data/test/controller/fake_models.rb +6 -0
- data/test/controller/filter_params_test.rb +14 -8
- data/test/controller/filters_test.rb +44 -16
- data/test/controller/flash_test.rb +2 -2
- data/test/controller/header_test.rb +14 -0
- data/test/controller/helper_test.rb +19 -15
- data/test/controller/html-scanner/document_test.rb +1 -2
- data/test/controller/html-scanner/node_test.rb +1 -2
- data/test/controller/html-scanner/sanitizer_test.rb +8 -5
- data/test/controller/html-scanner/tag_node_test.rb +1 -2
- data/test/controller/html-scanner/text_node_test.rb +2 -3
- data/test/controller/html-scanner/tokenizer_test.rb +8 -2
- data/test/controller/http_authentication_test.rb +1 -1
- data/test/controller/integration_test.rb +14 -16
- data/test/controller/integration_upload_test.rb +43 -0
- data/test/controller/layout_test.rb +26 -6
- data/test/controller/mime_responds_test.rb +39 -7
- data/test/controller/mime_type_test.rb +29 -5
- data/test/controller/new_render_test.rb +105 -34
- data/test/controller/polymorphic_routes_test.rb +32 -20
- data/test/controller/record_identifier_test.rb +38 -2
- data/test/controller/redirect_test.rb +21 -1
- data/test/controller/render_test.rb +59 -15
- data/test/controller/request_forgery_protection_test.rb +92 -5
- data/test/controller/request_test.rb +64 -6
- data/test/controller/rescue_test.rb +22 -6
- data/test/controller/resources_test.rb +102 -14
- data/test/controller/routing_test.rb +231 -19
- data/test/controller/selector_test.rb +2 -2
- data/test/controller/send_file_test.rb +14 -3
- data/test/controller/session/cookie_store_test.rb +16 -4
- data/test/controller/session/mem_cache_store_test.rb +3 -4
- data/test/controller/session_fixation_test.rb +1 -1
- data/test/controller/session_management_test.rb +23 -1
- data/test/controller/test_test.rb +39 -18
- data/test/controller/url_rewriter_test.rb +35 -1
- data/test/controller/verification_test.rb +1 -1
- data/test/controller/view_paths_test.rb +15 -12
- data/test/controller/webservice_test.rb +48 -3
- data/test/fixtures/bad_customers/_bad_customer.html.erb +1 -0
- data/test/fixtures/company.rb +1 -0
- data/test/fixtures/customers/_customer.html.erb +1 -0
- data/test/fixtures/db_definitions/sqlite.sql +6 -0
- data/test/fixtures/functional_caching/_partial.erb +3 -0
- data/test/fixtures/functional_caching/fragment_cached.html.erb +2 -0
- data/test/fixtures/functional_caching/html_fragment_cached_with_partial.html.erb +1 -0
- data/test/fixtures/functional_caching/js_fragment_cached_with_partial.js.rjs +1 -0
- data/test/fixtures/good_customers/_good_customer.html.erb +1 -0
- data/test/fixtures/mascot.rb +3 -0
- data/test/fixtures/mascots.yml +4 -0
- data/test/fixtures/mascots/_mascot.html.erb +1 -0
- data/test/fixtures/multipart/boundary_problem_file +10 -0
- data/test/fixtures/public/javascripts/application.js +1 -0
- data/test/fixtures/public/javascripts/controls.js +1 -0
- data/test/fixtures/public/javascripts/dragdrop.js +1 -0
- data/test/fixtures/public/javascripts/effects.js +1 -0
- data/test/fixtures/public/javascripts/prototype.js +1 -0
- data/test/fixtures/public/javascripts/version.1.0.js +1 -0
- data/test/fixtures/public/stylesheets/version.1.0.css +1 -0
- data/test/fixtures/reply.rb +1 -0
- data/test/fixtures/shared.html.erb +1 -0
- data/test/fixtures/symlink_parent/symlinked_layout.erb +5 -0
- data/test/fixtures/test/_customer_counter.erb +1 -0
- data/test/fixtures/test/_form.erb +1 -0
- data/test/fixtures/test/_labelling_form.erb +1 -0
- data/test/fixtures/test/_raise.html.erb +1 -0
- data/test/fixtures/test/greeting.js.rjs +1 -0
- data/test/fixtures/topics/_topic.html.erb +1 -0
- data/test/template/active_record_helper_test.rb +25 -8
- data/test/template/asset_tag_helper_test.rb +100 -17
- data/test/template/atom_feed_helper_test.rb +29 -1
- data/test/template/benchmark_helper_test.rb +10 -22
- data/test/template/date_helper_test.rb +455 -153
- data/test/template/erb_util_test.rb +10 -42
- data/test/template/form_helper_test.rb +192 -66
- data/test/template/form_options_helper_test.rb +19 -8
- data/test/template/form_tag_helper_test.rb +11 -8
- data/test/template/javascript_helper_test.rb +3 -9
- data/test/template/number_helper_test.rb +6 -3
- data/test/template/prototype_helper_test.rb +27 -40
- data/test/template/record_tag_helper_test.rb +54 -0
- data/test/template/sanitize_helper_test.rb +5 -6
- data/test/template/scriptaculous_helper_test.rb +7 -13
- data/test/template/tag_helper_test.rb +3 -6
- data/test/template/template_finder_test.rb +73 -0
- data/test/template/template_object_test.rb +95 -0
- data/test/template/test_test.rb +56 -0
- data/test/template/text_helper_test.rb +46 -33
- data/test/template/url_helper_test.rb +8 -10
- metadata +65 -12
- data/lib/action_view/compiled_templates.rb +0 -69
- data/test/action_view_test.rb +0 -44
- data/test/activerecord/fixtures_test.rb +0 -24
- data/test/controller/fragment_store_setting_test.rb +0 -47
- data/test/template/compiled_templates_test.rb +0 -197
- data/test/template/deprecate_ivars_test.rb +0 -51
|
@@ -17,13 +17,13 @@ module ActionController
|
|
|
17
17
|
reset!
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
def benchmark(n)
|
|
20
|
+
def benchmark(n, profiling = false)
|
|
21
21
|
@quiet = true
|
|
22
22
|
print ' '
|
|
23
23
|
|
|
24
24
|
result = Benchmark.realtime do
|
|
25
25
|
n.times do |i|
|
|
26
|
-
run
|
|
26
|
+
run(profiling)
|
|
27
27
|
print_progress(i)
|
|
28
28
|
end
|
|
29
29
|
end
|
|
@@ -41,7 +41,23 @@ module ActionController
|
|
|
41
41
|
private
|
|
42
42
|
def define_run_method(script_path)
|
|
43
43
|
script = File.read(script_path)
|
|
44
|
-
|
|
44
|
+
|
|
45
|
+
source = <<-end_source
|
|
46
|
+
def run(profiling = false)
|
|
47
|
+
if profiling
|
|
48
|
+
RubyProf.resume do
|
|
49
|
+
#{script}
|
|
50
|
+
end
|
|
51
|
+
else
|
|
52
|
+
#{script}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
old_request_count = request_count
|
|
56
|
+
reset!
|
|
57
|
+
self.request_count = old_request_count
|
|
58
|
+
end
|
|
59
|
+
end_source
|
|
60
|
+
|
|
45
61
|
instance_eval source, script_path, 1
|
|
46
62
|
end
|
|
47
63
|
|
|
@@ -82,21 +98,22 @@ module ActionController
|
|
|
82
98
|
def profile(sandbox)
|
|
83
99
|
load_ruby_prof
|
|
84
100
|
|
|
85
|
-
|
|
101
|
+
benchmark(sandbox, true)
|
|
102
|
+
results = RubyProf.stop
|
|
86
103
|
|
|
87
104
|
show_profile_results results
|
|
88
105
|
results
|
|
89
106
|
end
|
|
90
107
|
|
|
91
|
-
def benchmark(sandbox)
|
|
108
|
+
def benchmark(sandbox, profiling = false)
|
|
92
109
|
sandbox.request_count = 0
|
|
93
|
-
elapsed = sandbox.benchmark(options[:n]).to_f
|
|
110
|
+
elapsed = sandbox.benchmark(options[:n], profiling).to_f
|
|
94
111
|
count = sandbox.request_count.to_i
|
|
95
112
|
puts '%.2f sec, %d requests, %d req/sec' % [elapsed, count, count / elapsed]
|
|
96
113
|
end
|
|
97
114
|
|
|
98
115
|
def warmup(sandbox)
|
|
99
|
-
Benchmark.realtime { sandbox.run }
|
|
116
|
+
Benchmark.realtime { sandbox.run(false) }
|
|
100
117
|
end
|
|
101
118
|
|
|
102
119
|
def default_options
|
|
@@ -110,6 +127,7 @@ module ActionController
|
|
|
110
127
|
|
|
111
128
|
opt.on('-n', '--times [100]', 'How many requests to process. Defaults to 100.') { |v| options[:n] = v.to_i if v }
|
|
112
129
|
opt.on('-b', '--benchmark', 'Benchmark instead of profiling') { |v| options[:benchmark] = v }
|
|
130
|
+
opt.on('-m', '--measure [mode]', 'Which ruby-prof measure mode to use: process_time, wall_time, cpu_time, allocations, or memory. Defaults to process_time.') { |v| options[:measure] = v }
|
|
113
131
|
opt.on('--open [CMD]', 'Command to open profile results. Defaults to "open %s &"') { |v| options[:open] = v }
|
|
114
132
|
opt.on('-h', '--help', 'Show this help') { puts opt; exit }
|
|
115
133
|
|
|
@@ -126,8 +144,11 @@ module ActionController
|
|
|
126
144
|
protected
|
|
127
145
|
def load_ruby_prof
|
|
128
146
|
begin
|
|
147
|
+
gem 'ruby-prof', '>= 0.6.1'
|
|
129
148
|
require 'ruby-prof'
|
|
130
|
-
|
|
149
|
+
if mode = options[:measure]
|
|
150
|
+
RubyProf.measure_mode = RubyProf.const_get(mode.upcase)
|
|
151
|
+
end
|
|
131
152
|
rescue LoadError
|
|
132
153
|
abort '`gem install ruby-prof` to use the profiler'
|
|
133
154
|
end
|
|
@@ -26,7 +26,7 @@ module ActionController #:nodoc:
|
|
|
26
26
|
|
|
27
27
|
DEFAULT_RESCUE_TEMPLATE = 'diagnostics'
|
|
28
28
|
DEFAULT_RESCUE_TEMPLATES = {
|
|
29
|
-
'
|
|
29
|
+
'ActionView::MissingTemplate' => 'missing_template',
|
|
30
30
|
'ActionController::RoutingError' => 'routing_error',
|
|
31
31
|
'ActionController::UnknownAction' => 'unknown_action',
|
|
32
32
|
'ActionView::TemplateError' => 'template_error'
|
|
@@ -58,33 +58,35 @@ module ActionController #:nodoc:
|
|
|
58
58
|
# Rescue exceptions raised in controller actions.
|
|
59
59
|
#
|
|
60
60
|
# <tt>rescue_from</tt> receives a series of exception classes or class
|
|
61
|
-
# names, and a trailing
|
|
62
|
-
# object to be called to handle them. Alternatively a block can
|
|
61
|
+
# names, and a trailing <tt>:with</tt> option with the name of a method
|
|
62
|
+
# or a Proc object to be called to handle them. Alternatively a block can
|
|
63
|
+
# be given.
|
|
63
64
|
#
|
|
64
65
|
# Handlers that take one argument will be called with the exception, so
|
|
65
66
|
# that the exception can be inspected when dealing with it.
|
|
66
67
|
#
|
|
67
68
|
# Handlers are inherited. They are searched from right to left, from
|
|
68
69
|
# bottom to top, and up the hierarchy. The handler of the first class for
|
|
69
|
-
# which exception.is_a?(klass) holds true is the one invoked, if
|
|
70
|
+
# which <tt>exception.is_a?(klass)</tt> holds true is the one invoked, if
|
|
71
|
+
# any.
|
|
70
72
|
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
#
|
|
73
|
+
# class ApplicationController < ActionController::Base
|
|
74
|
+
# rescue_from User::NotAuthorized, :with => :deny_access # self defined exception
|
|
75
|
+
# rescue_from ActiveRecord::RecordInvalid, :with => :show_errors
|
|
74
76
|
#
|
|
75
|
-
#
|
|
76
|
-
#
|
|
77
|
-
# end
|
|
78
|
-
#
|
|
79
|
-
# protected
|
|
80
|
-
# def deny_access
|
|
81
|
-
# ...
|
|
77
|
+
# rescue_from 'MyAppError::Base' do |exception|
|
|
78
|
+
# render :xml => exception, :status => 500
|
|
82
79
|
# end
|
|
83
80
|
#
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
#
|
|
81
|
+
# protected
|
|
82
|
+
# def deny_access
|
|
83
|
+
# ...
|
|
84
|
+
# end
|
|
85
|
+
#
|
|
86
|
+
# def show_errors(exception)
|
|
87
|
+
# exception.record.new_record? ? ...
|
|
88
|
+
# end
|
|
89
|
+
# end
|
|
88
90
|
def rescue_from(*klasses, &block)
|
|
89
91
|
options = klasses.extract_options!
|
|
90
92
|
unless options.has_key?(:with)
|
|
@@ -153,7 +155,7 @@ module ActionController #:nodoc:
|
|
|
153
155
|
# If the file doesn't exist, the body of the response will be left empty.
|
|
154
156
|
def render_optional_error_file(status_code)
|
|
155
157
|
status = interpret_status(status_code)
|
|
156
|
-
path = "#{
|
|
158
|
+
path = "#{Rails.public_path}/#{status[0,3]}.html"
|
|
157
159
|
if File.exist?(path)
|
|
158
160
|
render :file => path, :status => status
|
|
159
161
|
else
|
|
@@ -165,7 +167,7 @@ module ActionController #:nodoc:
|
|
|
165
167
|
# method if you wish to redefine the meaning of a local request to
|
|
166
168
|
# include remote IP addresses or other criteria.
|
|
167
169
|
def local_request? #:doc:
|
|
168
|
-
request.remote_addr == LOCALHOST
|
|
170
|
+
request.remote_addr == LOCALHOST && request.remote_ip == LOCALHOST
|
|
169
171
|
end
|
|
170
172
|
|
|
171
173
|
# Render detailed diagnostics for unhandled exceptions rescued from
|
|
@@ -197,10 +199,8 @@ module ActionController #:nodoc:
|
|
|
197
199
|
private
|
|
198
200
|
def perform_action_with_rescue #:nodoc:
|
|
199
201
|
perform_action_without_rescue
|
|
200
|
-
rescue Exception => exception
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
rescue_action(exception)
|
|
202
|
+
rescue Exception => exception
|
|
203
|
+
rescue_action_with_handler(exception) || rescue_action(exception)
|
|
204
204
|
end
|
|
205
205
|
|
|
206
206
|
def rescues_path(template_name)
|
|
@@ -44,13 +44,14 @@ module ActionController
|
|
|
44
44
|
module Resources
|
|
45
45
|
class Resource #:nodoc:
|
|
46
46
|
attr_reader :collection_methods, :member_methods, :new_methods
|
|
47
|
-
attr_reader :path_prefix, :name_prefix
|
|
47
|
+
attr_reader :path_prefix, :name_prefix, :path_segment
|
|
48
48
|
attr_reader :plural, :singular
|
|
49
49
|
attr_reader :options
|
|
50
50
|
|
|
51
51
|
def initialize(entities, options)
|
|
52
52
|
@plural ||= entities
|
|
53
53
|
@singular ||= options[:singular] || plural.to_s.singularize
|
|
54
|
+
@path_segment = options.delete(:as) || @plural
|
|
54
55
|
|
|
55
56
|
@options = options
|
|
56
57
|
|
|
@@ -75,11 +76,13 @@ module ActionController
|
|
|
75
76
|
end
|
|
76
77
|
|
|
77
78
|
def path
|
|
78
|
-
@path ||= "#{path_prefix}/#{
|
|
79
|
+
@path ||= "#{path_prefix}/#{path_segment}"
|
|
79
80
|
end
|
|
80
81
|
|
|
81
82
|
def new_path
|
|
82
|
-
|
|
83
|
+
new_action = self.options[:path_names][:new] if self.options[:path_names]
|
|
84
|
+
new_action ||= Base.resources_path_names[:new]
|
|
85
|
+
@new_path ||= "#{path}/#{new_action}"
|
|
83
86
|
end
|
|
84
87
|
|
|
85
88
|
def member_path
|
|
@@ -188,7 +191,7 @@ module ActionController
|
|
|
188
191
|
# end
|
|
189
192
|
# end
|
|
190
193
|
#
|
|
191
|
-
# Along with the routes themselves,
|
|
194
|
+
# Along with the routes themselves, +resources+ generates named routes for use in
|
|
192
195
|
# controllers and views. <tt>map.resources :messages</tt> produces the following named routes and helpers:
|
|
193
196
|
#
|
|
194
197
|
# Named Route Helpers
|
|
@@ -205,7 +208,7 @@ module ActionController
|
|
|
205
208
|
# edit_message edit_message_url(id), hash_for_edit_message_url(id),
|
|
206
209
|
# edit_message_path(id), hash_for_edit_message_path(id)
|
|
207
210
|
#
|
|
208
|
-
# You can use these helpers instead of
|
|
211
|
+
# You can use these helpers instead of +url_for+ or methods that take +url_for+ parameters. For example:
|
|
209
212
|
#
|
|
210
213
|
# redirect_to :controller => 'messages', :action => 'index'
|
|
211
214
|
# # and
|
|
@@ -226,17 +229,53 @@ module ActionController
|
|
|
226
229
|
#
|
|
227
230
|
# <% form_for :message, @message, :url => message_path(@message), :html => {:method => :put} do |f| %>
|
|
228
231
|
#
|
|
229
|
-
#
|
|
230
|
-
#
|
|
232
|
+
# or
|
|
233
|
+
#
|
|
234
|
+
# <% form_for @message do |f| %>
|
|
235
|
+
#
|
|
236
|
+
# which takes into account whether <tt>@message</tt> is a new record or not and generates the
|
|
237
|
+
# path and method accordingly.
|
|
238
|
+
#
|
|
239
|
+
# The +resources+ method accepts the following options to customize the resulting routes:
|
|
240
|
+
# * <tt>:collection</tt> - Add named routes for other actions that operate on the collection.
|
|
231
241
|
# Takes a hash of <tt>#{action} => #{method}</tt>, where method is <tt>:get</tt>/<tt>:post</tt>/<tt>:put</tt>/<tt>:delete</tt>
|
|
232
|
-
# or <tt>:any</tt> if the method does not matter. These routes map to a URL like /messages/rss, with a route of rss_messages_url
|
|
233
|
-
# * <tt>:member</tt> -
|
|
234
|
-
# * <tt>:new</tt> -
|
|
235
|
-
# * <tt>:controller</tt> -
|
|
236
|
-
# * <tt>:singular</tt> -
|
|
237
|
-
# * <tt>:requirements</tt> -
|
|
238
|
-
# * <tt>:conditions</tt> -
|
|
239
|
-
# * <tt>:
|
|
242
|
+
# or <tt>:any</tt> if the method does not matter. These routes map to a URL like /messages/rss, with a route of +rss_messages_url+.
|
|
243
|
+
# * <tt>:member</tt> - Same as <tt>:collection</tt>, but for actions that operate on a specific member.
|
|
244
|
+
# * <tt>:new</tt> - Same as <tt>:collection</tt>, but for actions that operate on the new resource action.
|
|
245
|
+
# * <tt>:controller</tt> - Specify the controller name for the routes.
|
|
246
|
+
# * <tt>:singular</tt> - Specify the singular name used in the member routes.
|
|
247
|
+
# * <tt>:requirements</tt> - Set custom routing parameter requirements.
|
|
248
|
+
# * <tt>:conditions</tt> - Specify custom routing recognition conditions. Resources sets the <tt>:method</tt> value for the method-specific routes.
|
|
249
|
+
# * <tt>:as</tt> - Specify a different resource name to use in the URL path. For example:
|
|
250
|
+
# # products_path == '/productos'
|
|
251
|
+
# map.resources :products, :as => 'productos' do |product|
|
|
252
|
+
# # product_reviews_path(product) == '/productos/1234/comentarios'
|
|
253
|
+
# product.resources :product_reviews, :as => 'comentarios'
|
|
254
|
+
# end
|
|
255
|
+
#
|
|
256
|
+
# * <tt>:has_one</tt> - Specify nested resources, this is a shorthand for mapping singleton resources beneath the current.
|
|
257
|
+
# * <tt>:has_many</tt> - Same has <tt>:has_one</tt>, but for plural resources.
|
|
258
|
+
#
|
|
259
|
+
# You may directly specify the routing association with +has_one+ and +has_many+ like:
|
|
260
|
+
#
|
|
261
|
+
# map.resources :notes, :has_one => :author, :has_many => [:comments, :attachments]
|
|
262
|
+
#
|
|
263
|
+
# This is the same as:
|
|
264
|
+
#
|
|
265
|
+
# map.resources :notes do |notes|
|
|
266
|
+
# notes.resource :author
|
|
267
|
+
# notes.resources :comments
|
|
268
|
+
# notes.resources :attachments
|
|
269
|
+
# end
|
|
270
|
+
#
|
|
271
|
+
# * <tt>:path_names</tt> - Specify different names for the 'new' and 'edit' actions. For example:
|
|
272
|
+
# # new_products_path == '/productos/nuevo'
|
|
273
|
+
# map.resources :products, :as => 'productos', :path_names => { :new => 'nuevo', :edit => 'editar' }
|
|
274
|
+
#
|
|
275
|
+
# You can also set default action names from an environment, like this:
|
|
276
|
+
# config.action_controller.resources_path_names = { :new => 'nuevo', :edit => 'editar' }
|
|
277
|
+
#
|
|
278
|
+
# * <tt>:path_prefix</tt> - Set a prefix to the routes with required route variables.
|
|
240
279
|
#
|
|
241
280
|
# Weblog comments usually belong to a post, so you might use resources like:
|
|
242
281
|
#
|
|
@@ -249,7 +288,7 @@ module ActionController
|
|
|
249
288
|
# article.resources :comments
|
|
250
289
|
# end
|
|
251
290
|
#
|
|
252
|
-
# The comment resources work the same, but must now include a value for
|
|
291
|
+
# The comment resources work the same, but must now include a value for <tt>:article_id</tt>.
|
|
253
292
|
#
|
|
254
293
|
# article_comments_url(@article)
|
|
255
294
|
# article_comment_url(@article, @comment)
|
|
@@ -257,13 +296,13 @@ module ActionController
|
|
|
257
296
|
# article_comments_url(:article_id => @article)
|
|
258
297
|
# article_comment_url(:article_id => @article, :id => @comment)
|
|
259
298
|
#
|
|
260
|
-
# * <tt>:name_prefix</tt> -
|
|
299
|
+
# * <tt>:name_prefix</tt> - Define a prefix for all generated routes, usually ending in an underscore.
|
|
261
300
|
# Use this if you have named routes that may clash.
|
|
262
301
|
#
|
|
263
302
|
# map.resources :tags, :path_prefix => '/books/:book_id', :name_prefix => 'book_'
|
|
264
303
|
# map.resources :tags, :path_prefix => '/toys/:toy_id', :name_prefix => 'toy_'
|
|
265
304
|
#
|
|
266
|
-
# You may also use
|
|
305
|
+
# You may also use <tt>:name_prefix</tt> to override the generic named routes in a nested resource:
|
|
267
306
|
#
|
|
268
307
|
# map.resources :articles do |article|
|
|
269
308
|
# article.resources :comments, :name_prefix => nil
|
|
@@ -304,7 +343,7 @@ module ActionController
|
|
|
304
343
|
# # --> GET /categories/7/messages/1
|
|
305
344
|
# # has named route "category_message"
|
|
306
345
|
#
|
|
307
|
-
# The
|
|
346
|
+
# The +resources+ method sets HTTP method restrictions on the routes it generates. For example, making an
|
|
308
347
|
# HTTP POST on <tt>new_message_url</tt> will raise a RoutingError exception. The default route in
|
|
309
348
|
# <tt>config/routes.rb</tt> overrides this and allows invalid HTTP methods for resource routes.
|
|
310
349
|
def resources(*entities, &block)
|
|
@@ -325,7 +364,7 @@ module ActionController
|
|
|
325
364
|
#
|
|
326
365
|
# See map.resources for general conventions. These are the main differences:
|
|
327
366
|
# * A singular name is given to map.resource. The default controller name is still taken from the plural name.
|
|
328
|
-
# * To specify a custom plural name, use the
|
|
367
|
+
# * To specify a custom plural name, use the <tt>:plural</tt> option. There is no <tt>:singular</tt> option.
|
|
329
368
|
# * No default index route is created for the singleton resource controller.
|
|
330
369
|
# * When nesting singleton resources, only the singular name is used as the path prefix (example: 'account/messages/1')
|
|
331
370
|
#
|
|
@@ -367,7 +406,7 @@ module ActionController
|
|
|
367
406
|
# end
|
|
368
407
|
# end
|
|
369
408
|
#
|
|
370
|
-
# Along with the routes themselves,
|
|
409
|
+
# Along with the routes themselves, +resource+ generates named routes for
|
|
371
410
|
# use in controllers and views. <tt>map.resource :account</tt> produces
|
|
372
411
|
# these named routes and helpers:
|
|
373
412
|
#
|
|
@@ -485,8 +524,12 @@ module ActionController
|
|
|
485
524
|
resource.member_methods.each do |method, actions|
|
|
486
525
|
actions.each do |action|
|
|
487
526
|
action_options = action_options_for(action, resource, method)
|
|
488
|
-
|
|
489
|
-
|
|
527
|
+
|
|
528
|
+
action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
|
|
529
|
+
action_path ||= Base.resources_path_names[action] || action
|
|
530
|
+
|
|
531
|
+
map.named_route("#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}", action_options)
|
|
532
|
+
map.named_route("formatted_#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}.:format",action_options)
|
|
490
533
|
end
|
|
491
534
|
end
|
|
492
535
|
|
|
@@ -30,9 +30,9 @@ module ActionController
|
|
|
30
30
|
|
|
31
31
|
def redirect(to_url, response_status)
|
|
32
32
|
self.headers["Status"] = response_status
|
|
33
|
-
self.headers["Location"] = to_url
|
|
33
|
+
self.headers["Location"] = to_url
|
|
34
34
|
|
|
35
|
-
self.body = "<html><body>You are being <a href=\"#{
|
|
35
|
+
self.body = "<html><body>You are being <a href=\"#{to_url}\">redirected</a>.</body></html>"
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def prepare!
|
|
@@ -1,53 +1,13 @@
|
|
|
1
1
|
require 'cgi'
|
|
2
2
|
require 'uri'
|
|
3
3
|
require 'action_controller/polymorphic_routes'
|
|
4
|
-
require 'action_controller/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class TrueClass
|
|
13
|
-
def to_param
|
|
14
|
-
self
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
class FalseClass
|
|
19
|
-
def to_param
|
|
20
|
-
self
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
class NilClass
|
|
25
|
-
def to_param
|
|
26
|
-
self
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
class Regexp #:nodoc:
|
|
31
|
-
def number_of_captures
|
|
32
|
-
Regexp.new("|#{source}").match('').captures.length
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
class << self
|
|
36
|
-
def optionalize(pattern)
|
|
37
|
-
case unoptionalize(pattern)
|
|
38
|
-
when /\A(.|\(.*\))\Z/ then "#{pattern}?"
|
|
39
|
-
else "(?:#{pattern})?"
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def unoptionalize(pattern)
|
|
44
|
-
[/\A\(\?:(.*)\)\?\Z/, /\A(.|\(.*\))\?\Z/].each do |regexp|
|
|
45
|
-
return $1 if regexp =~ pattern
|
|
46
|
-
end
|
|
47
|
-
return pattern
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|
|
4
|
+
require 'action_controller/routing/optimisations'
|
|
5
|
+
require 'action_controller/routing/routing_ext'
|
|
6
|
+
require 'action_controller/routing/route'
|
|
7
|
+
require 'action_controller/routing/segments'
|
|
8
|
+
require 'action_controller/routing/builder'
|
|
9
|
+
require 'action_controller/routing/route_set'
|
|
10
|
+
require 'action_controller/routing/recognition_optimisation'
|
|
51
11
|
|
|
52
12
|
module ActionController
|
|
53
13
|
# == Routing
|
|
@@ -55,7 +15,7 @@ module ActionController
|
|
|
55
15
|
# The routing module provides URL rewriting in native Ruby. It's a way to
|
|
56
16
|
# redirect incoming requests to controllers and actions. This replaces
|
|
57
17
|
# mod_rewrite rules. Best of all, Rails' Routing works with any web server.
|
|
58
|
-
# Routes are defined in routes.rb
|
|
18
|
+
# Routes are defined in <tt>config/routes.rb</tt>.
|
|
59
19
|
#
|
|
60
20
|
# Consider the following route, installed by Rails when you generate your
|
|
61
21
|
# application:
|
|
@@ -63,7 +23,8 @@ module ActionController
|
|
|
63
23
|
# map.connect ':controller/:action/:id'
|
|
64
24
|
#
|
|
65
25
|
# This route states that it expects requests to consist of a
|
|
66
|
-
#
|
|
26
|
+
# <tt>:controller</tt> followed by an <tt>:action</tt> that in turn is fed
|
|
27
|
+
# some <tt>:id</tt>.
|
|
67
28
|
#
|
|
68
29
|
# Suppose you get an incoming request for <tt>/blog/edit/22</tt>, you'll end up
|
|
69
30
|
# with:
|
|
@@ -76,35 +37,35 @@ module ActionController
|
|
|
76
37
|
# Think of creating routes as drawing a map for your requests. The map tells
|
|
77
38
|
# them where to go based on some predefined pattern:
|
|
78
39
|
#
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
#
|
|
40
|
+
# ActionController::Routing::Routes.draw do |map|
|
|
41
|
+
# Pattern 1 tells some request to go to one place
|
|
42
|
+
# Pattern 2 tell them to go to another
|
|
43
|
+
# ...
|
|
44
|
+
# end
|
|
84
45
|
#
|
|
85
46
|
# The following symbols are special:
|
|
86
47
|
#
|
|
87
48
|
# :controller maps to your controller name
|
|
88
49
|
# :action maps to an action with your controllers
|
|
89
50
|
#
|
|
90
|
-
# Other names simply map to a parameter as in the case of
|
|
51
|
+
# Other names simply map to a parameter as in the case of <tt>:id</tt>.
|
|
91
52
|
#
|
|
92
53
|
# == Route priority
|
|
93
54
|
#
|
|
94
55
|
# Not all routes are created equally. Routes have priority defined by the
|
|
95
|
-
# order of appearance of the routes in the routes.rb file. The priority goes
|
|
56
|
+
# order of appearance of the routes in the <tt>config/routes.rb</tt> file. The priority goes
|
|
96
57
|
# from top to bottom. The last route in that file is at the lowest priority
|
|
97
58
|
# and will be applied last. If no route matches, 404 is returned.
|
|
98
59
|
#
|
|
99
60
|
# Within blocks, the empty pattern is at the highest priority.
|
|
100
61
|
# In practice this works out nicely:
|
|
101
62
|
#
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
#
|
|
106
|
-
#
|
|
107
|
-
#
|
|
63
|
+
# ActionController::Routing::Routes.draw do |map|
|
|
64
|
+
# map.with_options :controller => 'blog' do |blog|
|
|
65
|
+
# blog.show '', :action => 'list'
|
|
66
|
+
# end
|
|
67
|
+
# map.connect ':controller/:action/:view'
|
|
68
|
+
# end
|
|
108
69
|
#
|
|
109
70
|
# In this case, invoking blog controller (with an URL like '/blog/')
|
|
110
71
|
# without parameters will activate the 'list' action by default.
|
|
@@ -115,14 +76,15 @@ module ActionController
|
|
|
115
76
|
# Hash at the end of your mapping to set any default parameters.
|
|
116
77
|
#
|
|
117
78
|
# Example:
|
|
118
|
-
#
|
|
119
|
-
#
|
|
120
|
-
#
|
|
79
|
+
#
|
|
80
|
+
# ActionController::Routing:Routes.draw do |map|
|
|
81
|
+
# map.connect ':controller/:action/:id', :controller => 'blog'
|
|
82
|
+
# end
|
|
121
83
|
#
|
|
122
84
|
# This sets up +blog+ as the default controller if no other is specified.
|
|
123
85
|
# This means visiting '/' would invoke the blog controller.
|
|
124
86
|
#
|
|
125
|
-
# More formally, you can define defaults in a route with the
|
|
87
|
+
# More formally, you can define defaults in a route with the <tt>:defaults</tt> key.
|
|
126
88
|
#
|
|
127
89
|
# map.connect ':controller/:action/:id', :action => 'show', :defaults => { :page => 'Dashboard' }
|
|
128
90
|
#
|
|
@@ -133,6 +95,7 @@ module ActionController
|
|
|
133
95
|
# for the full URL and +name_of_route_path+ for the URI path.
|
|
134
96
|
#
|
|
135
97
|
# Example:
|
|
98
|
+
#
|
|
136
99
|
# # In routes.rb
|
|
137
100
|
# map.login 'login', :controller => 'accounts', :action => 'login'
|
|
138
101
|
#
|
|
@@ -155,6 +118,12 @@ module ActionController
|
|
|
155
118
|
# root_url # => 'http://www.example.com/'
|
|
156
119
|
# root_path # => ''
|
|
157
120
|
#
|
|
121
|
+
# You can also specify an already-defined named route in your <tt>map.root</tt> call:
|
|
122
|
+
#
|
|
123
|
+
# # In routes.rb
|
|
124
|
+
# map.new_session :controller => 'sessions', :action => 'new'
|
|
125
|
+
# map.root :new_session
|
|
126
|
+
#
|
|
158
127
|
# Note: when using +with_options+, the route is simply named after the
|
|
159
128
|
# method you call on the block parameter rather than map.
|
|
160
129
|
#
|
|
@@ -172,33 +141,52 @@ module ActionController
|
|
|
172
141
|
#
|
|
173
142
|
# Routes can generate pretty URLs. For example:
|
|
174
143
|
#
|
|
175
|
-
#
|
|
176
|
-
#
|
|
177
|
-
#
|
|
178
|
-
#
|
|
179
|
-
#
|
|
180
|
-
#
|
|
144
|
+
# map.connect 'articles/:year/:month/:day',
|
|
145
|
+
# :controller => 'articles',
|
|
146
|
+
# :action => 'find_by_date',
|
|
147
|
+
# :year => /\d{4}/,
|
|
148
|
+
# :month => /\d{1,2}/,
|
|
149
|
+
# :day => /\d{1,2}/
|
|
181
150
|
#
|
|
182
|
-
#
|
|
183
|
-
#
|
|
184
|
-
#
|
|
151
|
+
# Using the route above, the URL "http://localhost:3000/articles/2005/11/06"
|
|
152
|
+
# maps to
|
|
153
|
+
#
|
|
154
|
+
# params = {:year => '2005', :month => '11', :day => '06'}
|
|
185
155
|
#
|
|
186
156
|
# == Regular Expressions and parameters
|
|
187
157
|
# You can specify a regular expression to define a format for a parameter.
|
|
188
158
|
#
|
|
189
|
-
#
|
|
190
|
-
#
|
|
159
|
+
# map.geocode 'geocode/:postalcode', :controller => 'geocode',
|
|
160
|
+
# :action => 'show', :postalcode => /\d{5}(-\d{4})?/
|
|
191
161
|
#
|
|
192
162
|
# or, more formally:
|
|
193
163
|
#
|
|
194
164
|
# map.geocode 'geocode/:postalcode', :controller => 'geocode',
|
|
195
165
|
# :action => 'show', :requirements => { :postalcode => /\d{5}(-\d{4})?/ }
|
|
196
166
|
#
|
|
167
|
+
# Formats can include the 'ignorecase' and 'extended syntax' regular
|
|
168
|
+
# expression modifiers:
|
|
169
|
+
#
|
|
170
|
+
# map.geocode 'geocode/:postalcode', :controller => 'geocode',
|
|
171
|
+
# :action => 'show', :postalcode => /hx\d\d\s\d[a-z]{2}/i
|
|
172
|
+
#
|
|
173
|
+
# map.geocode 'geocode/:postalcode', :controller => 'geocode',
|
|
174
|
+
# :action => 'show',:requirements => {
|
|
175
|
+
# :postalcode => /# Postcode format
|
|
176
|
+
# \d{5} #Prefix
|
|
177
|
+
# (-\d{4})? #Suffix
|
|
178
|
+
# /x
|
|
179
|
+
# }
|
|
180
|
+
#
|
|
181
|
+
# Using the multiline match modifier will raise an ArgumentError.
|
|
182
|
+
# Encoding regular expression modifiers are silently ignored. The
|
|
183
|
+
# match will always use the default encoding or ASCII.
|
|
184
|
+
#
|
|
197
185
|
# == Route globbing
|
|
198
186
|
#
|
|
199
187
|
# Specifying <tt>*[string]</tt> as part of a rule like:
|
|
200
188
|
#
|
|
201
|
-
#
|
|
189
|
+
# map.connect '*path' , :controller => 'blog' , :action => 'unrecognized?'
|
|
202
190
|
#
|
|
203
191
|
# will glob all remaining parts of the route that were not recognized earlier. This idiom
|
|
204
192
|
# must appear at the end of the path. The globbed values are in <tt>params[:path]</tt> in
|
|
@@ -226,10 +214,10 @@ module ActionController
|
|
|
226
214
|
#
|
|
227
215
|
# You can reload routes if you feel you must:
|
|
228
216
|
#
|
|
229
|
-
#
|
|
217
|
+
# ActionController::Routing::Routes.reload
|
|
230
218
|
#
|
|
231
219
|
# 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
|
|
220
|
+
# last load. To absolutely force reloading, use <tt>reload!</tt>.
|
|
233
221
|
#
|
|
234
222
|
# == Testing Routes
|
|
235
223
|
#
|
|
@@ -237,19 +225,19 @@ module ActionController
|
|
|
237
225
|
#
|
|
238
226
|
# === +assert_routing+
|
|
239
227
|
#
|
|
240
|
-
#
|
|
241
|
-
#
|
|
242
|
-
#
|
|
243
|
-
#
|
|
228
|
+
# def test_movie_route_properly_splits
|
|
229
|
+
# opts = {:controller => "plugin", :action => "checkout", :id => "2"}
|
|
230
|
+
# assert_routing "plugin/checkout/2", opts
|
|
231
|
+
# end
|
|
244
232
|
#
|
|
245
233
|
# +assert_routing+ lets you test whether or not the route properly resolves into options.
|
|
246
234
|
#
|
|
247
235
|
# === +assert_recognizes+
|
|
248
236
|
#
|
|
249
|
-
#
|
|
250
|
-
#
|
|
251
|
-
#
|
|
252
|
-
#
|
|
237
|
+
# def test_route_has_options
|
|
238
|
+
# opts = {:controller => "plugin", :action => "show", :id => "12"}
|
|
239
|
+
# assert_recognizes opts, "/plugins/show/12"
|
|
240
|
+
# end
|
|
253
241
|
#
|
|
254
242
|
# Note the subtle difference between the two: +assert_routing+ tests that
|
|
255
243
|
# a URL fits options while +assert_recognizes+ tests that a URL
|
|
@@ -257,16 +245,16 @@ module ActionController
|
|
|
257
245
|
#
|
|
258
246
|
# In tests you can simply pass the URL or named route to +get+ or +post+.
|
|
259
247
|
#
|
|
260
|
-
#
|
|
261
|
-
#
|
|
262
|
-
#
|
|
263
|
-
#
|
|
264
|
-
#
|
|
248
|
+
# def send_to_jail
|
|
249
|
+
# get '/jail'
|
|
250
|
+
# assert_response :success
|
|
251
|
+
# assert_template "jail/front"
|
|
252
|
+
# end
|
|
265
253
|
#
|
|
266
|
-
#
|
|
267
|
-
#
|
|
268
|
-
#
|
|
269
|
-
#
|
|
254
|
+
# def goes_to_login
|
|
255
|
+
# get login_url
|
|
256
|
+
# #...
|
|
257
|
+
# end
|
|
270
258
|
#
|
|
271
259
|
# == View a list of all your routes
|
|
272
260
|
#
|
|
@@ -289,6 +277,9 @@ module ActionController
|
|
|
289
277
|
end
|
|
290
278
|
|
|
291
279
|
class << self
|
|
280
|
+
# Expects an array of controller names as the first argument.
|
|
281
|
+
# Executes the passed block with only the named controllers named available.
|
|
282
|
+
# This method is used in internal Rails testing.
|
|
292
283
|
def with_controllers(names)
|
|
293
284
|
prior_controllers = @possible_controllers
|
|
294
285
|
use_controllers! names
|
|
@@ -297,6 +288,10 @@ module ActionController
|
|
|
297
288
|
use_controllers! prior_controllers
|
|
298
289
|
end
|
|
299
290
|
|
|
291
|
+
# Returns an array of paths, cleaned of double-slashes and relative path references.
|
|
292
|
+
# * "\\\" and "//" become "\\" or "/".
|
|
293
|
+
# * "/foo/bar/../config" becomes "/foo/config".
|
|
294
|
+
# The returned array is sorted by length, descending.
|
|
300
295
|
def normalize_paths(paths)
|
|
301
296
|
# do the hokey-pokey of path normalization...
|
|
302
297
|
paths = paths.collect do |path|
|
|
@@ -315,6 +310,7 @@ module ActionController
|
|
|
315
310
|
paths = paths.uniq.sort_by { |path| - path.length }
|
|
316
311
|
end
|
|
317
312
|
|
|
313
|
+
# Returns the array of controller names currently available to ActionController::Routing.
|
|
318
314
|
def possible_controllers
|
|
319
315
|
unless @possible_controllers
|
|
320
316
|
@possible_controllers = []
|
|
@@ -339,10 +335,28 @@ module ActionController
|
|
|
339
335
|
@possible_controllers
|
|
340
336
|
end
|
|
341
337
|
|
|
338
|
+
# Replaces the internal list of controllers available to ActionController::Routing with the passed argument.
|
|
339
|
+
# ActionController::Routing.use_controllers!([ "posts", "comments", "admin/comments" ])
|
|
342
340
|
def use_controllers!(controller_names)
|
|
343
341
|
@possible_controllers = controller_names
|
|
344
342
|
end
|
|
345
343
|
|
|
344
|
+
# Returns a controller path for a new +controller+ based on a +previous+ controller path.
|
|
345
|
+
# Handles 4 scenarios:
|
|
346
|
+
#
|
|
347
|
+
# * stay in the previous controller:
|
|
348
|
+
# controller_relative_to( nil, "groups/discussion" ) # => "groups/discussion"
|
|
349
|
+
#
|
|
350
|
+
# * stay in the previous namespace:
|
|
351
|
+
# controller_relative_to( "posts", "groups/discussion" ) # => "groups/posts"
|
|
352
|
+
#
|
|
353
|
+
# * forced move to the root namespace:
|
|
354
|
+
# controller_relative_to( "/posts", "groups/discussion" ) # => "posts"
|
|
355
|
+
#
|
|
356
|
+
# * previous namespace is root:
|
|
357
|
+
# controller_relative_to( "posts", "anything_with_no_slashes" ) # =>"posts"
|
|
358
|
+
#
|
|
359
|
+
|
|
346
360
|
def controller_relative_to(controller, previous)
|
|
347
361
|
if controller.nil? then previous
|
|
348
362
|
elsif controller[0] == ?/ then controller[1..-1]
|
|
@@ -351,1142 +365,12 @@ module ActionController
|
|
|
351
365
|
end
|
|
352
366
|
end
|
|
353
367
|
end
|
|
354
|
-
|
|
355
|
-
class Route #:nodoc:
|
|
356
|
-
attr_accessor :segments, :requirements, :conditions, :optimise
|
|
357
|
-
|
|
358
|
-
def initialize
|
|
359
|
-
@segments = []
|
|
360
|
-
@requirements = {}
|
|
361
|
-
@conditions = {}
|
|
362
|
-
@optimise = true
|
|
363
|
-
end
|
|
364
|
-
|
|
365
|
-
# Indicates whether the routes should be optimised with the string interpolation
|
|
366
|
-
# version of the named routes methods.
|
|
367
|
-
def optimise?
|
|
368
|
-
@optimise && ActionController::Base::optimise_named_routes
|
|
369
|
-
end
|
|
370
|
-
|
|
371
|
-
def segment_keys
|
|
372
|
-
segments.collect do |segment|
|
|
373
|
-
segment.key if segment.respond_to? :key
|
|
374
|
-
end.compact
|
|
375
|
-
end
|
|
376
|
-
|
|
377
|
-
# Write and compile a +generate+ method for this Route.
|
|
378
|
-
def write_generation
|
|
379
|
-
# Build the main body of the generation
|
|
380
|
-
body = "expired = false\n#{generation_extraction}\n#{generation_structure}"
|
|
381
|
-
|
|
382
|
-
# If we have conditions that must be tested first, nest the body inside an if
|
|
383
|
-
body = "if #{generation_requirements}\n#{body}\nend" if generation_requirements
|
|
384
|
-
args = "options, hash, expire_on = {}"
|
|
385
|
-
|
|
386
|
-
# Nest the body inside of a def block, and then compile it.
|
|
387
|
-
raw_method = method_decl = "def generate_raw(#{args})\npath = begin\n#{body}\nend\n[path, hash]\nend"
|
|
388
|
-
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
|
|
389
|
-
|
|
390
|
-
# expire_on.keys == recall.keys; in other words, the keys in the expire_on hash
|
|
391
|
-
# are the same as the keys that were recalled from the previous request. Thus,
|
|
392
|
-
# we can use the expire_on.keys to determine which keys ought to be used to build
|
|
393
|
-
# the query string. (Never use keys from the recalled request when building the
|
|
394
|
-
# query string.)
|
|
395
|
-
|
|
396
|
-
method_decl = "def generate(#{args})\npath, hash = generate_raw(options, hash, expire_on)\nappend_query_string(path, hash, extra_keys(options))\nend"
|
|
397
|
-
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
|
|
398
|
-
|
|
399
|
-
method_decl = "def generate_extras(#{args})\npath, hash = generate_raw(options, hash, expire_on)\n[path, extra_keys(options)]\nend"
|
|
400
|
-
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
|
|
401
|
-
raw_method
|
|
402
|
-
end
|
|
403
|
-
|
|
404
|
-
# Build several lines of code that extract values from the options hash. If any
|
|
405
|
-
# of the values are missing or rejected then a return will be executed.
|
|
406
|
-
def generation_extraction
|
|
407
|
-
segments.collect do |segment|
|
|
408
|
-
segment.extraction_code
|
|
409
|
-
end.compact * "\n"
|
|
410
|
-
end
|
|
411
|
-
|
|
412
|
-
# Produce a condition expression that will check the requirements of this route
|
|
413
|
-
# upon generation.
|
|
414
|
-
def generation_requirements
|
|
415
|
-
requirement_conditions = requirements.collect do |key, req|
|
|
416
|
-
if req.is_a? Regexp
|
|
417
|
-
value_regexp = Regexp.new "\\A#{req.source}\\Z"
|
|
418
|
-
"hash[:#{key}] && #{value_regexp.inspect} =~ options[:#{key}]"
|
|
419
|
-
else
|
|
420
|
-
"hash[:#{key}] == #{req.inspect}"
|
|
421
|
-
end
|
|
422
|
-
end
|
|
423
|
-
requirement_conditions * ' && ' unless requirement_conditions.empty?
|
|
424
|
-
end
|
|
425
|
-
|
|
426
|
-
def generation_structure
|
|
427
|
-
segments.last.string_structure segments[0..-2]
|
|
428
|
-
end
|
|
429
|
-
|
|
430
|
-
# Write and compile a +recognize+ method for this Route.
|
|
431
|
-
def write_recognition
|
|
432
|
-
# Create an if structure to extract the params from a match if it occurs.
|
|
433
|
-
body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams"
|
|
434
|
-
body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend"
|
|
435
|
-
|
|
436
|
-
# Build the method declaration and compile it
|
|
437
|
-
method_decl = "def recognize(path, env={})\n#{body}\nend"
|
|
438
|
-
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
|
|
439
|
-
method_decl
|
|
440
|
-
end
|
|
441
|
-
|
|
442
|
-
# Plugins may override this method to add other conditions, like checks on
|
|
443
|
-
# host, subdomain, and so forth. Note that changes here only affect route
|
|
444
|
-
# recognition, not generation.
|
|
445
|
-
def recognition_conditions
|
|
446
|
-
result = ["(match = #{Regexp.new(recognition_pattern).inspect}.match(path))"]
|
|
447
|
-
result << "conditions[:method] === env[:method]" if conditions[:method]
|
|
448
|
-
result
|
|
449
|
-
end
|
|
450
|
-
|
|
451
|
-
# Build the regular expression pattern that will match this route.
|
|
452
|
-
def recognition_pattern(wrap = true)
|
|
453
|
-
pattern = ''
|
|
454
|
-
segments.reverse_each do |segment|
|
|
455
|
-
pattern = segment.build_pattern pattern
|
|
456
|
-
end
|
|
457
|
-
wrap ? ("\\A" + pattern + "\\Z") : pattern
|
|
458
|
-
end
|
|
459
|
-
|
|
460
|
-
# Write the code to extract the parameters from a matched route.
|
|
461
|
-
def recognition_extraction
|
|
462
|
-
next_capture = 1
|
|
463
|
-
extraction = segments.collect do |segment|
|
|
464
|
-
x = segment.match_extraction(next_capture)
|
|
465
|
-
next_capture += Regexp.new(segment.regexp_chunk).number_of_captures
|
|
466
|
-
x
|
|
467
|
-
end
|
|
468
|
-
extraction.compact
|
|
469
|
-
end
|
|
470
|
-
|
|
471
|
-
# Write the real generation implementation and then resend the message.
|
|
472
|
-
def generate(options, hash, expire_on = {})
|
|
473
|
-
write_generation
|
|
474
|
-
generate options, hash, expire_on
|
|
475
|
-
end
|
|
476
|
-
|
|
477
|
-
def generate_extras(options, hash, expire_on = {})
|
|
478
|
-
write_generation
|
|
479
|
-
generate_extras options, hash, expire_on
|
|
480
|
-
end
|
|
481
|
-
|
|
482
|
-
# Generate the query string with any extra keys in the hash and append
|
|
483
|
-
# it to the given path, returning the new path.
|
|
484
|
-
def append_query_string(path, hash, query_keys=nil)
|
|
485
|
-
return nil unless path
|
|
486
|
-
query_keys ||= extra_keys(hash)
|
|
487
|
-
"#{path}#{build_query_string(hash, query_keys)}"
|
|
488
|
-
end
|
|
489
|
-
|
|
490
|
-
# Determine which keys in the given hash are "extra". Extra keys are
|
|
491
|
-
# those that were not used to generate a particular route. The extra
|
|
492
|
-
# keys also do not include those recalled from the prior request, nor
|
|
493
|
-
# do they include any keys that were implied in the route (like a
|
|
494
|
-
# :controller that is required, but not explicitly used in the text of
|
|
495
|
-
# the route.)
|
|
496
|
-
def extra_keys(hash, recall={})
|
|
497
|
-
(hash || {}).keys.map { |k| k.to_sym } - (recall || {}).keys - significant_keys
|
|
498
|
-
end
|
|
499
|
-
|
|
500
|
-
# Build a query string from the keys of the given hash. If +only_keys+
|
|
501
|
-
# is given (as an array), only the keys indicated will be used to build
|
|
502
|
-
# the query string. The query string will correctly build array parameter
|
|
503
|
-
# values.
|
|
504
|
-
def build_query_string(hash, only_keys = nil)
|
|
505
|
-
elements = []
|
|
506
|
-
|
|
507
|
-
(only_keys || hash.keys).each do |key|
|
|
508
|
-
if value = hash[key]
|
|
509
|
-
elements << value.to_query(key)
|
|
510
|
-
end
|
|
511
|
-
end
|
|
512
|
-
|
|
513
|
-
elements.empty? ? '' : "?#{elements.sort * '&'}"
|
|
514
|
-
end
|
|
515
|
-
|
|
516
|
-
# Write the real recognition implementation and then resend the message.
|
|
517
|
-
def recognize(path, environment={})
|
|
518
|
-
write_recognition
|
|
519
|
-
recognize path, environment
|
|
520
|
-
end
|
|
521
|
-
|
|
522
|
-
# A route's parameter shell contains parameter values that are not in the
|
|
523
|
-
# route's path, but should be placed in the recognized hash.
|
|
524
|
-
#
|
|
525
|
-
# For example, +{:controller => 'pages', :action => 'show'} is the shell for the route:
|
|
526
|
-
#
|
|
527
|
-
# map.connect '/page/:id', :controller => 'pages', :action => 'show', :id => /\d+/
|
|
528
|
-
#
|
|
529
|
-
def parameter_shell
|
|
530
|
-
@parameter_shell ||= returning({}) do |shell|
|
|
531
|
-
requirements.each do |key, requirement|
|
|
532
|
-
shell[key] = requirement unless requirement.is_a? Regexp
|
|
533
|
-
end
|
|
534
|
-
end
|
|
535
|
-
end
|
|
536
|
-
|
|
537
|
-
# Return an array containing all the keys that are used in this route. This
|
|
538
|
-
# includes keys that appear inside the path, and keys that have requirements
|
|
539
|
-
# placed upon them.
|
|
540
|
-
def significant_keys
|
|
541
|
-
@significant_keys ||= returning [] do |sk|
|
|
542
|
-
segments.each { |segment| sk << segment.key if segment.respond_to? :key }
|
|
543
|
-
sk.concat requirements.keys
|
|
544
|
-
sk.uniq!
|
|
545
|
-
end
|
|
546
|
-
end
|
|
547
|
-
|
|
548
|
-
# Return a hash of key/value pairs representing the keys in the route that
|
|
549
|
-
# have defaults, or which are specified by non-regexp requirements.
|
|
550
|
-
def defaults
|
|
551
|
-
@defaults ||= returning({}) do |hash|
|
|
552
|
-
segments.each do |segment|
|
|
553
|
-
next unless segment.respond_to? :default
|
|
554
|
-
hash[segment.key] = segment.default unless segment.default.nil?
|
|
555
|
-
end
|
|
556
|
-
requirements.each do |key,req|
|
|
557
|
-
next if Regexp === req || req.nil?
|
|
558
|
-
hash[key] = req
|
|
559
|
-
end
|
|
560
|
-
end
|
|
561
|
-
end
|
|
562
|
-
|
|
563
|
-
def matches_controller_and_action?(controller, action)
|
|
564
|
-
unless defined? @matching_prepared
|
|
565
|
-
@controller_requirement = requirement_for(:controller)
|
|
566
|
-
@action_requirement = requirement_for(:action)
|
|
567
|
-
@matching_prepared = true
|
|
568
|
-
end
|
|
569
|
-
|
|
570
|
-
(@controller_requirement.nil? || @controller_requirement === controller) &&
|
|
571
|
-
(@action_requirement.nil? || @action_requirement === action)
|
|
572
|
-
end
|
|
573
|
-
|
|
574
|
-
def to_s
|
|
575
|
-
@to_s ||= begin
|
|
576
|
-
segs = segments.inject("") { |str,s| str << s.to_s }
|
|
577
|
-
"%-6s %-40s %s" % [(conditions[:method] || :any).to_s.upcase, segs, requirements.inspect]
|
|
578
|
-
end
|
|
579
|
-
end
|
|
580
|
-
|
|
581
|
-
protected
|
|
582
|
-
def requirement_for(key)
|
|
583
|
-
return requirements[key] if requirements.key? key
|
|
584
|
-
segments.each do |segment|
|
|
585
|
-
return segment.regexp if segment.respond_to?(:key) && segment.key == key
|
|
586
|
-
end
|
|
587
|
-
nil
|
|
588
|
-
end
|
|
589
|
-
|
|
590
|
-
end
|
|
591
|
-
|
|
592
|
-
class Segment #:nodoc:
|
|
593
|
-
RESERVED_PCHAR = ':@&=+$,;'
|
|
594
|
-
UNSAFE_PCHAR = Regexp.new("[^#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}]", false, 'N').freeze
|
|
595
|
-
|
|
596
|
-
attr_accessor :is_optional
|
|
597
|
-
alias_method :optional?, :is_optional
|
|
598
|
-
|
|
599
|
-
def initialize
|
|
600
|
-
self.is_optional = false
|
|
601
|
-
end
|
|
602
|
-
|
|
603
|
-
def extraction_code
|
|
604
|
-
nil
|
|
605
|
-
end
|
|
606
|
-
|
|
607
|
-
# Continue generating string for the prior segments.
|
|
608
|
-
def continue_string_structure(prior_segments)
|
|
609
|
-
if prior_segments.empty?
|
|
610
|
-
interpolation_statement(prior_segments)
|
|
611
|
-
else
|
|
612
|
-
new_priors = prior_segments[0..-2]
|
|
613
|
-
prior_segments.last.string_structure(new_priors)
|
|
614
|
-
end
|
|
615
|
-
end
|
|
616
|
-
|
|
617
|
-
def interpolation_chunk
|
|
618
|
-
URI.escape(value, UNSAFE_PCHAR)
|
|
619
|
-
end
|
|
620
|
-
|
|
621
|
-
# Return a string interpolation statement for this segment and those before it.
|
|
622
|
-
def interpolation_statement(prior_segments)
|
|
623
|
-
chunks = prior_segments.collect { |s| s.interpolation_chunk }
|
|
624
|
-
chunks << interpolation_chunk
|
|
625
|
-
"\"#{chunks * ''}\"#{all_optionals_available_condition(prior_segments)}"
|
|
626
|
-
end
|
|
627
|
-
|
|
628
|
-
def string_structure(prior_segments)
|
|
629
|
-
optional? ? continue_string_structure(prior_segments) : interpolation_statement(prior_segments)
|
|
630
|
-
end
|
|
631
|
-
|
|
632
|
-
# Return an if condition that is true if all the prior segments can be generated.
|
|
633
|
-
# If there are no optional segments before this one, then nil is returned.
|
|
634
|
-
def all_optionals_available_condition(prior_segments)
|
|
635
|
-
optional_locals = prior_segments.collect { |s| s.local_name if s.optional? && s.respond_to?(:local_name) }.compact
|
|
636
|
-
optional_locals.empty? ? nil : " if #{optional_locals * ' && '}"
|
|
637
|
-
end
|
|
638
|
-
|
|
639
|
-
# Recognition
|
|
640
|
-
|
|
641
|
-
def match_extraction(next_capture)
|
|
642
|
-
nil
|
|
643
|
-
end
|
|
644
|
-
|
|
645
|
-
# Warning
|
|
646
|
-
|
|
647
|
-
# Returns true if this segment is optional? because of a default. If so, then
|
|
648
|
-
# no warning will be emitted regarding this segment.
|
|
649
|
-
def optionality_implied?
|
|
650
|
-
false
|
|
651
|
-
end
|
|
652
|
-
end
|
|
653
|
-
|
|
654
|
-
class StaticSegment < Segment #:nodoc:
|
|
655
|
-
attr_accessor :value, :raw
|
|
656
|
-
alias_method :raw?, :raw
|
|
657
|
-
|
|
658
|
-
def initialize(value = nil)
|
|
659
|
-
super()
|
|
660
|
-
self.value = value
|
|
661
|
-
end
|
|
662
|
-
|
|
663
|
-
def interpolation_chunk
|
|
664
|
-
raw? ? value : super
|
|
665
|
-
end
|
|
666
|
-
|
|
667
|
-
def regexp_chunk
|
|
668
|
-
chunk = Regexp.escape(value)
|
|
669
|
-
optional? ? Regexp.optionalize(chunk) : chunk
|
|
670
|
-
end
|
|
671
|
-
|
|
672
|
-
def build_pattern(pattern)
|
|
673
|
-
escaped = Regexp.escape(value)
|
|
674
|
-
if optional? && ! pattern.empty?
|
|
675
|
-
"(?:#{Regexp.optionalize escaped}\\Z|#{escaped}#{Regexp.unoptionalize pattern})"
|
|
676
|
-
elsif optional?
|
|
677
|
-
Regexp.optionalize escaped
|
|
678
|
-
else
|
|
679
|
-
escaped + pattern
|
|
680
|
-
end
|
|
681
|
-
end
|
|
682
|
-
|
|
683
|
-
def to_s
|
|
684
|
-
value
|
|
685
|
-
end
|
|
686
|
-
end
|
|
687
|
-
|
|
688
|
-
class DividerSegment < StaticSegment #:nodoc:
|
|
689
|
-
def initialize(value = nil)
|
|
690
|
-
super(value)
|
|
691
|
-
self.raw = true
|
|
692
|
-
self.is_optional = true
|
|
693
|
-
end
|
|
694
|
-
|
|
695
|
-
def optionality_implied?
|
|
696
|
-
true
|
|
697
|
-
end
|
|
698
|
-
end
|
|
699
|
-
|
|
700
|
-
class DynamicSegment < Segment #:nodoc:
|
|
701
|
-
attr_accessor :key, :default, :regexp
|
|
702
|
-
|
|
703
|
-
def initialize(key = nil, options = {})
|
|
704
|
-
super()
|
|
705
|
-
self.key = key
|
|
706
|
-
self.default = options[:default] if options.key? :default
|
|
707
|
-
self.is_optional = true if options[:optional] || options.key?(:default)
|
|
708
|
-
end
|
|
709
|
-
|
|
710
|
-
def to_s
|
|
711
|
-
":#{key}"
|
|
712
|
-
end
|
|
713
|
-
|
|
714
|
-
# The local variable name that the value of this segment will be extracted to.
|
|
715
|
-
def local_name
|
|
716
|
-
"#{key}_value"
|
|
717
|
-
end
|
|
718
|
-
|
|
719
|
-
def extract_value
|
|
720
|
-
"#{local_name} = hash[:#{key}] && hash[:#{key}].to_param #{"|| #{default.inspect}" if default}"
|
|
721
|
-
end
|
|
722
|
-
def value_check
|
|
723
|
-
if default # Then we know it won't be nil
|
|
724
|
-
"#{value_regexp.inspect} =~ #{local_name}" if regexp
|
|
725
|
-
elsif optional?
|
|
726
|
-
# If we have a regexp check that the value is not given, or that it matches.
|
|
727
|
-
# If we have no regexp, return nil since we do not require a condition.
|
|
728
|
-
"#{local_name}.nil? || #{value_regexp.inspect} =~ #{local_name}" if regexp
|
|
729
|
-
else # Then it must be present, and if we have a regexp, it must match too.
|
|
730
|
-
"#{local_name} #{"&& #{value_regexp.inspect} =~ #{local_name}" if regexp}"
|
|
731
|
-
end
|
|
732
|
-
end
|
|
733
|
-
def expiry_statement
|
|
734
|
-
"expired, hash = true, options if !expired && expire_on[:#{key}]"
|
|
735
|
-
end
|
|
736
|
-
|
|
737
|
-
def extraction_code
|
|
738
|
-
s = extract_value
|
|
739
|
-
vc = value_check
|
|
740
|
-
s << "\nreturn [nil,nil] unless #{vc}" if vc
|
|
741
|
-
s << "\n#{expiry_statement}"
|
|
742
|
-
end
|
|
743
|
-
|
|
744
|
-
def interpolation_chunk(value_code = "#{local_name}")
|
|
745
|
-
"\#{URI.escape(#{value_code}.to_s, ActionController::Routing::Segment::UNSAFE_PCHAR)}"
|
|
746
|
-
end
|
|
747
|
-
|
|
748
|
-
def string_structure(prior_segments)
|
|
749
|
-
if optional? # We have a conditional to do...
|
|
750
|
-
# If we should not appear in the url, just write the code for the prior
|
|
751
|
-
# segments. This occurs if our value is the default value, or, if we are
|
|
752
|
-
# optional, if we have nil as our value.
|
|
753
|
-
"if #{local_name} == #{default.inspect}\n" +
|
|
754
|
-
continue_string_structure(prior_segments) +
|
|
755
|
-
"\nelse\n" + # Otherwise, write the code up to here
|
|
756
|
-
"#{interpolation_statement(prior_segments)}\nend"
|
|
757
|
-
else
|
|
758
|
-
interpolation_statement(prior_segments)
|
|
759
|
-
end
|
|
760
|
-
end
|
|
761
|
-
|
|
762
|
-
def value_regexp
|
|
763
|
-
Regexp.new "\\A#{regexp.source}\\Z" if regexp
|
|
764
|
-
end
|
|
765
|
-
def regexp_chunk
|
|
766
|
-
regexp ? "(#{regexp.source})" : "([^#{Routing::SEPARATORS.join}]+)"
|
|
767
|
-
end
|
|
768
|
-
|
|
769
|
-
def build_pattern(pattern)
|
|
770
|
-
chunk = regexp_chunk
|
|
771
|
-
chunk = "(#{chunk})" if Regexp.new(chunk).number_of_captures == 0
|
|
772
|
-
pattern = "#{chunk}#{pattern}"
|
|
773
|
-
optional? ? Regexp.optionalize(pattern) : pattern
|
|
774
|
-
end
|
|
775
|
-
def match_extraction(next_capture)
|
|
776
|
-
# All non code-related keys (such as :id, :slug) are URI-unescaped as
|
|
777
|
-
# path parameters.
|
|
778
|
-
default_value = default ? default.inspect : nil
|
|
779
|
-
%[
|
|
780
|
-
value = if (m = match[#{next_capture}])
|
|
781
|
-
URI.unescape(m)
|
|
782
|
-
else
|
|
783
|
-
#{default_value}
|
|
784
|
-
end
|
|
785
|
-
params[:#{key}] = value if value
|
|
786
|
-
]
|
|
787
|
-
end
|
|
788
|
-
|
|
789
|
-
def optionality_implied?
|
|
790
|
-
[:action, :id].include? key
|
|
791
|
-
end
|
|
792
|
-
|
|
793
|
-
end
|
|
794
|
-
|
|
795
|
-
class ControllerSegment < DynamicSegment #:nodoc:
|
|
796
|
-
def regexp_chunk
|
|
797
|
-
possible_names = Routing.possible_controllers.collect { |name| Regexp.escape name }
|
|
798
|
-
"(?i-:(#{(regexp || Regexp.union(*possible_names)).source}))"
|
|
799
|
-
end
|
|
800
|
-
|
|
801
|
-
# Don't URI.escape the controller name since it may contain slashes.
|
|
802
|
-
def interpolation_chunk(value_code = "#{local_name}")
|
|
803
|
-
"\#{#{value_code}.to_s}"
|
|
804
|
-
end
|
|
805
|
-
|
|
806
|
-
# Make sure controller names like Admin/Content are correctly normalized to
|
|
807
|
-
# admin/content
|
|
808
|
-
def extract_value
|
|
809
|
-
"#{local_name} = (hash[:#{key}] #{"|| #{default.inspect}" if default}).downcase"
|
|
810
|
-
end
|
|
811
|
-
|
|
812
|
-
def match_extraction(next_capture)
|
|
813
|
-
if default
|
|
814
|
-
"params[:#{key}] = match[#{next_capture}] ? match[#{next_capture}].downcase : '#{default}'"
|
|
815
|
-
else
|
|
816
|
-
"params[:#{key}] = match[#{next_capture}].downcase if match[#{next_capture}]"
|
|
817
|
-
end
|
|
818
|
-
end
|
|
819
|
-
end
|
|
820
|
-
|
|
821
|
-
class PathSegment < DynamicSegment #:nodoc:
|
|
822
|
-
RESERVED_PCHAR = "#{Segment::RESERVED_PCHAR}/"
|
|
823
|
-
UNSAFE_PCHAR = Regexp.new("[^#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}]", false, 'N').freeze
|
|
824
|
-
|
|
825
|
-
def interpolation_chunk(value_code = "#{local_name}")
|
|
826
|
-
"\#{URI.escape(#{value_code}.to_s, ActionController::Routing::PathSegment::UNSAFE_PCHAR)}"
|
|
827
|
-
end
|
|
828
|
-
|
|
829
|
-
def default
|
|
830
|
-
''
|
|
831
|
-
end
|
|
832
|
-
|
|
833
|
-
def default=(path)
|
|
834
|
-
raise RoutingError, "paths cannot have non-empty default values" unless path.blank?
|
|
835
|
-
end
|
|
836
|
-
|
|
837
|
-
def match_extraction(next_capture)
|
|
838
|
-
"params[:#{key}] = PathSegment::Result.new_escaped((match[#{next_capture}]#{" || " + default.inspect if default}).split('/'))#{" if match[" + next_capture + "]" if !default}"
|
|
839
|
-
end
|
|
840
|
-
|
|
841
|
-
def regexp_chunk
|
|
842
|
-
regexp || "(.*)"
|
|
843
|
-
end
|
|
844
|
-
|
|
845
|
-
def optionality_implied?
|
|
846
|
-
true
|
|
847
|
-
end
|
|
848
|
-
|
|
849
|
-
class Result < ::Array #:nodoc:
|
|
850
|
-
def to_s() join '/' end
|
|
851
|
-
def self.new_escaped(strings)
|
|
852
|
-
new strings.collect {|str| URI.unescape str}
|
|
853
|
-
end
|
|
854
|
-
end
|
|
855
|
-
end
|
|
856
|
-
|
|
857
|
-
class RouteBuilder #:nodoc:
|
|
858
|
-
attr_accessor :separators, :optional_separators
|
|
859
|
-
|
|
860
|
-
def initialize
|
|
861
|
-
self.separators = Routing::SEPARATORS
|
|
862
|
-
self.optional_separators = %w( / )
|
|
863
|
-
end
|
|
864
|
-
|
|
865
|
-
def separator_pattern(inverted = false)
|
|
866
|
-
"[#{'^' if inverted}#{Regexp.escape(separators.join)}]"
|
|
867
|
-
end
|
|
868
|
-
|
|
869
|
-
def interval_regexp
|
|
870
|
-
Regexp.new "(.*?)(#{separators.source}|$)"
|
|
871
|
-
end
|
|
872
|
-
|
|
873
|
-
# Accepts a "route path" (a string defining a route), and returns the array
|
|
874
|
-
# of segments that corresponds to it. Note that the segment array is only
|
|
875
|
-
# partially initialized--the defaults and requirements, for instance, need
|
|
876
|
-
# to be set separately, via the #assign_route_options method, and the
|
|
877
|
-
# #optional? method for each segment will not be reliable until after
|
|
878
|
-
# #assign_route_options is called, as well.
|
|
879
|
-
def segments_for_route_path(path)
|
|
880
|
-
rest, segments = path, []
|
|
881
|
-
|
|
882
|
-
until rest.empty?
|
|
883
|
-
segment, rest = segment_for rest
|
|
884
|
-
segments << segment
|
|
885
|
-
end
|
|
886
|
-
segments
|
|
887
|
-
end
|
|
888
|
-
|
|
889
|
-
# A factory method that returns a new segment instance appropriate for the
|
|
890
|
-
# format of the given string.
|
|
891
|
-
def segment_for(string)
|
|
892
|
-
segment = case string
|
|
893
|
-
when /\A:(\w+)/
|
|
894
|
-
key = $1.to_sym
|
|
895
|
-
case key
|
|
896
|
-
when :controller then ControllerSegment.new(key)
|
|
897
|
-
else DynamicSegment.new key
|
|
898
|
-
end
|
|
899
|
-
when /\A\*(\w+)/ then PathSegment.new($1.to_sym, :optional => true)
|
|
900
|
-
when /\A\?(.*?)\?/
|
|
901
|
-
returning segment = StaticSegment.new($1) do
|
|
902
|
-
segment.is_optional = true
|
|
903
|
-
end
|
|
904
|
-
when /\A(#{separator_pattern(:inverted)}+)/ then StaticSegment.new($1)
|
|
905
|
-
when Regexp.new(separator_pattern) then
|
|
906
|
-
returning segment = DividerSegment.new($&) do
|
|
907
|
-
segment.is_optional = (optional_separators.include? $&)
|
|
908
|
-
end
|
|
909
|
-
end
|
|
910
|
-
[segment, $~.post_match]
|
|
911
|
-
end
|
|
912
|
-
|
|
913
|
-
# Split the given hash of options into requirement and default hashes. The
|
|
914
|
-
# segments are passed alongside in order to distinguish between default values
|
|
915
|
-
# and requirements.
|
|
916
|
-
def divide_route_options(segments, options)
|
|
917
|
-
options = options.dup
|
|
918
|
-
|
|
919
|
-
if options[:namespace]
|
|
920
|
-
options[:controller] = "#{options[:path_prefix]}/#{options[:controller]}"
|
|
921
|
-
options.delete(:path_prefix)
|
|
922
|
-
options.delete(:name_prefix)
|
|
923
|
-
options.delete(:namespace)
|
|
924
|
-
end
|
|
925
|
-
|
|
926
|
-
requirements = (options.delete(:requirements) || {}).dup
|
|
927
|
-
defaults = (options.delete(:defaults) || {}).dup
|
|
928
|
-
conditions = (options.delete(:conditions) || {}).dup
|
|
929
|
-
|
|
930
|
-
path_keys = segments.collect { |segment| segment.key if segment.respond_to?(:key) }.compact
|
|
931
|
-
options.each do |key, value|
|
|
932
|
-
hash = (path_keys.include?(key) && ! value.is_a?(Regexp)) ? defaults : requirements
|
|
933
|
-
hash[key] = value
|
|
934
|
-
end
|
|
935
|
-
|
|
936
|
-
[defaults, requirements, conditions]
|
|
937
|
-
end
|
|
938
|
-
|
|
939
|
-
# Takes a hash of defaults and a hash of requirements, and assigns them to
|
|
940
|
-
# the segments. Any unused requirements (which do not correspond to a segment)
|
|
941
|
-
# are returned as a hash.
|
|
942
|
-
def assign_route_options(segments, defaults, requirements)
|
|
943
|
-
route_requirements = {} # Requirements that do not belong to a segment
|
|
944
|
-
|
|
945
|
-
segment_named = Proc.new do |key|
|
|
946
|
-
segments.detect { |segment| segment.key == key if segment.respond_to?(:key) }
|
|
947
|
-
end
|
|
948
|
-
|
|
949
|
-
requirements.each do |key, requirement|
|
|
950
|
-
segment = segment_named[key]
|
|
951
|
-
if segment
|
|
952
|
-
raise TypeError, "#{key}: requirements on a path segment must be regular expressions" unless requirement.is_a?(Regexp)
|
|
953
|
-
if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
|
|
954
|
-
raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
|
|
955
|
-
end
|
|
956
|
-
segment.regexp = requirement
|
|
957
|
-
else
|
|
958
|
-
route_requirements[key] = requirement
|
|
959
|
-
end
|
|
960
|
-
end
|
|
961
|
-
|
|
962
|
-
defaults.each do |key, default|
|
|
963
|
-
segment = segment_named[key]
|
|
964
|
-
raise ArgumentError, "#{key}: No matching segment exists; cannot assign default" unless segment
|
|
965
|
-
segment.is_optional = true
|
|
966
|
-
segment.default = default.to_param if default
|
|
967
|
-
end
|
|
968
|
-
|
|
969
|
-
assign_default_route_options(segments)
|
|
970
|
-
ensure_required_segments(segments)
|
|
971
|
-
route_requirements
|
|
972
|
-
end
|
|
973
|
-
|
|
974
|
-
# Assign default options, such as 'index' as a default for :action. This
|
|
975
|
-
# method must be run *after* user supplied requirements and defaults have
|
|
976
|
-
# been applied to the segments.
|
|
977
|
-
def assign_default_route_options(segments)
|
|
978
|
-
segments.each do |segment|
|
|
979
|
-
next unless segment.is_a? DynamicSegment
|
|
980
|
-
case segment.key
|
|
981
|
-
when :action
|
|
982
|
-
if segment.regexp.nil? || segment.regexp.match('index').to_s == 'index'
|
|
983
|
-
segment.default ||= 'index'
|
|
984
|
-
segment.is_optional = true
|
|
985
|
-
end
|
|
986
|
-
when :id
|
|
987
|
-
if segment.default.nil? && segment.regexp.nil? || segment.regexp =~ ''
|
|
988
|
-
segment.is_optional = true
|
|
989
|
-
end
|
|
990
|
-
end
|
|
991
|
-
end
|
|
992
|
-
end
|
|
993
|
-
|
|
994
|
-
# Makes sure that there are no optional segments that precede a required
|
|
995
|
-
# segment. If any are found that precede a required segment, they are
|
|
996
|
-
# made required.
|
|
997
|
-
def ensure_required_segments(segments)
|
|
998
|
-
allow_optional = true
|
|
999
|
-
segments.reverse_each do |segment|
|
|
1000
|
-
allow_optional &&= segment.optional?
|
|
1001
|
-
if !allow_optional && segment.optional?
|
|
1002
|
-
unless segment.optionality_implied?
|
|
1003
|
-
warn "Route segment \"#{segment.to_s}\" cannot be optional because it precedes a required segment. This segment will be required."
|
|
1004
|
-
end
|
|
1005
|
-
segment.is_optional = false
|
|
1006
|
-
elsif allow_optional && segment.respond_to?(:default) && segment.default
|
|
1007
|
-
# if a segment has a default, then it is optional
|
|
1008
|
-
segment.is_optional = true
|
|
1009
|
-
end
|
|
1010
|
-
end
|
|
1011
|
-
end
|
|
1012
|
-
|
|
1013
|
-
# Construct and return a route with the given path and options.
|
|
1014
|
-
def build(path, options)
|
|
1015
|
-
# Wrap the path with slashes
|
|
1016
|
-
path = "/#{path}" unless path[0] == ?/
|
|
1017
|
-
path = "#{path}/" unless path[-1] == ?/
|
|
1018
|
-
|
|
1019
|
-
path = "/#{options[:path_prefix].to_s.gsub(/^\//,'')}#{path}" if options[:path_prefix]
|
|
1020
|
-
|
|
1021
|
-
segments = segments_for_route_path(path)
|
|
1022
|
-
defaults, requirements, conditions = divide_route_options(segments, options)
|
|
1023
|
-
requirements = assign_route_options(segments, defaults, requirements)
|
|
1024
|
-
|
|
1025
|
-
route = Route.new
|
|
1026
|
-
|
|
1027
|
-
route.segments = segments
|
|
1028
|
-
route.requirements = requirements
|
|
1029
|
-
route.conditions = conditions
|
|
1030
|
-
|
|
1031
|
-
if !route.significant_keys.include?(:action) && !route.requirements[:action]
|
|
1032
|
-
route.requirements[:action] = "index"
|
|
1033
|
-
route.significant_keys << :action
|
|
1034
|
-
end
|
|
1035
|
-
|
|
1036
|
-
# Routes cannot use the current string interpolation method
|
|
1037
|
-
# if there are user-supplied :requirements as the interpolation
|
|
1038
|
-
# code won't raise RoutingErrors when generating
|
|
1039
|
-
if options.key?(:requirements) || route.requirements.keys.to_set != Routing::ALLOWED_REQUIREMENTS_FOR_OPTIMISATION
|
|
1040
|
-
route.optimise = false
|
|
1041
|
-
end
|
|
1042
|
-
|
|
1043
|
-
if !route.significant_keys.include?(:controller)
|
|
1044
|
-
raise ArgumentError, "Illegal route: the :controller must be specified!"
|
|
1045
|
-
end
|
|
1046
|
-
|
|
1047
|
-
route
|
|
1048
|
-
end
|
|
1049
|
-
end
|
|
1050
|
-
|
|
1051
|
-
class RouteSet #:nodoc:
|
|
1052
|
-
# Mapper instances are used to build routes. The object passed to the draw
|
|
1053
|
-
# block in config/routes.rb is a Mapper instance.
|
|
1054
|
-
#
|
|
1055
|
-
# Mapper instances have relatively few instance methods, in order to avoid
|
|
1056
|
-
# clashes with named routes.
|
|
1057
|
-
class Mapper #:doc:
|
|
1058
|
-
def initialize(set) #:nodoc:
|
|
1059
|
-
@set = set
|
|
1060
|
-
end
|
|
1061
|
-
|
|
1062
|
-
# Create an unnamed route with the provided +path+ and +options+. See
|
|
1063
|
-
# ActionController::Routing for an introduction to routes.
|
|
1064
|
-
def connect(path, options = {})
|
|
1065
|
-
@set.add_route(path, options)
|
|
1066
|
-
end
|
|
1067
|
-
|
|
1068
|
-
# Creates a named route called "root" for matching the root level request.
|
|
1069
|
-
def root(options = {})
|
|
1070
|
-
named_route("root", '', options)
|
|
1071
|
-
end
|
|
1072
|
-
|
|
1073
|
-
def named_route(name, path, options = {}) #:nodoc:
|
|
1074
|
-
@set.add_named_route(name, path, options)
|
|
1075
|
-
end
|
|
1076
|
-
|
|
1077
|
-
# Enables the use of resources in a module by setting the name_prefix, path_prefix, and namespace for the model.
|
|
1078
|
-
# Example:
|
|
1079
|
-
#
|
|
1080
|
-
# map.namespace(:admin) do |admin|
|
|
1081
|
-
# admin.resources :products,
|
|
1082
|
-
# :has_many => [ :tags, :images, :variants ]
|
|
1083
|
-
# end
|
|
1084
|
-
#
|
|
1085
|
-
# This will create +admin_products_url+ pointing to "admin/products", which will look for an Admin::ProductsController.
|
|
1086
|
-
# It'll also create +admin_product_tags_url+ pointing to "admin/products/#{product_id}/tags", which will look for
|
|
1087
|
-
# Admin::TagsController.
|
|
1088
|
-
def namespace(name, options = {}, &block)
|
|
1089
|
-
if options[:namespace]
|
|
1090
|
-
with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}", :name_prefix => "#{options.delete(:name_prefix)}#{name}_", :namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block)
|
|
1091
|
-
else
|
|
1092
|
-
with_options({:path_prefix => name, :name_prefix => "#{name}_", :namespace => "#{name}/" }.merge(options), &block)
|
|
1093
|
-
end
|
|
1094
|
-
end
|
|
1095
|
-
|
|
1096
|
-
def method_missing(route_name, *args, &proc) #:nodoc:
|
|
1097
|
-
super unless args.length >= 1 && proc.nil?
|
|
1098
|
-
@set.add_named_route(route_name, *args)
|
|
1099
|
-
end
|
|
1100
|
-
end
|
|
1101
|
-
|
|
1102
|
-
# A NamedRouteCollection instance is a collection of named routes, and also
|
|
1103
|
-
# maintains an anonymous module that can be used to install helpers for the
|
|
1104
|
-
# named routes.
|
|
1105
|
-
class NamedRouteCollection #:nodoc:
|
|
1106
|
-
include Enumerable
|
|
1107
|
-
include ActionController::Routing::Optimisation
|
|
1108
|
-
attr_reader :routes, :helpers
|
|
1109
|
-
|
|
1110
|
-
def initialize
|
|
1111
|
-
clear!
|
|
1112
|
-
end
|
|
1113
|
-
|
|
1114
|
-
def clear!
|
|
1115
|
-
@routes = {}
|
|
1116
|
-
@helpers = []
|
|
1117
|
-
|
|
1118
|
-
@module ||= Module.new
|
|
1119
|
-
@module.instance_methods.each do |selector|
|
|
1120
|
-
@module.class_eval { remove_method selector }
|
|
1121
|
-
end
|
|
1122
|
-
end
|
|
1123
|
-
|
|
1124
|
-
def add(name, route)
|
|
1125
|
-
routes[name.to_sym] = route
|
|
1126
|
-
define_named_route_methods(name, route)
|
|
1127
|
-
end
|
|
1128
|
-
|
|
1129
|
-
def get(name)
|
|
1130
|
-
routes[name.to_sym]
|
|
1131
|
-
end
|
|
1132
|
-
|
|
1133
|
-
alias []= add
|
|
1134
|
-
alias [] get
|
|
1135
|
-
alias clear clear!
|
|
1136
|
-
|
|
1137
|
-
def each
|
|
1138
|
-
routes.each { |name, route| yield name, route }
|
|
1139
|
-
self
|
|
1140
|
-
end
|
|
1141
|
-
|
|
1142
|
-
def names
|
|
1143
|
-
routes.keys
|
|
1144
|
-
end
|
|
1145
|
-
|
|
1146
|
-
def length
|
|
1147
|
-
routes.length
|
|
1148
|
-
end
|
|
1149
|
-
|
|
1150
|
-
def reset!
|
|
1151
|
-
old_routes = routes.dup
|
|
1152
|
-
clear!
|
|
1153
|
-
old_routes.each do |name, route|
|
|
1154
|
-
add(name, route)
|
|
1155
|
-
end
|
|
1156
|
-
end
|
|
1157
|
-
|
|
1158
|
-
def install(destinations = [ActionController::Base, ActionView::Base], regenerate = false)
|
|
1159
|
-
reset! if regenerate
|
|
1160
|
-
Array(destinations).each do |dest|
|
|
1161
|
-
dest.send! :include, @module
|
|
1162
|
-
end
|
|
1163
|
-
end
|
|
1164
|
-
|
|
1165
|
-
private
|
|
1166
|
-
def url_helper_name(name, kind = :url)
|
|
1167
|
-
:"#{name}_#{kind}"
|
|
1168
|
-
end
|
|
1169
|
-
|
|
1170
|
-
def hash_access_name(name, kind = :url)
|
|
1171
|
-
:"hash_for_#{name}_#{kind}"
|
|
1172
|
-
end
|
|
1173
|
-
|
|
1174
|
-
def define_named_route_methods(name, route)
|
|
1175
|
-
{:url => {:only_path => false}, :path => {:only_path => true}}.each do |kind, opts|
|
|
1176
|
-
hash = route.defaults.merge(:use_route => name).merge(opts)
|
|
1177
|
-
define_hash_access route, name, kind, hash
|
|
1178
|
-
define_url_helper route, name, kind, hash
|
|
1179
|
-
end
|
|
1180
|
-
end
|
|
1181
|
-
|
|
1182
|
-
def define_hash_access(route, name, kind, options)
|
|
1183
|
-
selector = hash_access_name(name, kind)
|
|
1184
|
-
@module.module_eval <<-end_eval # We use module_eval to avoid leaks
|
|
1185
|
-
def #{selector}(options = nil)
|
|
1186
|
-
options ? #{options.inspect}.merge(options) : #{options.inspect}
|
|
1187
|
-
end
|
|
1188
|
-
protected :#{selector}
|
|
1189
|
-
end_eval
|
|
1190
|
-
helpers << selector
|
|
1191
|
-
end
|
|
1192
|
-
|
|
1193
|
-
def define_url_helper(route, name, kind, options)
|
|
1194
|
-
selector = url_helper_name(name, kind)
|
|
1195
|
-
# The segment keys used for positional paramters
|
|
1196
|
-
|
|
1197
|
-
hash_access_method = hash_access_name(name, kind)
|
|
1198
|
-
|
|
1199
|
-
# allow ordered parameters to be associated with corresponding
|
|
1200
|
-
# dynamic segments, so you can do
|
|
1201
|
-
#
|
|
1202
|
-
# foo_url(bar, baz, bang)
|
|
1203
|
-
#
|
|
1204
|
-
# instead of
|
|
1205
|
-
#
|
|
1206
|
-
# foo_url(:bar => bar, :baz => baz, :bang => bang)
|
|
1207
|
-
#
|
|
1208
|
-
# Also allow options hash, so you can do
|
|
1209
|
-
#
|
|
1210
|
-
# foo_url(bar, baz, bang, :sort_by => 'baz')
|
|
1211
|
-
#
|
|
1212
|
-
@module.module_eval <<-end_eval # We use module_eval to avoid leaks
|
|
1213
|
-
def #{selector}(*args)
|
|
1214
|
-
#{generate_optimisation_block(route, kind)}
|
|
1215
|
-
|
|
1216
|
-
opts = if args.empty? || Hash === args.first
|
|
1217
|
-
args.first || {}
|
|
1218
|
-
else
|
|
1219
|
-
options = args.extract_options!
|
|
1220
|
-
args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)|
|
|
1221
|
-
h[k] = v
|
|
1222
|
-
h
|
|
1223
|
-
end
|
|
1224
|
-
options.merge(args)
|
|
1225
|
-
end
|
|
1226
|
-
|
|
1227
|
-
url_for(#{hash_access_method}(opts))
|
|
1228
|
-
end
|
|
1229
|
-
protected :#{selector}
|
|
1230
|
-
end_eval
|
|
1231
|
-
helpers << selector
|
|
1232
|
-
end
|
|
1233
|
-
end
|
|
1234
|
-
|
|
1235
|
-
attr_accessor :routes, :named_routes
|
|
1236
|
-
|
|
1237
|
-
def initialize
|
|
1238
|
-
self.routes = []
|
|
1239
|
-
self.named_routes = NamedRouteCollection.new
|
|
1240
|
-
end
|
|
1241
|
-
|
|
1242
|
-
# Subclasses and plugins may override this method to specify a different
|
|
1243
|
-
# RouteBuilder instance, so that other route DSL's can be created.
|
|
1244
|
-
def builder
|
|
1245
|
-
@builder ||= RouteBuilder.new
|
|
1246
|
-
end
|
|
1247
|
-
|
|
1248
|
-
def draw
|
|
1249
|
-
clear!
|
|
1250
|
-
yield Mapper.new(self)
|
|
1251
|
-
install_helpers
|
|
1252
|
-
end
|
|
1253
|
-
|
|
1254
|
-
def clear!
|
|
1255
|
-
routes.clear
|
|
1256
|
-
named_routes.clear
|
|
1257
|
-
@combined_regexp = nil
|
|
1258
|
-
@routes_by_controller = nil
|
|
1259
|
-
end
|
|
1260
|
-
|
|
1261
|
-
def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false)
|
|
1262
|
-
Array(destinations).each { |d| d.module_eval { include Helpers } }
|
|
1263
|
-
named_routes.install(destinations, regenerate_code)
|
|
1264
|
-
end
|
|
1265
|
-
|
|
1266
|
-
def empty?
|
|
1267
|
-
routes.empty?
|
|
1268
|
-
end
|
|
1269
|
-
|
|
1270
|
-
def load!
|
|
1271
|
-
Routing.use_controllers! nil # Clear the controller cache so we may discover new ones
|
|
1272
|
-
clear!
|
|
1273
|
-
load_routes!
|
|
1274
|
-
install_helpers
|
|
1275
|
-
end
|
|
1276
|
-
|
|
1277
|
-
# reload! will always force a reload whereas load checks the timestamp first
|
|
1278
|
-
alias reload! load!
|
|
1279
|
-
|
|
1280
|
-
def reload
|
|
1281
|
-
if @routes_last_modified && defined?(RAILS_ROOT)
|
|
1282
|
-
mtime = File.stat("#{RAILS_ROOT}/config/routes.rb").mtime
|
|
1283
|
-
# if it hasn't been changed, then just return
|
|
1284
|
-
return if mtime == @routes_last_modified
|
|
1285
|
-
# if it has changed then record the new time and fall to the load! below
|
|
1286
|
-
@routes_last_modified = mtime
|
|
1287
|
-
end
|
|
1288
|
-
load!
|
|
1289
|
-
end
|
|
1290
|
-
|
|
1291
|
-
def load_routes!
|
|
1292
|
-
if defined?(RAILS_ROOT) && defined?(::ActionController::Routing::Routes) && self == ::ActionController::Routing::Routes
|
|
1293
|
-
load File.join("#{RAILS_ROOT}/config/routes.rb")
|
|
1294
|
-
@routes_last_modified = File.stat("#{RAILS_ROOT}/config/routes.rb").mtime
|
|
1295
|
-
else
|
|
1296
|
-
add_route ":controller/:action/:id"
|
|
1297
|
-
end
|
|
1298
|
-
end
|
|
1299
|
-
|
|
1300
|
-
def add_route(path, options = {})
|
|
1301
|
-
route = builder.build(path, options)
|
|
1302
|
-
routes << route
|
|
1303
|
-
route
|
|
1304
|
-
end
|
|
1305
|
-
|
|
1306
|
-
def add_named_route(name, path, options = {})
|
|
1307
|
-
# TODO - is options EVER used?
|
|
1308
|
-
name = options[:name_prefix] + name.to_s if options[:name_prefix]
|
|
1309
|
-
named_routes[name.to_sym] = add_route(path, options)
|
|
1310
|
-
end
|
|
1311
|
-
|
|
1312
|
-
def options_as_params(options)
|
|
1313
|
-
# If an explicit :controller was given, always make :action explicit
|
|
1314
|
-
# too, so that action expiry works as expected for things like
|
|
1315
|
-
#
|
|
1316
|
-
# generate({:controller => 'content'}, {:controller => 'content', :action => 'show'})
|
|
1317
|
-
#
|
|
1318
|
-
# (the above is from the unit tests). In the above case, because the
|
|
1319
|
-
# controller was explicitly given, but no action, the action is implied to
|
|
1320
|
-
# be "index", not the recalled action of "show".
|
|
1321
|
-
#
|
|
1322
|
-
# great fun, eh?
|
|
1323
|
-
|
|
1324
|
-
options_as_params = options.clone
|
|
1325
|
-
options_as_params[:action] ||= 'index' if options[:controller]
|
|
1326
|
-
options_as_params[:action] = options_as_params[:action].to_s if options_as_params[:action]
|
|
1327
|
-
options_as_params
|
|
1328
|
-
end
|
|
1329
|
-
|
|
1330
|
-
def build_expiry(options, recall)
|
|
1331
|
-
recall.inject({}) do |expiry, (key, recalled_value)|
|
|
1332
|
-
expiry[key] = (options.key?(key) && options[key].to_param != recalled_value.to_param)
|
|
1333
|
-
expiry
|
|
1334
|
-
end
|
|
1335
|
-
end
|
|
1336
|
-
|
|
1337
|
-
# Generate the path indicated by the arguments, and return an array of
|
|
1338
|
-
# the keys that were not used to generate it.
|
|
1339
|
-
def extra_keys(options, recall={})
|
|
1340
|
-
generate_extras(options, recall).last
|
|
1341
|
-
end
|
|
1342
|
-
|
|
1343
|
-
def generate_extras(options, recall={})
|
|
1344
|
-
generate(options, recall, :generate_extras)
|
|
1345
|
-
end
|
|
1346
|
-
|
|
1347
|
-
def generate(options, recall = {}, method=:generate)
|
|
1348
|
-
named_route_name = options.delete(:use_route)
|
|
1349
|
-
generate_all = options.delete(:generate_all)
|
|
1350
|
-
if named_route_name
|
|
1351
|
-
named_route = named_routes[named_route_name]
|
|
1352
|
-
options = named_route.parameter_shell.merge(options)
|
|
1353
|
-
end
|
|
1354
|
-
|
|
1355
|
-
options = options_as_params(options)
|
|
1356
|
-
expire_on = build_expiry(options, recall)
|
|
1357
|
-
|
|
1358
|
-
if options[:controller]
|
|
1359
|
-
options[:controller] = options[:controller].to_s
|
|
1360
|
-
end
|
|
1361
|
-
# if the controller has changed, make sure it changes relative to the
|
|
1362
|
-
# current controller module, if any. In other words, if we're currently
|
|
1363
|
-
# on admin/get, and the new controller is 'set', the new controller
|
|
1364
|
-
# should really be admin/set.
|
|
1365
|
-
if !named_route && expire_on[:controller] && options[:controller] && options[:controller][0] != ?/
|
|
1366
|
-
old_parts = recall[:controller].split('/')
|
|
1367
|
-
new_parts = options[:controller].split('/')
|
|
1368
|
-
parts = old_parts[0..-(new_parts.length + 1)] + new_parts
|
|
1369
|
-
options[:controller] = parts.join('/')
|
|
1370
|
-
end
|
|
1371
|
-
|
|
1372
|
-
# drop the leading '/' on the controller name
|
|
1373
|
-
options[:controller] = options[:controller][1..-1] if options[:controller] && options[:controller][0] == ?/
|
|
1374
|
-
merged = recall.merge(options)
|
|
1375
|
-
|
|
1376
|
-
if named_route
|
|
1377
|
-
path = named_route.generate(options, merged, expire_on)
|
|
1378
|
-
if path.nil?
|
|
1379
|
-
raise_named_route_error(options, named_route, named_route_name)
|
|
1380
|
-
else
|
|
1381
|
-
return path
|
|
1382
|
-
end
|
|
1383
|
-
else
|
|
1384
|
-
merged[:action] ||= 'index'
|
|
1385
|
-
options[:action] ||= 'index'
|
|
1386
|
-
|
|
1387
|
-
controller = merged[:controller]
|
|
1388
|
-
action = merged[:action]
|
|
1389
|
-
|
|
1390
|
-
raise RoutingError, "Need controller and action!" unless controller && action
|
|
1391
|
-
|
|
1392
|
-
if generate_all
|
|
1393
|
-
# Used by caching to expire all paths for a resource
|
|
1394
|
-
return routes.collect do |route|
|
|
1395
|
-
route.send!(method, options, merged, expire_on)
|
|
1396
|
-
end.compact
|
|
1397
|
-
end
|
|
1398
|
-
|
|
1399
|
-
# don't use the recalled keys when determining which routes to check
|
|
1400
|
-
routes = routes_by_controller[controller][action][options.keys.sort_by { |x| x.object_id }]
|
|
1401
|
-
|
|
1402
|
-
routes.each do |route|
|
|
1403
|
-
results = route.send!(method, options, merged, expire_on)
|
|
1404
|
-
return results if results && (!results.is_a?(Array) || results.first)
|
|
1405
|
-
end
|
|
1406
|
-
end
|
|
1407
|
-
|
|
1408
|
-
raise RoutingError, "No route matches #{options.inspect}"
|
|
1409
|
-
end
|
|
1410
|
-
|
|
1411
|
-
# try to give a helpful error message when named route generation fails
|
|
1412
|
-
def raise_named_route_error(options, named_route, named_route_name)
|
|
1413
|
-
diff = named_route.requirements.diff(options)
|
|
1414
|
-
unless diff.empty?
|
|
1415
|
-
raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect}, expected: #{named_route.requirements.inspect}, diff: #{named_route.requirements.diff(options).inspect}"
|
|
1416
|
-
else
|
|
1417
|
-
required_segments = named_route.segments.select {|seg| (!seg.optional?) && (!seg.is_a?(DividerSegment)) }
|
|
1418
|
-
required_keys_or_values = required_segments.map { |seg| seg.key rescue seg.value } # we want either the key or the value from the segment
|
|
1419
|
-
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?"
|
|
1420
|
-
end
|
|
1421
|
-
end
|
|
1422
|
-
|
|
1423
|
-
def recognize(request)
|
|
1424
|
-
params = recognize_path(request.path, extract_request_environment(request))
|
|
1425
|
-
request.path_parameters = params.with_indifferent_access
|
|
1426
|
-
"#{params[:controller].camelize}Controller".constantize
|
|
1427
|
-
end
|
|
1428
|
-
|
|
1429
|
-
def recognize_path(path, environment={})
|
|
1430
|
-
routes.each do |route|
|
|
1431
|
-
result = route.recognize(path, environment) and return result
|
|
1432
|
-
end
|
|
1433
|
-
|
|
1434
|
-
allows = HTTP_METHODS.select { |verb| routes.find { |r| r.recognize(path, :method => verb) } }
|
|
1435
|
-
|
|
1436
|
-
if environment[:method] && !HTTP_METHODS.include?(environment[:method])
|
|
1437
|
-
raise NotImplemented.new(*allows)
|
|
1438
|
-
elsif !allows.empty?
|
|
1439
|
-
raise MethodNotAllowed.new(*allows)
|
|
1440
|
-
else
|
|
1441
|
-
raise RoutingError, "No route matches #{path.inspect} with #{environment.inspect}"
|
|
1442
|
-
end
|
|
1443
|
-
end
|
|
1444
|
-
|
|
1445
|
-
def routes_by_controller
|
|
1446
|
-
@routes_by_controller ||= Hash.new do |controller_hash, controller|
|
|
1447
|
-
controller_hash[controller] = Hash.new do |action_hash, action|
|
|
1448
|
-
action_hash[action] = Hash.new do |key_hash, keys|
|
|
1449
|
-
key_hash[keys] = routes_for_controller_and_action_and_keys(controller, action, keys)
|
|
1450
|
-
end
|
|
1451
|
-
end
|
|
1452
|
-
end
|
|
1453
|
-
end
|
|
1454
|
-
|
|
1455
|
-
def routes_for(options, merged, expire_on)
|
|
1456
|
-
raise "Need controller and action!" unless controller && action
|
|
1457
|
-
controller = merged[:controller]
|
|
1458
|
-
merged = options if expire_on[:controller]
|
|
1459
|
-
action = merged[:action] || 'index'
|
|
1460
|
-
|
|
1461
|
-
routes_by_controller[controller][action][merged.keys]
|
|
1462
|
-
end
|
|
1463
|
-
|
|
1464
|
-
def routes_for_controller_and_action(controller, action)
|
|
1465
|
-
selected = routes.select do |route|
|
|
1466
|
-
route.matches_controller_and_action? controller, action
|
|
1467
|
-
end
|
|
1468
|
-
(selected.length == routes.length) ? routes : selected
|
|
1469
|
-
end
|
|
1470
|
-
|
|
1471
|
-
def routes_for_controller_and_action_and_keys(controller, action, keys)
|
|
1472
|
-
selected = routes.select do |route|
|
|
1473
|
-
route.matches_controller_and_action? controller, action
|
|
1474
|
-
end
|
|
1475
|
-
selected.sort_by do |route|
|
|
1476
|
-
(keys - route.significant_keys).length
|
|
1477
|
-
end
|
|
1478
|
-
end
|
|
1479
|
-
|
|
1480
|
-
# Subclasses and plugins may override this method to extract further attributes
|
|
1481
|
-
# from the request, for use by route conditions and such.
|
|
1482
|
-
def extract_request_environment(request)
|
|
1483
|
-
{ :method => request.method }
|
|
1484
|
-
end
|
|
1485
|
-
end
|
|
368
|
+
|
|
1486
369
|
|
|
1487
370
|
Routes = RouteSet.new
|
|
1488
371
|
|
|
1489
372
|
::Inflector.module_eval do
|
|
373
|
+
# Ensures that routes are reloaded when Rails inflections are updated.
|
|
1490
374
|
def inflections_with_route_reloading(&block)
|
|
1491
375
|
returning(inflections_without_route_reloading(&block)) {
|
|
1492
376
|
ActionController::Routing::Routes.reload! if block_given?
|