actionpack 2.1.2 → 2.2.2
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 +223 -7
- data/README +6 -12
- data/Rakefile +11 -11
- data/lib/action_controller.rb +9 -9
- data/lib/action_controller/assertions/response_assertions.rb +29 -78
- data/lib/action_controller/assertions/routing_assertions.rb +33 -33
- data/lib/action_controller/assertions/selector_assertions.rb +9 -5
- data/lib/action_controller/base.rb +227 -161
- data/lib/action_controller/benchmarking.rb +37 -24
- data/lib/action_controller/caching/actions.rb +53 -21
- data/lib/action_controller/caching/fragments.rb +10 -36
- data/lib/action_controller/caching/sweeping.rb +3 -3
- data/lib/action_controller/cgi_ext/session.rb +2 -22
- data/lib/action_controller/cgi_process.rb +8 -46
- data/lib/action_controller/components.rb +4 -1
- data/lib/action_controller/cookies.rb +10 -0
- data/lib/action_controller/dispatcher.rb +49 -15
- data/lib/action_controller/filters.rb +48 -10
- data/lib/action_controller/headers.rb +16 -14
- data/lib/action_controller/helpers.rb +2 -2
- data/lib/action_controller/http_authentication.rb +1 -1
- data/lib/action_controller/integration.rb +57 -60
- data/lib/action_controller/layout.rb +27 -53
- data/lib/action_controller/mime_responds.rb +5 -1
- data/lib/action_controller/mime_type.rb +64 -42
- data/lib/action_controller/mime_types.rb +2 -1
- data/lib/action_controller/performance_test.rb +16 -0
- data/lib/action_controller/polymorphic_routes.rb +16 -9
- data/lib/action_controller/rack_process.rb +303 -0
- data/lib/action_controller/request.rb +205 -97
- data/lib/action_controller/request_forgery_protection.rb +2 -2
- data/lib/action_controller/request_profiler.rb +0 -0
- data/lib/action_controller/rescue.rb +20 -115
- data/lib/action_controller/resources.rb +186 -83
- data/lib/action_controller/response.rb +140 -26
- data/lib/action_controller/routing.rb +28 -30
- data/lib/action_controller/routing/builder.rb +45 -54
- data/lib/action_controller/routing/optimisations.rb +31 -21
- data/lib/action_controller/routing/recognition_optimisation.rb +33 -27
- data/lib/action_controller/routing/route.rb +162 -147
- data/lib/action_controller/routing/route_set.rb +8 -7
- data/lib/action_controller/routing/routing_ext.rb +4 -1
- data/lib/action_controller/routing/segments.rb +50 -21
- data/lib/action_controller/session/cookie_store.rb +3 -2
- data/lib/action_controller/session/drb_server.rb +7 -7
- data/lib/action_controller/session_management.rb +6 -2
- data/lib/action_controller/streaming.rb +15 -8
- data/lib/action_controller/templates/rescues/diagnostics.erb +2 -2
- data/lib/action_controller/templates/rescues/template_error.erb +2 -2
- data/lib/action_controller/test_case.rb +66 -2
- data/lib/action_controller/test_process.rb +71 -66
- data/lib/action_controller/translation.rb +13 -0
- data/lib/action_controller/url_rewriter.rb +90 -13
- data/lib/action_controller/vendor/html-scanner/html/node.rb +9 -2
- data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +1 -1
- data/lib/action_controller/vendor/html-scanner/html/selector.rb +2 -2
- data/lib/action_controller/verification.rb +2 -2
- data/lib/action_pack/version.rb +1 -1
- data/lib/action_view.rb +19 -11
- data/lib/action_view/base.rb +184 -150
- data/lib/action_view/helpers.rb +38 -0
- data/lib/action_view/helpers/active_record_helper.rb +56 -27
- data/lib/action_view/helpers/asset_tag_helper.rb +356 -153
- data/lib/action_view/helpers/atom_feed_helper.rb +74 -19
- data/lib/action_view/helpers/benchmark_helper.rb +3 -3
- data/lib/action_view/helpers/cache_helper.rb +1 -2
- data/lib/action_view/helpers/capture_helper.rb +19 -44
- data/lib/action_view/helpers/date_helper.rb +486 -296
- data/lib/action_view/helpers/debug_helper.rb +20 -13
- data/lib/action_view/helpers/form_helper.rb +71 -30
- data/lib/action_view/helpers/form_options_helper.rb +15 -85
- data/lib/action_view/helpers/form_tag_helper.rb +61 -38
- data/lib/action_view/helpers/javascript_helper.rb +80 -89
- data/lib/action_view/helpers/number_helper.rb +179 -74
- data/lib/action_view/helpers/prototype_helper.rb +216 -201
- data/lib/action_view/helpers/record_tag_helper.rb +4 -5
- data/lib/action_view/helpers/sanitize_helper.rb +65 -33
- data/lib/action_view/helpers/scriptaculous_helper.rb +2 -2
- data/lib/action_view/helpers/tag_helper.rb +39 -22
- data/lib/action_view/helpers/text_helper.rb +212 -118
- data/lib/action_view/helpers/translation_helper.rb +21 -0
- data/lib/action_view/helpers/url_helper.rb +100 -58
- data/lib/action_view/inline_template.rb +13 -14
- data/lib/action_view/locale/en.yml +91 -0
- data/lib/action_view/partials.rb +100 -55
- data/lib/action_view/paths.rb +125 -0
- data/lib/action_view/renderable.rb +102 -0
- data/lib/action_view/renderable_partial.rb +48 -0
- data/lib/action_view/template.rb +90 -101
- data/lib/action_view/template_error.rb +11 -21
- data/lib/action_view/template_handler.rb +8 -28
- data/lib/action_view/template_handlers.rb +45 -0
- data/lib/action_view/template_handlers/builder.rb +5 -15
- data/lib/action_view/template_handlers/erb.rb +9 -6
- data/lib/action_view/template_handlers/rjs.rb +2 -17
- data/lib/action_view/test_case.rb +7 -4
- data/test/abstract_unit.rb +4 -1
- data/test/active_record_unit.rb +28 -30
- data/test/activerecord/render_partial_with_record_identification_test.rb +25 -12
- data/test/controller/action_pack_assertions_test.rb +8 -37
- data/test/controller/addresses_render_test.rb +0 -3
- data/test/controller/assert_select_test.rb +51 -24
- data/test/controller/base_test.rb +4 -4
- data/test/controller/caching_test.rb +136 -66
- data/test/controller/capture_test.rb +1 -21
- data/test/controller/cgi_test.rb +157 -10
- data/test/controller/components_test.rb +41 -25
- data/test/controller/content_type_test.rb +49 -17
- data/test/controller/cookie_test.rb +1 -1
- data/test/controller/deprecation/deprecated_base_methods_test.rb +0 -3
- data/test/controller/dispatcher_test.rb +9 -1
- data/test/controller/filter_params_test.rb +2 -2
- data/test/controller/filters_test.rb +13 -13
- data/test/controller/html-scanner/cdata_node_test.rb +15 -0
- data/test/controller/html-scanner/node_test.rb +21 -0
- data/test/controller/html-scanner/sanitizer_test.rb +14 -0
- data/test/controller/integration_test.rb +167 -6
- data/test/controller/layout_test.rb +11 -68
- data/test/controller/logging_test.rb +46 -0
- data/test/controller/mime_responds_test.rb +61 -59
- data/test/controller/mime_type_test.rb +6 -6
- data/test/controller/polymorphic_routes_test.rb +37 -2
- data/test/controller/rack_test.rb +323 -0
- data/test/controller/redirect_test.rb +72 -71
- data/test/controller/render_test.rb +1120 -108
- data/test/controller/request_forgery_protection_test.rb +66 -52
- data/test/controller/request_test.rb +103 -146
- data/test/controller/rescue_test.rb +20 -24
- data/test/controller/resources_test.rb +408 -25
- data/test/controller/routing_test.rb +1774 -1774
- data/test/controller/send_file_test.rb +0 -4
- data/test/controller/session/cookie_store_test.rb +53 -1
- data/test/controller/test_test.rb +15 -37
- data/test/controller/translation_test.rb +26 -0
- data/test/controller/url_rewriter_test.rb +27 -28
- data/test/controller/view_paths_test.rb +48 -47
- data/test/fixtures/_top_level_partial.html.erb +1 -0
- data/test/fixtures/_top_level_partial_only.erb +1 -0
- data/test/fixtures/developers/_developer.erb +1 -0
- data/test/fixtures/fun/games/_game.erb +1 -0
- data/test/fixtures/fun/serious/games/_game.erb +1 -0
- data/test/fixtures/functional_caching/formatted_fragment_cached.html.erb +3 -0
- data/test/fixtures/functional_caching/formatted_fragment_cached.js.rjs +6 -0
- data/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder +5 -0
- data/test/fixtures/functional_caching/inline_fragment_cached.html.erb +2 -0
- data/test/fixtures/layouts/_column.html.erb +2 -0
- data/test/fixtures/projects/_project.erb +1 -0
- data/test/fixtures/public/javascripts/subdir/subdir.js +1 -0
- data/test/fixtures/public/stylesheets/subdir/subdir.css +1 -0
- data/test/fixtures/replies/_reply.erb +1 -0
- data/test/fixtures/test/_counter.html.erb +1 -0
- data/test/fixtures/test/_customer.erb +1 -1
- data/test/fixtures/test/_customer_with_var.erb +1 -0
- data/test/fixtures/test/_layout_for_block_with_args.html.erb +3 -0
- data/test/fixtures/test/_local_inspector.html.erb +1 -0
- data/test/fixtures/test/_partial_with_only_html_version.html.erb +1 -0
- data/test/fixtures/test/hello.builder +1 -1
- data/test/fixtures/test/hyphen-ated.erb +1 -0
- data/test/fixtures/test/implicit_content_type.atom.builder +2 -0
- data/test/fixtures/test/nested_layout.erb +3 -0
- data/test/fixtures/test/non_erb_block_content_for.builder +1 -1
- data/test/fixtures/test/sub_template_raise.html.erb +1 -0
- data/test/fixtures/test/template.erb +1 -0
- data/test/fixtures/test/using_layout_around_block_with_args.html.erb +1 -0
- data/test/template/active_record_helper_i18n_test.rb +46 -0
- data/test/template/active_record_helper_test.rb +24 -24
- data/test/template/asset_tag_helper_test.rb +161 -29
- data/test/template/atom_feed_helper_test.rb +114 -5
- data/test/template/compiled_templates_test.rb +59 -0
- data/test/template/date_helper_i18n_test.rb +113 -0
- data/test/template/date_helper_test.rb +403 -109
- data/test/template/form_helper_test.rb +213 -154
- data/test/template/form_options_helper_test.rb +249 -897
- data/test/template/form_tag_helper_test.rb +80 -32
- data/test/template/javascript_helper_test.rb +17 -18
- data/test/template/number_helper_i18n_test.rb +54 -0
- data/test/template/number_helper_test.rb +43 -13
- data/test/template/prototype_helper_test.rb +101 -84
- data/test/template/record_tag_helper_test.rb +24 -20
- data/test/template/render_test.rb +193 -0
- data/test/template/sanitize_helper_test.rb +3 -3
- data/test/template/tag_helper_test.rb +34 -14
- data/test/template/text_helper_test.rb +83 -9
- data/test/template/translation_helper_test.rb +28 -0
- data/test/template/url_helper_test.rb +55 -18
- metadata +57 -18
- data/lib/action_view/helpers/javascripts/controls.js +0 -963
- data/lib/action_view/helpers/javascripts/dragdrop.js +0 -972
- data/lib/action_view/helpers/javascripts/effects.js +0 -1120
- data/lib/action_view/helpers/javascripts/prototype.js +0 -4225
- data/lib/action_view/partial_template.rb +0 -70
- data/lib/action_view/template_finder.rb +0 -177
- data/lib/action_view/template_handlers/compilable.rb +0 -128
- data/test/controller/custom_handler_test.rb +0 -45
- data/test/controller/new_render_test.rb +0 -945
- data/test/fixtures/test/block_content_for.erb +0 -2
- data/test/fixtures/test/erb_content_for.erb +0 -2
- data/test/template/deprecated_erb_variable_test.rb +0 -9
- data/test/template/template_finder_test.rb +0 -73
- data/test/template/template_object_test.rb +0 -95
@@ -1,57 +1,169 @@
|
|
1
1
|
require 'digest/md5'
|
2
2
|
|
3
|
-
module ActionController
|
4
|
-
|
3
|
+
module ActionController # :nodoc:
|
4
|
+
# Represents an HTTP response generated by a controller action. One can use an
|
5
|
+
# ActionController::AbstractResponse object to retrieve the current state of the
|
6
|
+
# response, or customize the response. An AbstractResponse object can either
|
7
|
+
# represent a "real" HTTP response (i.e. one that is meant to be sent back to the
|
8
|
+
# web browser) or a test response (i.e. one that is generated from integration
|
9
|
+
# tests). See CgiResponse and TestResponse, respectively.
|
10
|
+
#
|
11
|
+
# AbstractResponse is mostly a Ruby on Rails framework implement detail, and should
|
12
|
+
# never be used directly in controllers. Controllers should use the methods defined
|
13
|
+
# in ActionController::Base instead. For example, if you want to set the HTTP
|
14
|
+
# response's content MIME type, then use ActionControllerBase#headers instead of
|
15
|
+
# AbstractResponse#headers.
|
16
|
+
#
|
17
|
+
# Nevertheless, integration tests may want to inspect controller responses in more
|
18
|
+
# detail, and that's when AbstractResponse can be useful for application developers.
|
19
|
+
# Integration test methods such as ActionController::Integration::Session#get and
|
20
|
+
# ActionController::Integration::Session#post return objects of type TestResponse
|
21
|
+
# (which are of course also of type AbstractResponse).
|
22
|
+
#
|
23
|
+
# For example, the following demo integration "test" prints the body of the
|
24
|
+
# controller response to the console:
|
25
|
+
#
|
26
|
+
# class DemoControllerTest < ActionController::IntegrationTest
|
27
|
+
# def test_print_root_path_to_console
|
28
|
+
# get('/')
|
29
|
+
# puts @response.body
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
class AbstractResponse
|
5
33
|
DEFAULT_HEADERS = { "Cache-Control" => "no-cache" }
|
6
34
|
attr_accessor :request
|
7
|
-
|
35
|
+
|
36
|
+
# The body content (e.g. HTML) of the response, as a String.
|
37
|
+
attr_accessor :body
|
38
|
+
# The headers of the response, as a Hash. It maps header names to header values.
|
39
|
+
attr_accessor :headers
|
40
|
+
attr_accessor :session, :cookies, :assigns, :template, :layout
|
41
|
+
attr_accessor :redirected_to, :redirected_to_method_params
|
42
|
+
|
43
|
+
delegate :default_charset, :to => 'ActionController::Base'
|
8
44
|
|
9
45
|
def initialize
|
10
46
|
@body, @headers, @session, @assigns = "", DEFAULT_HEADERS.merge("cookie" => []), [], []
|
11
47
|
end
|
12
48
|
|
49
|
+
def status; headers['Status'] end
|
50
|
+
def status=(status) headers['Status'] = status end
|
51
|
+
|
52
|
+
def location; headers['Location'] end
|
53
|
+
def location=(url) headers['Location'] = url end
|
54
|
+
|
55
|
+
|
56
|
+
# Sets the HTTP response's content MIME type. For example, in the controller
|
57
|
+
# you could write this:
|
58
|
+
#
|
59
|
+
# response.content_type = "text/plain"
|
60
|
+
#
|
61
|
+
# If a character set has been defined for this response (see charset=) then
|
62
|
+
# the character set information will also be included in the content type
|
63
|
+
# information.
|
13
64
|
def content_type=(mime_type)
|
14
|
-
self.headers["Content-Type"] =
|
65
|
+
self.headers["Content-Type"] =
|
66
|
+
if mime_type =~ /charset/ || (c = charset).nil?
|
67
|
+
mime_type.to_s
|
68
|
+
else
|
69
|
+
"#{mime_type}; charset=#{c}"
|
70
|
+
end
|
15
71
|
end
|
16
|
-
|
72
|
+
|
73
|
+
# Returns the response's content MIME type, or nil if content type has been set.
|
17
74
|
def content_type
|
18
75
|
content_type = String(headers["Content-Type"] || headers["type"]).split(";")[0]
|
19
76
|
content_type.blank? ? nil : content_type
|
20
77
|
end
|
21
|
-
|
22
|
-
|
23
|
-
|
78
|
+
|
79
|
+
# Set the charset of the Content-Type header. Set to nil to remove it.
|
80
|
+
# If no content type is set, it defaults to HTML.
|
81
|
+
def charset=(charset)
|
82
|
+
headers["Content-Type"] =
|
83
|
+
if charset
|
84
|
+
"#{content_type || Mime::HTML}; charset=#{charset}"
|
85
|
+
else
|
86
|
+
content_type || Mime::HTML.to_s
|
87
|
+
end
|
24
88
|
end
|
25
|
-
|
89
|
+
|
26
90
|
def charset
|
27
91
|
charset = String(headers["Content-Type"] || headers["type"]).split(";")[1]
|
28
92
|
charset.blank? ? nil : charset.strip.split("=")[1]
|
29
93
|
end
|
30
94
|
|
31
|
-
def
|
32
|
-
|
33
|
-
|
95
|
+
def last_modified
|
96
|
+
if last = headers['Last-Modified']
|
97
|
+
Time.httpdate(last)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def last_modified?
|
102
|
+
headers.include?('Last-Modified')
|
103
|
+
end
|
34
104
|
|
35
|
-
|
105
|
+
def last_modified=(utc_time)
|
106
|
+
headers['Last-Modified'] = utc_time.httpdate
|
107
|
+
end
|
108
|
+
|
109
|
+
def etag
|
110
|
+
headers['ETag']
|
111
|
+
end
|
112
|
+
|
113
|
+
def etag?
|
114
|
+
headers.include?('ETag')
|
115
|
+
end
|
116
|
+
|
117
|
+
def etag=(etag)
|
118
|
+
headers['ETag'] = %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(etag))}")
|
119
|
+
end
|
120
|
+
|
121
|
+
def redirect(url, status)
|
122
|
+
self.status = status
|
123
|
+
self.location = url.gsub(/[\r\n]/, '')
|
124
|
+
self.body = "<html><body>You are being <a href=\"#{CGI.escapeHTML(url)}\">redirected</a>.</body></html>"
|
125
|
+
end
|
126
|
+
|
127
|
+
def sending_file?
|
128
|
+
headers["Content-Transfer-Encoding"] == "binary"
|
129
|
+
end
|
130
|
+
|
131
|
+
def assign_default_content_type_and_charset!
|
132
|
+
self.content_type ||= Mime::HTML
|
133
|
+
self.charset ||= default_charset unless sending_file?
|
36
134
|
end
|
37
135
|
|
38
136
|
def prepare!
|
137
|
+
assign_default_content_type_and_charset!
|
39
138
|
handle_conditional_get!
|
40
|
-
convert_content_type!
|
41
139
|
set_content_length!
|
140
|
+
convert_content_type!
|
42
141
|
end
|
43
142
|
|
44
|
-
|
45
143
|
private
|
46
|
-
def handle_conditional_get!
|
47
|
-
if
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
self.
|
54
|
-
|
144
|
+
def handle_conditional_get!
|
145
|
+
if etag? || last_modified?
|
146
|
+
set_conditional_cache_control!
|
147
|
+
elsif nonempty_ok_response?
|
148
|
+
self.etag = body
|
149
|
+
|
150
|
+
if request && request.etag_matches?(etag)
|
151
|
+
self.status = '304 Not Modified'
|
152
|
+
self.body = ''
|
153
|
+
end
|
154
|
+
|
155
|
+
set_conditional_cache_control!
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def nonempty_ok_response?
|
160
|
+
ok = !status || status[0..2] == '200'
|
161
|
+
ok && body.is_a?(String) && !body.empty?
|
162
|
+
end
|
163
|
+
|
164
|
+
def set_conditional_cache_control!
|
165
|
+
if headers['Cache-Control'] == DEFAULT_HEADERS['Cache-Control']
|
166
|
+
headers['Cache-Control'] = 'private, max-age=0, must-revalidate'
|
55
167
|
end
|
56
168
|
end
|
57
169
|
|
@@ -70,7 +182,9 @@ module ActionController
|
|
70
182
|
# Don't set the Content-Length for block-based bodies as that would mean reading it all into memory. Not nice
|
71
183
|
# for, say, a 2GB streaming file.
|
72
184
|
def set_content_length!
|
73
|
-
|
185
|
+
unless body.respond_to?(:call) || (status && status[0..2] == '304')
|
186
|
+
self.headers["Content-Length"] ||= body.size
|
187
|
+
end
|
74
188
|
end
|
75
189
|
end
|
76
|
-
end
|
190
|
+
end
|
@@ -201,7 +201,7 @@ module ActionController
|
|
201
201
|
# With conditions you can define restrictions on routes. Currently the only valid condition is <tt>:method</tt>.
|
202
202
|
#
|
203
203
|
# * <tt>:method</tt> - Allows you to specify which method can access the route. Possible values are <tt>:post</tt>,
|
204
|
-
# <tt>:get</tt>, <tt>:put</tt>, <tt>:delete</tt> and <tt>:any</tt>. The default value is <tt>:any</tt>,
|
204
|
+
# <tt>:get</tt>, <tt>:put</tt>, <tt>:delete</tt> and <tt>:any</tt>. The default value is <tt>:any</tt>,
|
205
205
|
# <tt>:any</tt> means that any method can access the route.
|
206
206
|
#
|
207
207
|
# Example:
|
@@ -213,7 +213,7 @@ module ActionController
|
|
213
213
|
#
|
214
214
|
# Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same
|
215
215
|
# URL will route to the <tt>show</tt> action.
|
216
|
-
#
|
216
|
+
#
|
217
217
|
# == Reloading routes
|
218
218
|
#
|
219
219
|
# You can reload routes if you feel you must:
|
@@ -281,9 +281,9 @@ module ActionController
|
|
281
281
|
end
|
282
282
|
|
283
283
|
class << self
|
284
|
-
|
285
|
-
|
286
|
-
|
284
|
+
# Expects an array of controller names as the first argument.
|
285
|
+
# Executes the passed block with only the named controllers named available.
|
286
|
+
# This method is used in internal Rails testing.
|
287
287
|
def with_controllers(names)
|
288
288
|
prior_controllers = @possible_controllers
|
289
289
|
use_controllers! names
|
@@ -292,10 +292,10 @@ module ActionController
|
|
292
292
|
use_controllers! prior_controllers
|
293
293
|
end
|
294
294
|
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
295
|
+
# Returns an array of paths, cleaned of double-slashes and relative path references.
|
296
|
+
# * "\\\" and "//" become "\\" or "/".
|
297
|
+
# * "/foo/bar/../config" becomes "/foo/config".
|
298
|
+
# The returned array is sorted by length, descending.
|
299
299
|
def normalize_paths(paths)
|
300
300
|
# do the hokey-pokey of path normalization...
|
301
301
|
paths = paths.collect do |path|
|
@@ -314,7 +314,7 @@ module ActionController
|
|
314
314
|
paths = paths.uniq.sort_by { |path| - path.length }
|
315
315
|
end
|
316
316
|
|
317
|
-
|
317
|
+
# Returns the array of controller names currently available to ActionController::Routing.
|
318
318
|
def possible_controllers
|
319
319
|
unless @possible_controllers
|
320
320
|
@possible_controllers = []
|
@@ -339,28 +339,27 @@ module ActionController
|
|
339
339
|
@possible_controllers
|
340
340
|
end
|
341
341
|
|
342
|
-
|
343
|
-
|
342
|
+
# Replaces the internal list of controllers available to ActionController::Routing with the passed argument.
|
343
|
+
# ActionController::Routing.use_controllers!([ "posts", "comments", "admin/comments" ])
|
344
344
|
def use_controllers!(controller_names)
|
345
345
|
@possible_controllers = controller_names
|
346
346
|
end
|
347
347
|
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
348
|
+
# Returns a controller path for a new +controller+ based on a +previous+ controller path.
|
349
|
+
# Handles 4 scenarios:
|
350
|
+
#
|
351
|
+
# * stay in the previous controller:
|
352
|
+
# controller_relative_to( nil, "groups/discussion" ) # => "groups/discussion"
|
353
|
+
#
|
354
|
+
# * stay in the previous namespace:
|
355
|
+
# controller_relative_to( "posts", "groups/discussion" ) # => "groups/posts"
|
356
|
+
#
|
357
|
+
# * forced move to the root namespace:
|
358
|
+
# controller_relative_to( "/posts", "groups/discussion" ) # => "posts"
|
359
|
+
#
|
360
|
+
# * previous namespace is root:
|
361
|
+
# controller_relative_to( "posts", "anything_with_no_slashes" ) # =>"posts"
|
362
|
+
#
|
364
363
|
def controller_relative_to(controller, previous)
|
365
364
|
if controller.nil? then previous
|
366
365
|
elsif controller[0] == ?/ then controller[1..-1]
|
@@ -369,12 +368,11 @@ module ActionController
|
|
369
368
|
end
|
370
369
|
end
|
371
370
|
end
|
372
|
-
|
373
371
|
|
374
372
|
Routes = RouteSet.new
|
375
373
|
|
376
374
|
ActiveSupport::Inflector.module_eval do
|
377
|
-
|
375
|
+
# Ensures that routes are reloaded when Rails inflections are updated.
|
378
376
|
def inflections_with_route_reloading(&block)
|
379
377
|
returning(inflections_without_route_reloading(&block)) {
|
380
378
|
ActionController::Routing::Routes.reload! if block_given?
|
@@ -1,23 +1,16 @@
|
|
1
1
|
module ActionController
|
2
2
|
module Routing
|
3
3
|
class RouteBuilder #:nodoc:
|
4
|
-
|
4
|
+
attr_reader :separators, :optional_separators
|
5
|
+
attr_reader :separator_regexp, :nonseparator_regexp, :interval_regexp
|
5
6
|
|
6
7
|
def initialize
|
7
|
-
|
8
|
-
|
9
|
-
end
|
10
|
-
|
11
|
-
def separator_pattern(inverted = false)
|
12
|
-
"[#{'^' if inverted}#{Regexp.escape(separators.join)}]"
|
13
|
-
end
|
8
|
+
@separators = Routing::SEPARATORS
|
9
|
+
@optional_separators = %w( / )
|
14
10
|
|
15
|
-
|
16
|
-
Regexp.
|
17
|
-
|
18
|
-
|
19
|
-
def multiline_regexp?(expression)
|
20
|
-
expression.options & Regexp::MULTILINE == Regexp::MULTILINE
|
11
|
+
@separator_regexp = /[#{Regexp.escape(separators.join)}]/
|
12
|
+
@nonseparator_regexp = /\A([^#{Regexp.escape(separators.join)}]+)/
|
13
|
+
@interval_regexp = /(.*?)(#{separator_regexp}|$)/
|
21
14
|
end
|
22
15
|
|
23
16
|
# Accepts a "route path" (a string defining a route), and returns the array
|
@@ -30,7 +23,7 @@ module ActionController
|
|
30
23
|
rest, segments = path, []
|
31
24
|
|
32
25
|
until rest.empty?
|
33
|
-
segment, rest = segment_for
|
26
|
+
segment, rest = segment_for(rest)
|
34
27
|
segments << segment
|
35
28
|
end
|
36
29
|
segments
|
@@ -39,24 +32,20 @@ module ActionController
|
|
39
32
|
# A factory method that returns a new segment instance appropriate for the
|
40
33
|
# format of the given string.
|
41
34
|
def segment_for(string)
|
42
|
-
segment =
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
returning segment = DividerSegment.new($&) do
|
57
|
-
segment.is_optional = (optional_separators.include? $&)
|
58
|
-
end
|
59
|
-
end
|
35
|
+
segment =
|
36
|
+
case string
|
37
|
+
when /\A:(\w+)/
|
38
|
+
key = $1.to_sym
|
39
|
+
key == :controller ? ControllerSegment.new(key) : DynamicSegment.new(key)
|
40
|
+
when /\A\*(\w+)/
|
41
|
+
PathSegment.new($1.to_sym, :optional => true)
|
42
|
+
when /\A\?(.*?)\?/
|
43
|
+
StaticSegment.new($1, :optional => true)
|
44
|
+
when nonseparator_regexp
|
45
|
+
StaticSegment.new($1)
|
46
|
+
when separator_regexp
|
47
|
+
DividerSegment.new($&, :optional => optional_separators.include?($&))
|
48
|
+
end
|
60
49
|
[segment, $~.post_match]
|
61
50
|
end
|
62
51
|
|
@@ -64,18 +53,18 @@ module ActionController
|
|
64
53
|
# segments are passed alongside in order to distinguish between default values
|
65
54
|
# and requirements.
|
66
55
|
def divide_route_options(segments, options)
|
67
|
-
options = options.
|
56
|
+
options = options.except(:path_prefix, :name_prefix)
|
68
57
|
|
69
58
|
if options[:namespace]
|
70
59
|
options[:controller] = "#{options.delete(:namespace).sub(/\/$/, '')}/#{options[:controller]}"
|
71
|
-
options.delete(:path_prefix)
|
72
|
-
options.delete(:name_prefix)
|
73
60
|
end
|
74
61
|
|
75
62
|
requirements = (options.delete(:requirements) || {}).dup
|
76
63
|
defaults = (options.delete(:defaults) || {}).dup
|
77
64
|
conditions = (options.delete(:conditions) || {}).dup
|
78
65
|
|
66
|
+
validate_route_conditions(conditions)
|
67
|
+
|
79
68
|
path_keys = segments.collect { |segment| segment.key if segment.respond_to?(:key) }.compact
|
80
69
|
options.each do |key, value|
|
81
70
|
hash = (path_keys.include?(key) && ! value.is_a?(Regexp)) ? defaults : requirements
|
@@ -102,7 +91,7 @@ module ActionController
|
|
102
91
|
if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
|
103
92
|
raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
|
104
93
|
end
|
105
|
-
if
|
94
|
+
if requirement.multiline?
|
106
95
|
raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
|
107
96
|
end
|
108
97
|
segment.regexp = requirement
|
@@ -174,30 +163,32 @@ module ActionController
|
|
174
163
|
defaults, requirements, conditions = divide_route_options(segments, options)
|
175
164
|
requirements = assign_route_options(segments, defaults, requirements)
|
176
165
|
|
177
|
-
|
166
|
+
# TODO: Segments should be frozen on initialize
|
167
|
+
segments.each { |segment| segment.freeze }
|
178
168
|
|
179
|
-
route.segments
|
180
|
-
route.requirements = requirements
|
181
|
-
route.conditions = conditions
|
182
|
-
|
183
|
-
if !route.significant_keys.include?(:action) && !route.requirements[:action]
|
184
|
-
route.requirements[:action] = "index"
|
185
|
-
route.significant_keys << :action
|
186
|
-
end
|
187
|
-
|
188
|
-
# Routes cannot use the current string interpolation method
|
189
|
-
# if there are user-supplied <tt>:requirements</tt> as the interpolation
|
190
|
-
# code won't raise RoutingErrors when generating
|
191
|
-
if options.key?(:requirements) || route.requirements.keys.to_set != Routing::ALLOWED_REQUIREMENTS_FOR_OPTIMISATION
|
192
|
-
route.optimise = false
|
193
|
-
end
|
169
|
+
route = Route.new(segments, requirements, conditions)
|
194
170
|
|
195
171
|
if !route.significant_keys.include?(:controller)
|
196
172
|
raise ArgumentError, "Illegal route: the :controller must be specified!"
|
197
173
|
end
|
198
174
|
|
199
|
-
route
|
175
|
+
route.freeze
|
200
176
|
end
|
177
|
+
|
178
|
+
private
|
179
|
+
def validate_route_conditions(conditions)
|
180
|
+
if method = conditions[:method]
|
181
|
+
[method].flatten.each do |m|
|
182
|
+
if m == :head
|
183
|
+
raise ArgumentError, "HTTP method HEAD is invalid in route conditions. Rails processes HEAD requests the same as GETs, returning just the response headers"
|
184
|
+
end
|
185
|
+
|
186
|
+
unless HTTP_METHODS.include?(m.to_sym)
|
187
|
+
raise ArgumentError, "Invalid HTTP method specified in route conditions: #{conditions.inspect}"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
201
192
|
end
|
202
193
|
end
|
203
194
|
end
|