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.

Files changed (200) hide show
  1. data/CHANGELOG +223 -7
  2. data/README +6 -12
  3. data/Rakefile +11 -11
  4. data/lib/action_controller.rb +9 -9
  5. data/lib/action_controller/assertions/response_assertions.rb +29 -78
  6. data/lib/action_controller/assertions/routing_assertions.rb +33 -33
  7. data/lib/action_controller/assertions/selector_assertions.rb +9 -5
  8. data/lib/action_controller/base.rb +227 -161
  9. data/lib/action_controller/benchmarking.rb +37 -24
  10. data/lib/action_controller/caching/actions.rb +53 -21
  11. data/lib/action_controller/caching/fragments.rb +10 -36
  12. data/lib/action_controller/caching/sweeping.rb +3 -3
  13. data/lib/action_controller/cgi_ext/session.rb +2 -22
  14. data/lib/action_controller/cgi_process.rb +8 -46
  15. data/lib/action_controller/components.rb +4 -1
  16. data/lib/action_controller/cookies.rb +10 -0
  17. data/lib/action_controller/dispatcher.rb +49 -15
  18. data/lib/action_controller/filters.rb +48 -10
  19. data/lib/action_controller/headers.rb +16 -14
  20. data/lib/action_controller/helpers.rb +2 -2
  21. data/lib/action_controller/http_authentication.rb +1 -1
  22. data/lib/action_controller/integration.rb +57 -60
  23. data/lib/action_controller/layout.rb +27 -53
  24. data/lib/action_controller/mime_responds.rb +5 -1
  25. data/lib/action_controller/mime_type.rb +64 -42
  26. data/lib/action_controller/mime_types.rb +2 -1
  27. data/lib/action_controller/performance_test.rb +16 -0
  28. data/lib/action_controller/polymorphic_routes.rb +16 -9
  29. data/lib/action_controller/rack_process.rb +303 -0
  30. data/lib/action_controller/request.rb +205 -97
  31. data/lib/action_controller/request_forgery_protection.rb +2 -2
  32. data/lib/action_controller/request_profiler.rb +0 -0
  33. data/lib/action_controller/rescue.rb +20 -115
  34. data/lib/action_controller/resources.rb +186 -83
  35. data/lib/action_controller/response.rb +140 -26
  36. data/lib/action_controller/routing.rb +28 -30
  37. data/lib/action_controller/routing/builder.rb +45 -54
  38. data/lib/action_controller/routing/optimisations.rb +31 -21
  39. data/lib/action_controller/routing/recognition_optimisation.rb +33 -27
  40. data/lib/action_controller/routing/route.rb +162 -147
  41. data/lib/action_controller/routing/route_set.rb +8 -7
  42. data/lib/action_controller/routing/routing_ext.rb +4 -1
  43. data/lib/action_controller/routing/segments.rb +50 -21
  44. data/lib/action_controller/session/cookie_store.rb +3 -2
  45. data/lib/action_controller/session/drb_server.rb +7 -7
  46. data/lib/action_controller/session_management.rb +6 -2
  47. data/lib/action_controller/streaming.rb +15 -8
  48. data/lib/action_controller/templates/rescues/diagnostics.erb +2 -2
  49. data/lib/action_controller/templates/rescues/template_error.erb +2 -2
  50. data/lib/action_controller/test_case.rb +66 -2
  51. data/lib/action_controller/test_process.rb +71 -66
  52. data/lib/action_controller/translation.rb +13 -0
  53. data/lib/action_controller/url_rewriter.rb +90 -13
  54. data/lib/action_controller/vendor/html-scanner/html/node.rb +9 -2
  55. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +1 -1
  56. data/lib/action_controller/vendor/html-scanner/html/selector.rb +2 -2
  57. data/lib/action_controller/verification.rb +2 -2
  58. data/lib/action_pack/version.rb +1 -1
  59. data/lib/action_view.rb +19 -11
  60. data/lib/action_view/base.rb +184 -150
  61. data/lib/action_view/helpers.rb +38 -0
  62. data/lib/action_view/helpers/active_record_helper.rb +56 -27
  63. data/lib/action_view/helpers/asset_tag_helper.rb +356 -153
  64. data/lib/action_view/helpers/atom_feed_helper.rb +74 -19
  65. data/lib/action_view/helpers/benchmark_helper.rb +3 -3
  66. data/lib/action_view/helpers/cache_helper.rb +1 -2
  67. data/lib/action_view/helpers/capture_helper.rb +19 -44
  68. data/lib/action_view/helpers/date_helper.rb +486 -296
  69. data/lib/action_view/helpers/debug_helper.rb +20 -13
  70. data/lib/action_view/helpers/form_helper.rb +71 -30
  71. data/lib/action_view/helpers/form_options_helper.rb +15 -85
  72. data/lib/action_view/helpers/form_tag_helper.rb +61 -38
  73. data/lib/action_view/helpers/javascript_helper.rb +80 -89
  74. data/lib/action_view/helpers/number_helper.rb +179 -74
  75. data/lib/action_view/helpers/prototype_helper.rb +216 -201
  76. data/lib/action_view/helpers/record_tag_helper.rb +4 -5
  77. data/lib/action_view/helpers/sanitize_helper.rb +65 -33
  78. data/lib/action_view/helpers/scriptaculous_helper.rb +2 -2
  79. data/lib/action_view/helpers/tag_helper.rb +39 -22
  80. data/lib/action_view/helpers/text_helper.rb +212 -118
  81. data/lib/action_view/helpers/translation_helper.rb +21 -0
  82. data/lib/action_view/helpers/url_helper.rb +100 -58
  83. data/lib/action_view/inline_template.rb +13 -14
  84. data/lib/action_view/locale/en.yml +91 -0
  85. data/lib/action_view/partials.rb +100 -55
  86. data/lib/action_view/paths.rb +125 -0
  87. data/lib/action_view/renderable.rb +102 -0
  88. data/lib/action_view/renderable_partial.rb +48 -0
  89. data/lib/action_view/template.rb +90 -101
  90. data/lib/action_view/template_error.rb +11 -21
  91. data/lib/action_view/template_handler.rb +8 -28
  92. data/lib/action_view/template_handlers.rb +45 -0
  93. data/lib/action_view/template_handlers/builder.rb +5 -15
  94. data/lib/action_view/template_handlers/erb.rb +9 -6
  95. data/lib/action_view/template_handlers/rjs.rb +2 -17
  96. data/lib/action_view/test_case.rb +7 -4
  97. data/test/abstract_unit.rb +4 -1
  98. data/test/active_record_unit.rb +28 -30
  99. data/test/activerecord/render_partial_with_record_identification_test.rb +25 -12
  100. data/test/controller/action_pack_assertions_test.rb +8 -37
  101. data/test/controller/addresses_render_test.rb +0 -3
  102. data/test/controller/assert_select_test.rb +51 -24
  103. data/test/controller/base_test.rb +4 -4
  104. data/test/controller/caching_test.rb +136 -66
  105. data/test/controller/capture_test.rb +1 -21
  106. data/test/controller/cgi_test.rb +157 -10
  107. data/test/controller/components_test.rb +41 -25
  108. data/test/controller/content_type_test.rb +49 -17
  109. data/test/controller/cookie_test.rb +1 -1
  110. data/test/controller/deprecation/deprecated_base_methods_test.rb +0 -3
  111. data/test/controller/dispatcher_test.rb +9 -1
  112. data/test/controller/filter_params_test.rb +2 -2
  113. data/test/controller/filters_test.rb +13 -13
  114. data/test/controller/html-scanner/cdata_node_test.rb +15 -0
  115. data/test/controller/html-scanner/node_test.rb +21 -0
  116. data/test/controller/html-scanner/sanitizer_test.rb +14 -0
  117. data/test/controller/integration_test.rb +167 -6
  118. data/test/controller/layout_test.rb +11 -68
  119. data/test/controller/logging_test.rb +46 -0
  120. data/test/controller/mime_responds_test.rb +61 -59
  121. data/test/controller/mime_type_test.rb +6 -6
  122. data/test/controller/polymorphic_routes_test.rb +37 -2
  123. data/test/controller/rack_test.rb +323 -0
  124. data/test/controller/redirect_test.rb +72 -71
  125. data/test/controller/render_test.rb +1120 -108
  126. data/test/controller/request_forgery_protection_test.rb +66 -52
  127. data/test/controller/request_test.rb +103 -146
  128. data/test/controller/rescue_test.rb +20 -24
  129. data/test/controller/resources_test.rb +408 -25
  130. data/test/controller/routing_test.rb +1774 -1774
  131. data/test/controller/send_file_test.rb +0 -4
  132. data/test/controller/session/cookie_store_test.rb +53 -1
  133. data/test/controller/test_test.rb +15 -37
  134. data/test/controller/translation_test.rb +26 -0
  135. data/test/controller/url_rewriter_test.rb +27 -28
  136. data/test/controller/view_paths_test.rb +48 -47
  137. data/test/fixtures/_top_level_partial.html.erb +1 -0
  138. data/test/fixtures/_top_level_partial_only.erb +1 -0
  139. data/test/fixtures/developers/_developer.erb +1 -0
  140. data/test/fixtures/fun/games/_game.erb +1 -0
  141. data/test/fixtures/fun/serious/games/_game.erb +1 -0
  142. data/test/fixtures/functional_caching/formatted_fragment_cached.html.erb +3 -0
  143. data/test/fixtures/functional_caching/formatted_fragment_cached.js.rjs +6 -0
  144. data/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder +5 -0
  145. data/test/fixtures/functional_caching/inline_fragment_cached.html.erb +2 -0
  146. data/test/fixtures/layouts/_column.html.erb +2 -0
  147. data/test/fixtures/projects/_project.erb +1 -0
  148. data/test/fixtures/public/javascripts/subdir/subdir.js +1 -0
  149. data/test/fixtures/public/stylesheets/subdir/subdir.css +1 -0
  150. data/test/fixtures/replies/_reply.erb +1 -0
  151. data/test/fixtures/test/_counter.html.erb +1 -0
  152. data/test/fixtures/test/_customer.erb +1 -1
  153. data/test/fixtures/test/_customer_with_var.erb +1 -0
  154. data/test/fixtures/test/_layout_for_block_with_args.html.erb +3 -0
  155. data/test/fixtures/test/_local_inspector.html.erb +1 -0
  156. data/test/fixtures/test/_partial_with_only_html_version.html.erb +1 -0
  157. data/test/fixtures/test/hello.builder +1 -1
  158. data/test/fixtures/test/hyphen-ated.erb +1 -0
  159. data/test/fixtures/test/implicit_content_type.atom.builder +2 -0
  160. data/test/fixtures/test/nested_layout.erb +3 -0
  161. data/test/fixtures/test/non_erb_block_content_for.builder +1 -1
  162. data/test/fixtures/test/sub_template_raise.html.erb +1 -0
  163. data/test/fixtures/test/template.erb +1 -0
  164. data/test/fixtures/test/using_layout_around_block_with_args.html.erb +1 -0
  165. data/test/template/active_record_helper_i18n_test.rb +46 -0
  166. data/test/template/active_record_helper_test.rb +24 -24
  167. data/test/template/asset_tag_helper_test.rb +161 -29
  168. data/test/template/atom_feed_helper_test.rb +114 -5
  169. data/test/template/compiled_templates_test.rb +59 -0
  170. data/test/template/date_helper_i18n_test.rb +113 -0
  171. data/test/template/date_helper_test.rb +403 -109
  172. data/test/template/form_helper_test.rb +213 -154
  173. data/test/template/form_options_helper_test.rb +249 -897
  174. data/test/template/form_tag_helper_test.rb +80 -32
  175. data/test/template/javascript_helper_test.rb +17 -18
  176. data/test/template/number_helper_i18n_test.rb +54 -0
  177. data/test/template/number_helper_test.rb +43 -13
  178. data/test/template/prototype_helper_test.rb +101 -84
  179. data/test/template/record_tag_helper_test.rb +24 -20
  180. data/test/template/render_test.rb +193 -0
  181. data/test/template/sanitize_helper_test.rb +3 -3
  182. data/test/template/tag_helper_test.rb +34 -14
  183. data/test/template/text_helper_test.rb +83 -9
  184. data/test/template/translation_helper_test.rb +28 -0
  185. data/test/template/url_helper_test.rb +55 -18
  186. metadata +57 -18
  187. data/lib/action_view/helpers/javascripts/controls.js +0 -963
  188. data/lib/action_view/helpers/javascripts/dragdrop.js +0 -972
  189. data/lib/action_view/helpers/javascripts/effects.js +0 -1120
  190. data/lib/action_view/helpers/javascripts/prototype.js +0 -4225
  191. data/lib/action_view/partial_template.rb +0 -70
  192. data/lib/action_view/template_finder.rb +0 -177
  193. data/lib/action_view/template_handlers/compilable.rb +0 -128
  194. data/test/controller/custom_handler_test.rb +0 -45
  195. data/test/controller/new_render_test.rb +0 -945
  196. data/test/fixtures/test/block_content_for.erb +0 -2
  197. data/test/fixtures/test/erb_content_for.erb +0 -2
  198. data/test/template/deprecated_erb_variable_test.rb +0 -9
  199. data/test/template/template_finder_test.rb +0 -73
  200. data/test/template/template_object_test.rb +0 -95
@@ -1,57 +1,169 @@
1
1
  require 'digest/md5'
2
2
 
3
- module ActionController
4
- class AbstractResponse #:nodoc:
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
- attr_accessor :body, :headers, :session, :cookies, :assigns, :template, :redirected_to, :redirected_to_method_params, :layout
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"] = charset ? "#{mime_type}; charset=#{charset}" : mime_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
- def charset=(encoding)
23
- self.headers["Content-Type"] = "#{content_type || Mime::HTML}; charset=#{encoding}"
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 redirect(to_url, response_status)
32
- self.headers["Status"] = response_status
33
- self.headers["Location"] = to_url.gsub(/[\r\n]/, '')
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
- self.body = "<html><body>You are being <a href=\"#{CGI.escapeHTML(to_url)}\">redirected</a>.</body></html>"
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 body.is_a?(String) && (headers['Status'] ? headers['Status'][0..2] == '200' : true) && !body.empty?
48
- self.headers['ETag'] ||= %("#{Digest::MD5.hexdigest(body)}")
49
- self.headers['Cache-Control'] = 'private, max-age=0, must-revalidate' if headers['Cache-Control'] == DEFAULT_HEADERS['Cache-Control']
50
-
51
- if request.headers['HTTP_IF_NONE_MATCH'] == headers['ETag']
52
- self.headers['Status'] = '304 Not Modified'
53
- self.body = ''
54
- end
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
- self.headers["Content-Length"] = body.size unless body.respond_to?(:call)
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
- # 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.
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
- # 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.
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
- # Returns the array of controller names currently available to ActionController::Routing.
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
- # Replaces the internal list of controllers available to ActionController::Routing with the passed argument.
343
- # ActionController::Routing.use_controllers!([ "posts", "comments", "admin/comments" ])
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
- # 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
- #
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
- # Ensures that routes are reloaded when Rails inflections are updated.
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
- attr_accessor :separators, :optional_separators
4
+ attr_reader :separators, :optional_separators
5
+ attr_reader :separator_regexp, :nonseparator_regexp, :interval_regexp
5
6
 
6
7
  def initialize
7
- self.separators = Routing::SEPARATORS
8
- self.optional_separators = %w( / )
9
- end
10
-
11
- def separator_pattern(inverted = false)
12
- "[#{'^' if inverted}#{Regexp.escape(separators.join)}]"
13
- end
8
+ @separators = Routing::SEPARATORS
9
+ @optional_separators = %w( / )
14
10
 
15
- def interval_regexp
16
- Regexp.new "(.*?)(#{separators.source}|$)"
17
- end
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 rest
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 = case string
43
- when /\A:(\w+)/
44
- key = $1.to_sym
45
- case key
46
- when :controller then ControllerSegment.new(key)
47
- else DynamicSegment.new key
48
- end
49
- when /\A\*(\w+)/ then PathSegment.new($1.to_sym, :optional => true)
50
- when /\A\?(.*?)\?/
51
- returning segment = StaticSegment.new($1) do
52
- segment.is_optional = true
53
- end
54
- when /\A(#{separator_pattern(:inverted)}+)/ then StaticSegment.new($1)
55
- when Regexp.new(separator_pattern) then
56
- returning segment = DividerSegment.new($&) do
57
- segment.is_optional = (optional_separators.include? $&)
58
- end
59
- end
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.dup
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 multiline_regexp?(requirement)
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
- route = Route.new
166
+ # TODO: Segments should be frozen on initialize
167
+ segments.each { |segment| segment.freeze }
178
168
 
179
- route.segments = segments
180
- route.requirements = requirements
181
- route.conditions = conditions
182
-
183
- if !route.significant_keys.include?(:action) && !route.requirements[:action]
184
- route.requirements[:action] = "index"
185
- route.significant_keys << :action
186
- end
187
-
188
- # Routes cannot use the current string interpolation method
189
- # if there are user-supplied <tt>:requirements</tt> as the interpolation
190
- # code won't raise RoutingErrors when generating
191
- if options.key?(:requirements) || route.requirements.keys.to_set != Routing::ALLOWED_REQUIREMENTS_FOR_OPTIMISATION
192
- route.optimise = false
193
- end
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