actionpack 3.1.12 → 3.2.0.rc1

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 (128) hide show
  1. data/CHANGELOG.md +5503 -108
  2. data/README.rdoc +3 -3
  3. data/lib/abstract_controller/asset_paths.rb +1 -1
  4. data/lib/abstract_controller/base.rb +1 -1
  5. data/lib/abstract_controller/callbacks.rb +102 -18
  6. data/lib/abstract_controller/helpers.rb +1 -1
  7. data/lib/abstract_controller/layouts.rb +116 -50
  8. data/lib/abstract_controller/logger.rb +1 -1
  9. data/lib/abstract_controller/railties/routes_helpers.rb +2 -2
  10. data/lib/abstract_controller/rendering.rb +1 -6
  11. data/lib/abstract_controller/view_paths.rb +6 -5
  12. data/lib/action_controller.rb +0 -15
  13. data/lib/action_controller/caching.rb +0 -1
  14. data/lib/action_controller/caching/actions.rb +5 -6
  15. data/lib/action_controller/caching/fragments.rb +18 -18
  16. data/lib/action_controller/caching/pages.rb +7 -6
  17. data/lib/action_controller/caching/sweeping.rb +1 -1
  18. data/lib/action_controller/log_subscriber.rb +8 -4
  19. data/lib/action_controller/metal.rb +7 -1
  20. data/lib/action_controller/metal/conditional_get.rb +49 -4
  21. data/lib/action_controller/metal/data_streaming.rb +17 -5
  22. data/lib/action_controller/metal/force_ssl.rb +8 -5
  23. data/lib/action_controller/metal/helpers.rb +7 -4
  24. data/lib/action_controller/metal/http_authentication.rb +9 -12
  25. data/lib/action_controller/metal/instrumentation.rb +9 -4
  26. data/lib/action_controller/metal/mime_responds.rb +4 -4
  27. data/lib/action_controller/metal/params_wrapper.rb +12 -8
  28. data/lib/action_controller/metal/redirecting.rb +7 -6
  29. data/lib/action_controller/metal/renderers.rb +9 -11
  30. data/lib/action_controller/metal/request_forgery_protection.rb +2 -1
  31. data/lib/action_controller/metal/rescue.rb +13 -0
  32. data/lib/action_controller/metal/responder.rb +11 -23
  33. data/lib/action_controller/metal/streaming.rb +0 -25
  34. data/lib/action_controller/railtie.rb +1 -0
  35. data/lib/action_controller/railties/paths.rb +4 -3
  36. data/lib/action_controller/record_identifier.rb +4 -4
  37. data/lib/action_controller/test_case.rb +60 -56
  38. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +6 -6
  39. data/lib/action_dispatch.rb +5 -1
  40. data/lib/action_dispatch/http/cache.rb +27 -15
  41. data/lib/action_dispatch/http/filter_parameters.rb +3 -1
  42. data/lib/action_dispatch/http/headers.rb +3 -5
  43. data/lib/action_dispatch/http/mime_negotiation.rb +2 -1
  44. data/lib/action_dispatch/http/mime_type.rb +7 -3
  45. data/lib/action_dispatch/http/mime_types.rb +12 -0
  46. data/lib/action_dispatch/http/parameter_filter.rb +3 -1
  47. data/lib/action_dispatch/http/parameters.rb +0 -4
  48. data/lib/action_dispatch/http/request.rb +18 -68
  49. data/lib/action_dispatch/http/response.rb +11 -32
  50. data/lib/action_dispatch/http/upload.rb +3 -14
  51. data/lib/action_dispatch/http/url.rb +1 -1
  52. data/lib/action_dispatch/middleware/callbacks.rb +1 -2
  53. data/lib/action_dispatch/middleware/cookies.rb +20 -16
  54. data/lib/action_dispatch/middleware/debug_exceptions.rb +82 -0
  55. data/lib/action_dispatch/middleware/exception_wrapper.rb +78 -0
  56. data/lib/action_dispatch/middleware/flash.rb +6 -9
  57. data/lib/action_dispatch/middleware/params_parser.rb +6 -11
  58. data/lib/action_dispatch/middleware/public_exceptions.rb +30 -0
  59. data/lib/action_dispatch/middleware/reloader.rb +38 -14
  60. data/lib/action_dispatch/middleware/remote_ip.rb +66 -36
  61. data/lib/action_dispatch/middleware/request_id.rb +39 -0
  62. data/lib/action_dispatch/middleware/session/abstract_store.rb +4 -16
  63. data/lib/action_dispatch/middleware/session/cache_store.rb +50 -0
  64. data/lib/action_dispatch/middleware/session/cookie_store.rb +1 -1
  65. data/lib/action_dispatch/middleware/show_exceptions.rb +58 -142
  66. data/lib/action_dispatch/middleware/static.rb +2 -10
  67. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +1 -0
  68. data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +13 -8
  69. data/lib/action_dispatch/railtie.rb +15 -1
  70. data/lib/action_dispatch/routing.rb +1 -2
  71. data/lib/action_dispatch/routing/mapper.rb +108 -107
  72. data/lib/action_dispatch/routing/redirection.rb +63 -69
  73. data/lib/action_dispatch/routing/route_set.rb +75 -43
  74. data/lib/action_dispatch/routing/routes_proxy.rb +0 -4
  75. data/lib/action_dispatch/routing/url_for.rb +3 -3
  76. data/lib/action_dispatch/testing/assertions/response.rb +5 -7
  77. data/lib/action_dispatch/testing/assertions/routing.rb +10 -9
  78. data/lib/action_dispatch/testing/integration.rb +8 -25
  79. data/lib/action_dispatch/testing/test_process.rb +3 -2
  80. data/lib/action_dispatch/testing/test_request.rb +4 -23
  81. data/lib/action_pack/version.rb +3 -3
  82. data/lib/action_view.rb +1 -5
  83. data/lib/action_view/asset_paths.rb +7 -8
  84. data/lib/action_view/base.rb +7 -5
  85. data/lib/action_view/helpers/asset_paths.rb +1 -1
  86. data/lib/action_view/helpers/asset_tag_helper.rb +4 -8
  87. data/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +3 -0
  88. data/lib/action_view/helpers/atom_feed_helper.rb +2 -2
  89. data/lib/action_view/helpers/capture_helper.rb +3 -3
  90. data/lib/action_view/helpers/controller_helper.rb +1 -1
  91. data/lib/action_view/helpers/date_helper.rb +26 -18
  92. data/lib/action_view/helpers/debug_helper.rb +1 -1
  93. data/lib/action_view/helpers/form_helper.rb +71 -13
  94. data/lib/action_view/helpers/form_options_helper.rb +65 -34
  95. data/lib/action_view/helpers/form_tag_helper.rb +24 -18
  96. data/lib/action_view/helpers/javascript_helper.rb +12 -3
  97. data/lib/action_view/helpers/number_helper.rb +3 -2
  98. data/lib/action_view/helpers/record_tag_helper.rb +51 -5
  99. data/lib/action_view/helpers/rendering_helper.rb +2 -2
  100. data/lib/action_view/helpers/sanitize_helper.rb +6 -7
  101. data/lib/action_view/helpers/tag_helper.rb +1 -1
  102. data/lib/action_view/helpers/text_helper.rb +5 -4
  103. data/lib/action_view/helpers/url_helper.rb +19 -11
  104. data/lib/action_view/locale/en.yml +6 -0
  105. data/lib/action_view/log_subscriber.rb +1 -1
  106. data/lib/action_view/lookup_context.rb +123 -125
  107. data/lib/action_view/path_set.rb +60 -13
  108. data/lib/action_view/renderer/abstract_renderer.rb +16 -11
  109. data/lib/action_view/renderer/partial_renderer.rb +59 -40
  110. data/lib/action_view/renderer/template_renderer.rb +29 -17
  111. data/lib/action_view/template.rb +0 -1
  112. data/lib/action_view/template/error.rb +6 -5
  113. data/lib/action_view/template/handlers.rb +0 -6
  114. data/lib/action_view/template/handlers/builder.rb +10 -1
  115. data/lib/action_view/template/handlers/erb.rb +2 -2
  116. data/lib/action_view/template/resolver.rb +20 -31
  117. data/lib/action_view/test_case.rb +7 -10
  118. data/lib/sprockets/assets.rake +1 -1
  119. data/lib/sprockets/bootstrap.rb +3 -31
  120. data/lib/sprockets/compressors.rb +69 -7
  121. data/lib/sprockets/helpers/rails_helper.rb +6 -11
  122. data/lib/sprockets/railtie.rb +1 -0
  123. data/lib/sprockets/static_compiler.rb +0 -3
  124. metadata +57 -86
  125. checksums.yaml +0 -7
  126. data/lib/action_dispatch/middleware/closed_error.rb +0 -7
  127. data/lib/action_dispatch/routing/route.rb +0 -67
  128. data/lib/action_view/template/handler.rb +0 -49
@@ -11,14 +11,14 @@ module ActionDispatch
11
11
  def match?(path)
12
12
  path = path.dup
13
13
 
14
- full_path = path.empty? ? @root : File.join(@root, escape_glob_chars(unescape_path(path)))
14
+ full_path = path.empty? ? @root : File.join(@root, ::Rack::Utils.unescape(path))
15
15
  paths = "#{full_path}#{ext}"
16
16
 
17
17
  matches = Dir[paths]
18
18
  match = matches.detect { |m| File.file?(m) }
19
19
  if match
20
20
  match.sub!(@compiled_root, '')
21
- ::Rack::Utils.escape(match)
21
+ match
22
22
  end
23
23
  end
24
24
 
@@ -32,14 +32,6 @@ module ActionDispatch
32
32
  "{,#{ext},/index#{ext}}"
33
33
  end
34
34
  end
35
-
36
- def unescape_path(path)
37
- URI.parser.unescape(path)
38
- end
39
-
40
- def escape_glob_chars(path)
41
- path.gsub(/[*?{}\[\]]/, "\\\\\\&")
42
- end
43
35
  end
44
36
 
45
37
  class Static
@@ -16,6 +16,7 @@
16
16
  background-color: #eee;
17
17
  padding: 10px;
18
18
  font-size: 11px;
19
+ white-space: pre-wrap;
19
20
  }
20
21
 
21
22
  a { color: #000; }
@@ -1,10 +1,15 @@
1
1
  <h1>Routing Error</h1>
2
2
  <p><pre><%=h @exception.message %></pre></p>
3
- <% unless @exception.failures.empty? %><p>
4
- <h2>Failure reasons:</h2>
5
- <ol>
6
- <% @exception.failures.each do |route, reason| %>
7
- <li><code><%=h route.inspect.gsub('\\', '') %></code> failed because <%=h reason.downcase %></li>
8
- <% end %>
9
- </ol>
10
- </p><% end %>
3
+ <% unless @exception.failures.empty? %>
4
+ <p>
5
+ <h2>Failure reasons:</h2>
6
+ <ol>
7
+ <% @exception.failures.each do |route, reason| %>
8
+ <li><code><%=h route.inspect.gsub('\\', '') %></code> failed because <%=h reason.downcase %></li>
9
+ <% end %>
10
+ </ol>
11
+ </p>
12
+ <% end %>
13
+ <p>
14
+ Try running <code>rake routes</code> for more information on available routes.
15
+ </p>
@@ -9,11 +9,25 @@ module ActionDispatch
9
9
  config.action_dispatch.best_standards_support = true
10
10
  config.action_dispatch.tld_length = 1
11
11
  config.action_dispatch.ignore_accept_header = false
12
- config.action_dispatch.rack_cache = {:metastore => "rails:/", :entitystore => "rails:/", :verbose => true}
12
+ config.action_dispatch.rescue_templates = { }
13
+ config.action_dispatch.rescue_responses = { }
14
+
15
+ config.action_dispatch.rack_cache = {
16
+ :metastore => "rails:/",
17
+ :entitystore => "rails:/",
18
+ :verbose => true
19
+ }
13
20
 
14
21
  initializer "action_dispatch.configure" do |app|
15
22
  ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length
16
23
  ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header
24
+ ActionDispatch::Response.default_charset = app.config.encoding
25
+
26
+ ActionDispatch::ExceptionWrapper.rescue_responses.merge!(config.action_dispatch.rescue_responses)
27
+ ActionDispatch::ExceptionWrapper.rescue_templates.merge!(config.action_dispatch.rescue_templates)
28
+
29
+ config.action_dispatch.always_write_cookie = Rails.env.development? if config.action_dispatch.always_write_cookie.nil?
30
+ ActionDispatch::Cookies::CookieJar.always_write_cookie = config.action_dispatch.always_write_cookie
17
31
  end
18
32
  end
19
33
  end
@@ -161,7 +161,7 @@ module ActionDispatch
161
161
  # Consider the following route, which you will find commented out at the
162
162
  # bottom of your generated <tt>config/routes.rb</tt>:
163
163
  #
164
- # match ':controller(/:action(/:id(.:format)))'
164
+ # match ':controller(/:action(/:id))(.:format)'
165
165
  #
166
166
  # This route states that it expects requests to consist of a
167
167
  # <tt>:controller</tt> followed optionally by an <tt>:action</tt> that in
@@ -277,7 +277,6 @@ module ActionDispatch
277
277
  #
278
278
  module Routing
279
279
  autoload :Mapper, 'action_dispatch/routing/mapper'
280
- autoload :Route, 'action_dispatch/routing/route'
281
280
  autoload :RouteSet, 'action_dispatch/routing/route_set'
282
281
  autoload :RoutesProxy, 'action_dispatch/routing/routes_proxy'
283
282
  autoload :UrlFor, 'action_dispatch/routing/url_for'
@@ -1,4 +1,3 @@
1
- require 'erb'
2
1
  require 'active_support/core_ext/hash/except'
3
2
  require 'active_support/core_ext/object/blank'
4
3
  require 'active_support/core_ext/object/inclusion'
@@ -35,8 +34,6 @@ module ActionDispatch
35
34
  }
36
35
 
37
36
  return true
38
- ensure
39
- req.reset_parameters
40
37
  end
41
38
 
42
39
  def call(env)
@@ -51,6 +48,9 @@ module ActionDispatch
51
48
 
52
49
  class Mapping #:nodoc:
53
50
  IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix]
51
+ ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
52
+ SHORTHAND_REGEX = %r{/[\w/]+$}
53
+ WILDCARD_PATH = %r{\*([^/\)]+)\)?$}
54
54
 
55
55
  def initialize(set, scope, path, options)
56
56
  @set, @scope = set, scope
@@ -70,7 +70,7 @@ module ActionDispatch
70
70
 
71
71
  if using_match_shorthand?(path_without_format, @options)
72
72
  to_shorthand = @options[:to].blank?
73
- @options[:to] ||= path_without_format[1..-1].sub(%r{/([^/]*)$}, '#\1')
73
+ @options[:to] ||= path_without_format.gsub(/\(.*\)/, "")[1..-1].sub(%r{/([^/]*)$}, '#\1')
74
74
  end
75
75
 
76
76
  @options.merge!(default_controller_and_action(to_shorthand))
@@ -79,7 +79,7 @@ module ActionDispatch
79
79
  # segment_keys.include?(k.to_s) || k == :controller
80
80
  next unless Regexp === requirement && !constraints[name]
81
81
 
82
- if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
82
+ if requirement.source =~ ANCHOR_CHARACTERS_REGEX
83
83
  raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
84
84
  end
85
85
  if requirement.multiline?
@@ -90,7 +90,7 @@ module ActionDispatch
90
90
 
91
91
  # match "account/overview"
92
92
  def using_match_shorthand?(path, options)
93
- path && options.except(:via, :anchor, :to, :as).empty? && path =~ %r{^/[\w\/]+$}
93
+ path && (options[:to] || options[:action]).nil? && path =~ SHORTHAND_REGEX
94
94
  end
95
95
 
96
96
  def normalize_path(path)
@@ -104,13 +104,13 @@ module ActionDispatch
104
104
  # controllers with default routes like :controller/:action/:id(.:format), e.g:
105
105
  # GET /admin/products/show/1
106
106
  # => { :controller => 'admin/products', :action => 'show', :id => '1' }
107
- @options.reverse_merge!(:controller => /.+?/)
107
+ @options[:controller] ||= /.+?/
108
108
  end
109
109
 
110
110
  # Add a constraint for wildcard route to make it non-greedy and match the
111
111
  # optional format part of the route by default
112
- if path.match(%r{\*([^/\)]+)\)?$}) && @options[:format] != false
113
- @options.reverse_merge!(:"#{$1}" => /.+?/)
112
+ if path.match(WILDCARD_PATH) && @options[:format] != false
113
+ @options[$1.to_sym] ||= /.+?/
114
114
  end
115
115
 
116
116
  if @options[:format] == false
@@ -213,8 +213,8 @@ module ActionDispatch
213
213
  end
214
214
 
215
215
  def segment_keys
216
- @segment_keys ||= Rack::Mount::RegexpWithNamedGroups.new(
217
- Rack::Mount::Strexp.compile(@path, requirements, SEPARATORS)
216
+ @segment_keys ||= Journey::Path::Pattern.new(
217
+ Journey::Router::Strexp.compile(@path, requirements, SEPARATORS)
218
218
  ).names
219
219
  end
220
220
 
@@ -223,19 +223,11 @@ module ActionDispatch
223
223
  end
224
224
 
225
225
  def default_controller
226
- if @options[:controller]
227
- @options[:controller]
228
- elsif @scope[:controller]
229
- @scope[:controller]
230
- end
226
+ @options[:controller] || @scope[:controller]
231
227
  end
232
228
 
233
229
  def default_action
234
- if @options[:action]
235
- @options[:action]
236
- elsif @scope[:action]
237
- @scope[:action]
238
- end
230
+ @options[:action] || @scope[:action]
239
231
  end
240
232
  end
241
233
 
@@ -243,7 +235,7 @@ module ActionDispatch
243
235
  # (:locale) becomes (/:locale) instead of /(:locale). Except
244
236
  # for root cases, where the latter is the correct one.
245
237
  def self.normalize_path(path)
246
- path = Rack::Mount::Utils.normalize_path(path)
238
+ path = Journey::Router::Utils.normalize_path(path)
247
239
  path.gsub!(%r{/(\(+)/?}, '\1/') unless path =~ %r{^/\(+[^/]+\)$}
248
240
  path
249
241
  end
@@ -263,7 +255,7 @@ module ActionDispatch
263
255
  # because this means it will be matched first. As this is the most popular route
264
256
  # of most Rails applications, this is beneficial.
265
257
  def root(options = {})
266
- match '/', options.reverse_merge(:as => :root)
258
+ match '/', { :as => :root }.merge(options)
267
259
  end
268
260
 
269
261
  # Matches a url pattern to one or more routes. Any symbols in a pattern
@@ -293,7 +285,7 @@ module ActionDispatch
293
285
  # A pattern can also point to a +Rack+ endpoint i.e. anything that
294
286
  # responds to +call+:
295
287
  #
296
- # match 'photos/:id' => lambda {|hash| [200, {}, "Coming soon" }
288
+ # match 'photos/:id' => lambda {|hash| [200, {}, "Coming soon"] }
297
289
  # match 'photos/:id' => PhotoRackApp
298
290
  # # Yes, controller actions are just rack endpoints
299
291
  # match 'photos/:id' => PhotosController.action(:show)
@@ -333,12 +325,12 @@ module ActionDispatch
333
325
  # +call+ or a string representing a controller's action.
334
326
  #
335
327
  # match 'path', :to => 'controller#action'
336
- # match 'path', :to => lambda { |env| [200, {}, "Success!"] }
328
+ # match 'path', :to => lambda { [200, {}, "Success!"] }
337
329
  # match 'path', :to => RackApp
338
330
  #
339
331
  # [:on]
340
332
  # Shorthand for wrapping routes in a specific RESTful context. Valid
341
- # values are +:member+, +:collection+, and +:new+. Only use within
333
+ # values are +:member+, +:collection+, and +:new+. Only use within
342
334
  # <tt>resource(s)</tt> block. For example:
343
335
  #
344
336
  # resource :bar do
@@ -382,10 +374,6 @@ module ActionDispatch
382
374
  # # Matches any request starting with 'path'
383
375
  # match 'path' => 'c#a', :anchor => false
384
376
  def match(path, options=nil)
385
- mapping = Mapping.new(@set, @scope, path, options || {})
386
- app, conditions, requirements, defaults, as, anchor = mapping.to_route
387
- @set.add_route(app, conditions, requirements, defaults, as, anchor)
388
- self
389
377
  end
390
378
 
391
379
  # Mount a Rack-based application to be used within the application.
@@ -583,8 +571,8 @@ module ActionDispatch
583
571
  # end
584
572
  #
585
573
  # This generates helpers such as +account_projects_path+, just like +resources+ does.
586
- # The difference here being that the routes generated are like /rails/projects/2,
587
- # rather than /accounts/rails/projects/2.
574
+ # The difference here being that the routes generated are like /:account_id/projects,
575
+ # rather than /accounts/:account_id/projects.
588
576
  #
589
577
  # === Options
590
578
  #
@@ -661,13 +649,13 @@ module ActionDispatch
661
649
  #
662
650
  # This generates the following routes:
663
651
  #
664
- # admin_posts GET /admin/posts(.:format) {:action=>"index", :controller=>"admin/posts"}
665
- # admin_posts POST /admin/posts(.:format) {:action=>"create", :controller=>"admin/posts"}
666
- # new_admin_post GET /admin/posts/new(.:format) {:action=>"new", :controller=>"admin/posts"}
667
- # edit_admin_post GET /admin/posts/:id/edit(.:format) {:action=>"edit", :controller=>"admin/posts"}
668
- # admin_post GET /admin/posts/:id(.:format) {:action=>"show", :controller=>"admin/posts"}
669
- # admin_post PUT /admin/posts/:id(.:format) {:action=>"update", :controller=>"admin/posts"}
670
- # admin_post DELETE /admin/posts/:id(.:format) {:action=>"destroy", :controller=>"admin/posts"}
652
+ # admin_posts GET /admin/posts(.:format) admin/posts#index
653
+ # admin_posts POST /admin/posts(.:format) admin/posts#create
654
+ # new_admin_post GET /admin/posts/new(.:format) admin/posts#new
655
+ # edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit
656
+ # admin_post GET /admin/posts/:id(.:format) admin/posts#show
657
+ # admin_post PUT /admin/posts/:id(.:format) admin/posts#update
658
+ # admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy
671
659
  #
672
660
  # === Options
673
661
  #
@@ -875,8 +863,6 @@ module ActionDispatch
875
863
  CANONICAL_ACTIONS = %w(index create new show update destroy)
876
864
 
877
865
  class Resource #:nodoc:
878
- DEFAULT_ACTIONS = [:index, :create, :new, :show, :update, :destroy, :edit]
879
-
880
866
  attr_reader :controller, :path, :options
881
867
 
882
868
  def initialize(entities, options = {})
@@ -888,7 +874,7 @@ module ActionDispatch
888
874
  end
889
875
 
890
876
  def default_actions
891
- self.class::DEFAULT_ACTIONS
877
+ [:index, :create, :new, :show, :update, :destroy, :edit]
892
878
  end
893
879
 
894
880
  def actions
@@ -942,16 +928,17 @@ module ActionDispatch
942
928
  end
943
929
 
944
930
  class SingletonResource < Resource #:nodoc:
945
- DEFAULT_ACTIONS = [:show, :create, :update, :destroy, :new, :edit]
946
-
947
931
  def initialize(entities, options)
948
932
  super
949
-
950
933
  @as = nil
951
934
  @controller = (options[:controller] || plural).to_s
952
935
  @as = options[:as]
953
936
  end
954
937
 
938
+ def default_actions
939
+ [:show, :create, :update, :destroy, :new, :edit]
940
+ end
941
+
955
942
  def plural
956
943
  @plural ||= name.to_s.pluralize
957
944
  end
@@ -999,7 +986,7 @@ module ActionDispatch
999
986
  return self
1000
987
  end
1001
988
 
1002
- resource_scope(SingletonResource.new(resources.pop, options)) do
989
+ resource_scope(:resource, SingletonResource.new(resources.pop, options)) do
1003
990
  yield if block_given?
1004
991
 
1005
992
  collection do
@@ -1031,6 +1018,7 @@ module ActionDispatch
1031
1018
  # creates seven different routes in your application, all mapping to
1032
1019
  # the +Photos+ controller:
1033
1020
  #
1021
+ # GET /photos
1034
1022
  # GET /photos/new
1035
1023
  # POST /photos
1036
1024
  # GET /photos/:id
@@ -1046,19 +1034,20 @@ module ActionDispatch
1046
1034
  #
1047
1035
  # This generates the following comments routes:
1048
1036
  #
1049
- # GET /photos/:id/comments/new
1050
- # POST /photos/:id/comments
1051
- # GET /photos/:id/comments/:id
1052
- # GET /photos/:id/comments/:id/edit
1053
- # PUT /photos/:id/comments/:id
1054
- # DELETE /photos/:id/comments/:id
1037
+ # GET /photos/:photo_id/comments
1038
+ # GET /photos/:photo_id/comments/new
1039
+ # POST /photos/:photo_id/comments
1040
+ # GET /photos/:photo_id/comments/:id
1041
+ # GET /photos/:photo_id/comments/:id/edit
1042
+ # PUT /photos/:photo_id/comments/:id
1043
+ # DELETE /photos/:photo_id/comments/:id
1055
1044
  #
1056
1045
  # === Options
1057
1046
  # Takes same options as <tt>Base#match</tt> as well as:
1058
1047
  #
1059
1048
  # [:path_names]
1060
- # Allows you to change the paths of the seven default actions.
1061
- # Paths not specified are not changed.
1049
+ # Allows you to change the segment component of the +edit+ and +new+ actions.
1050
+ # Actions not specified are not changed.
1062
1051
  #
1063
1052
  # resources :posts, :path_names => { :new => "brand_new" }
1064
1053
  #
@@ -1098,11 +1087,11 @@ module ActionDispatch
1098
1087
  # [:shallow_path]
1099
1088
  # Prefixes nested shallow routes with the specified path.
1100
1089
  #
1101
- # scope :shallow_path => "sekret" do
1102
- # resources :posts do
1103
- # resources :comments, :shallow => true
1090
+ # scope :shallow_path => "sekret" do
1091
+ # resources :posts do
1092
+ # resources :comments, :shallow => true
1093
+ # end
1104
1094
  # end
1105
- # end
1106
1095
  #
1107
1096
  # The +comments+ resource here will have the following routes generated for it:
1108
1097
  #
@@ -1128,7 +1117,7 @@ module ActionDispatch
1128
1117
  return self
1129
1118
  end
1130
1119
 
1131
- resource_scope(Resource.new(resources.pop, options)) do
1120
+ resource_scope(:resources, Resource.new(resources.pop, options)) do
1132
1121
  yield if block_given?
1133
1122
 
1134
1123
  collection do
@@ -1251,32 +1240,44 @@ module ActionDispatch
1251
1240
  parent_resource.instance_of?(Resource) && @scope[:shallow]
1252
1241
  end
1253
1242
 
1254
- def match(*args)
1255
- options = args.extract_options!.dup
1256
- options[:anchor] = true unless options.key?(:anchor)
1257
-
1258
- if args.length > 1
1259
- args.each { |path| match(path, options.dup) }
1260
- return self
1243
+ def match(path, *rest)
1244
+ if rest.empty? && Hash === path
1245
+ options = path
1246
+ path, to = options.find { |name, value| name.is_a?(String) }
1247
+ options[:to] = to
1248
+ options.delete(path)
1249
+ paths = [path]
1250
+ else
1251
+ options = rest.pop || {}
1252
+ paths = [path] + rest
1261
1253
  end
1262
1254
 
1263
- on = options.delete(:on)
1264
- if VALID_ON_OPTIONS.include?(on)
1265
- args.push(options)
1266
- return send(on){ match(*args) }
1267
- elsif on
1255
+ options[:anchor] = true unless options.key?(:anchor)
1256
+
1257
+ if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
1268
1258
  raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
1269
1259
  end
1270
1260
 
1271
- if @scope[:scope_level] == :resources
1272
- args.push(options)
1273
- return nested { match(*args) }
1274
- elsif @scope[:scope_level] == :resource
1275
- args.push(options)
1276
- return member { match(*args) }
1261
+ paths.each { |_path| decomposed_match(_path, options.dup) }
1262
+ self
1263
+ end
1264
+
1265
+ def decomposed_match(path, options) # :nodoc:
1266
+ if on = options.delete(:on)
1267
+ send(on) { decomposed_match(path, options) }
1268
+ else
1269
+ case @scope[:scope_level]
1270
+ when :resources
1271
+ nested { decomposed_match(path, options) }
1272
+ when :resource
1273
+ member { decomposed_match(path, options) }
1274
+ else
1275
+ add_route(path, options)
1276
+ end
1277
1277
  end
1278
+ end
1278
1279
 
1279
- action = args.first
1280
+ def add_route(action, options) # :nodoc:
1280
1281
  path = path_for_action(action, options.delete(:path))
1281
1282
 
1282
1283
  if action.to_s =~ /^[\w\/]+$/
@@ -1285,13 +1286,15 @@ module ActionDispatch
1285
1286
  action = nil
1286
1287
  end
1287
1288
 
1288
- if options.key?(:as) && !options[:as]
1289
+ if !options.fetch(:as, true)
1289
1290
  options.delete(:as)
1290
1291
  else
1291
1292
  options[:as] = name_for_action(options[:as], action)
1292
1293
  end
1293
1294
 
1294
- super(path, options)
1295
+ mapping = Mapping.new(@set, @scope, path, options)
1296
+ app, conditions, requirements, defaults, as, anchor = mapping.to_route
1297
+ @set.add_route(app, conditions, requirements, defaults, as, anchor)
1295
1298
  end
1296
1299
 
1297
1300
  def root(options={})
@@ -1347,7 +1350,7 @@ module ActionDispatch
1347
1350
  end
1348
1351
 
1349
1352
  def scope_action_options? #:nodoc:
1350
- @scope[:options].is_a?(Hash) && (@scope[:options][:only] || @scope[:options][:except])
1353
+ @scope[:options] && (@scope[:options][:only] || @scope[:options][:except])
1351
1354
  end
1352
1355
 
1353
1356
  def scope_action_options #:nodoc:
@@ -1355,11 +1358,11 @@ module ActionDispatch
1355
1358
  end
1356
1359
 
1357
1360
  def resource_scope? #:nodoc:
1358
- @scope[:scope_level].in?([:resource, :resources])
1361
+ [:resource, :resources].include? @scope[:scope_level]
1359
1362
  end
1360
1363
 
1361
1364
  def resource_method_scope? #:nodoc:
1362
- @scope[:scope_level].in?([:collection, :member, :new])
1365
+ [:collection, :member, :new].include? @scope[:scope_level]
1363
1366
  end
1364
1367
 
1365
1368
  def with_exclusive_scope
@@ -1384,8 +1387,8 @@ module ActionDispatch
1384
1387
  @scope[:scope_level_resource] = old_resource
1385
1388
  end
1386
1389
 
1387
- def resource_scope(resource) #:nodoc:
1388
- with_scope_level(resource.is_a?(SingletonResource) ? :resource : :resources, resource) do
1390
+ def resource_scope(kind, resource) #:nodoc:
1391
+ with_scope_level(kind, resource) do
1389
1392
  scope(parent_resource.resource_scope) do
1390
1393
  yield
1391
1394
  end
@@ -1393,10 +1396,12 @@ module ActionDispatch
1393
1396
  end
1394
1397
 
1395
1398
  def nested_options #:nodoc:
1396
- {}.tap do |options|
1397
- options[:as] = parent_resource.member_name
1398
- options[:constraints] = { "#{parent_resource.singular}_id".to_sym => id_constraint } if id_constraint?
1399
- end
1399
+ options = { :as => parent_resource.member_name }
1400
+ options[:constraints] = {
1401
+ :"#{parent_resource.singular}_id" => id_constraint
1402
+ } if id_constraint?
1403
+
1404
+ options
1400
1405
  end
1401
1406
 
1402
1407
  def id_constraint? #:nodoc:
@@ -1427,7 +1432,9 @@ module ActionDispatch
1427
1432
  end
1428
1433
 
1429
1434
  def action_path(name, path = nil) #:nodoc:
1430
- path || @scope[:path_names][name.to_sym] || name.to_s
1435
+ # Ruby 1.8 can't transform empty strings to symbols
1436
+ name = name.to_sym if name.is_a?(String) && !name.empty?
1437
+ path || @scope[:path_names][name] || name.to_s
1431
1438
  end
1432
1439
 
1433
1440
  def prefix_name_for_action(as, action) #:nodoc:
@@ -1444,7 +1451,7 @@ module ActionDispatch
1444
1451
  name_prefix = @scope[:as]
1445
1452
 
1446
1453
  if parent_resource
1447
- return nil if as.nil? && action.nil?
1454
+ return nil unless as || action
1448
1455
 
1449
1456
  collection_name = parent_resource.collection_name
1450
1457
  member_name = parent_resource.member_name
@@ -1465,22 +1472,17 @@ module ActionDispatch
1465
1472
  [name_prefix, member_name, prefix]
1466
1473
  end
1467
1474
 
1468
- candidate = name.select(&:present?).join("_").presence
1469
- candidate unless as.nil? && @set.routes.find { |r| r.name == candidate }
1470
- end
1471
- end
1472
-
1473
- module Shorthand #:nodoc:
1474
- def match(*args)
1475
- if args.size == 1 && args.last.is_a?(Hash)
1476
- options = args.pop
1477
- path, to = options.find { |name, value| name.is_a?(String) }
1478
- options.merge!(:to => to).delete(path)
1479
- super(path, options)
1480
- else
1481
- super
1475
+ if candidate = name.select(&:present?).join("_").presence
1476
+ # If a name was not explicitly given, we check if it is valid
1477
+ # and return nil in case it isn't. Otherwise, we pass the invalid name
1478
+ # forward so the underlying router engine treats it and raises an exception.
1479
+ if as.nil?
1480
+ candidate unless @set.routes.find { |r| r.name == candidate } || candidate !~ /\A[_a-z]/i
1481
+ else
1482
+ candidate
1483
+ end
1484
+ end
1482
1485
  end
1483
- end
1484
1486
  end
1485
1487
 
1486
1488
  def initialize(set) #:nodoc:
@@ -1493,7 +1495,6 @@ module ActionDispatch
1493
1495
  include Redirection
1494
1496
  include Scoping
1495
1497
  include Resources
1496
- include Shorthand
1497
1498
  end
1498
1499
  end
1499
1500
  end