actionpack 5.0.7.2 → 5.1.0.beta1

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. checksums.yaml +5 -5
  2. data/CHANGELOG.md +189 -1002
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/abstract_controller.rb +3 -3
  6. data/lib/abstract_controller/base.rb +10 -12
  7. data/lib/abstract_controller/caching.rb +6 -3
  8. data/lib/abstract_controller/caching/fragments.rb +1 -1
  9. data/lib/abstract_controller/callbacks.rb +2 -43
  10. data/lib/abstract_controller/collector.rb +2 -2
  11. data/lib/abstract_controller/helpers.rb +19 -19
  12. data/lib/abstract_controller/rendering.rb +9 -11
  13. data/lib/abstract_controller/translation.rb +3 -3
  14. data/lib/action_controller.rb +15 -13
  15. data/lib/action_controller/api.rb +3 -3
  16. data/lib/action_controller/base.rb +7 -12
  17. data/lib/action_controller/caching.rb +1 -1
  18. data/lib/action_controller/log_subscriber.rb +2 -2
  19. data/lib/action_controller/metal.rb +34 -43
  20. data/lib/action_controller/metal/conditional_get.rb +10 -9
  21. data/lib/action_controller/metal/data_streaming.rb +8 -9
  22. data/lib/action_controller/metal/etag_with_flash.rb +16 -0
  23. data/lib/action_controller/metal/etag_with_template_digest.rb +15 -15
  24. data/lib/action_controller/metal/exceptions.rb +4 -14
  25. data/lib/action_controller/metal/flash.rb +1 -1
  26. data/lib/action_controller/metal/force_ssl.rb +6 -6
  27. data/lib/action_controller/metal/head.rb +13 -19
  28. data/lib/action_controller/metal/helpers.rb +6 -6
  29. data/lib/action_controller/metal/http_authentication.rb +22 -23
  30. data/lib/action_controller/metal/implicit_render.rb +2 -5
  31. data/lib/action_controller/metal/instrumentation.rb +14 -14
  32. data/lib/action_controller/metal/live.rb +15 -16
  33. data/lib/action_controller/metal/mime_responds.rb +3 -3
  34. data/lib/action_controller/metal/parameter_encoding.rb +49 -0
  35. data/lib/action_controller/metal/params_wrapper.rb +32 -32
  36. data/lib/action_controller/metal/redirecting.rb +8 -24
  37. data/lib/action_controller/metal/renderers.rb +2 -3
  38. data/lib/action_controller/metal/rendering.rb +50 -60
  39. data/lib/action_controller/metal/request_forgery_protection.rb +51 -49
  40. data/lib/action_controller/metal/rescue.rb +1 -1
  41. data/lib/action_controller/metal/streaming.rb +4 -4
  42. data/lib/action_controller/metal/strong_parameters.rb +117 -250
  43. data/lib/action_controller/metal/testing.rb +1 -1
  44. data/lib/action_controller/metal/url_for.rb +4 -4
  45. data/lib/action_controller/railtie.rb +9 -13
  46. data/lib/action_controller/renderer.rb +17 -16
  47. data/lib/action_controller/test_case.rb +75 -148
  48. data/lib/action_dispatch.rb +20 -19
  49. data/lib/action_dispatch/http/cache.rb +9 -10
  50. data/lib/action_dispatch/http/filter_parameters.rb +8 -8
  51. data/lib/action_dispatch/http/filter_redirect.rb +2 -4
  52. data/lib/action_dispatch/http/headers.rb +10 -10
  53. data/lib/action_dispatch/http/mime_negotiation.rb +17 -22
  54. data/lib/action_dispatch/http/mime_type.rb +27 -52
  55. data/lib/action_dispatch/http/parameter_filter.rb +8 -6
  56. data/lib/action_dispatch/http/parameters.rb +40 -17
  57. data/lib/action_dispatch/http/request.rb +38 -34
  58. data/lib/action_dispatch/http/response.rb +16 -16
  59. data/lib/action_dispatch/http/upload.rb +6 -10
  60. data/lib/action_dispatch/http/url.rb +48 -74
  61. data/lib/action_dispatch/journey.rb +5 -5
  62. data/lib/action_dispatch/journey/formatter.rb +8 -4
  63. data/lib/action_dispatch/journey/gtg/builder.rb +5 -5
  64. data/lib/action_dispatch/journey/gtg/simulator.rb +1 -1
  65. data/lib/action_dispatch/journey/gtg/transition_table.rb +15 -15
  66. data/lib/action_dispatch/journey/nfa/builder.rb +3 -3
  67. data/lib/action_dispatch/journey/nfa/dot.rb +2 -2
  68. data/lib/action_dispatch/journey/nfa/simulator.rb +1 -1
  69. data/lib/action_dispatch/journey/nfa/transition_table.rb +2 -2
  70. data/lib/action_dispatch/journey/nodes/node.rb +5 -5
  71. data/lib/action_dispatch/journey/parser.rb +23 -24
  72. data/lib/action_dispatch/journey/parser.y +3 -2
  73. data/lib/action_dispatch/journey/parser_extras.rb +2 -2
  74. data/lib/action_dispatch/journey/path/pattern.rb +10 -3
  75. data/lib/action_dispatch/journey/route.rb +19 -12
  76. data/lib/action_dispatch/journey/router.rb +19 -12
  77. data/lib/action_dispatch/journey/router/utils.rb +9 -9
  78. data/lib/action_dispatch/journey/scanner.rb +17 -15
  79. data/lib/action_dispatch/journey/visitors.rb +23 -23
  80. data/lib/action_dispatch/middleware/callbacks.rb +0 -12
  81. data/lib/action_dispatch/middleware/cookies.rb +39 -39
  82. data/lib/action_dispatch/middleware/debug_exceptions.rb +126 -112
  83. data/lib/action_dispatch/middleware/debug_locks.rb +8 -8
  84. data/lib/action_dispatch/middleware/exception_wrapper.rb +55 -55
  85. data/lib/action_dispatch/middleware/executor.rb +1 -1
  86. data/lib/action_dispatch/middleware/flash.rb +17 -16
  87. data/lib/action_dispatch/middleware/public_exceptions.rb +20 -20
  88. data/lib/action_dispatch/middleware/reloader.rb +3 -47
  89. data/lib/action_dispatch/middleware/remote_ip.rb +6 -8
  90. data/lib/action_dispatch/middleware/request_id.rb +6 -5
  91. data/lib/action_dispatch/middleware/session/abstract_store.rb +14 -26
  92. data/lib/action_dispatch/middleware/session/cache_store.rb +3 -3
  93. data/lib/action_dispatch/middleware/session/cookie_store.rb +35 -35
  94. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +2 -2
  95. data/lib/action_dispatch/middleware/show_exceptions.rb +19 -19
  96. data/lib/action_dispatch/middleware/ssl.rb +9 -27
  97. data/lib/action_dispatch/middleware/stack.rb +7 -26
  98. data/lib/action_dispatch/middleware/static.rb +13 -24
  99. data/lib/action_dispatch/railtie.rb +9 -11
  100. data/lib/action_dispatch/request/session.rb +22 -22
  101. data/lib/action_dispatch/request/utils.rb +11 -2
  102. data/lib/action_dispatch/routing.rb +8 -6
  103. data/lib/action_dispatch/routing/inspector.rb +37 -37
  104. data/lib/action_dispatch/routing/mapper.rb +296 -203
  105. data/lib/action_dispatch/routing/polymorphic_routes.rb +160 -134
  106. data/lib/action_dispatch/routing/redirection.rb +27 -22
  107. data/lib/action_dispatch/routing/route_set.rb +206 -92
  108. data/lib/action_dispatch/routing/routes_proxy.rb +2 -2
  109. data/lib/action_dispatch/routing/url_for.rb +14 -12
  110. data/lib/action_dispatch/system_test_case.rb +119 -0
  111. data/lib/action_dispatch/system_testing/browser.rb +28 -0
  112. data/lib/action_dispatch/system_testing/driver.rb +18 -0
  113. data/lib/action_dispatch/system_testing/server.rb +32 -0
  114. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +61 -0
  115. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +20 -0
  116. data/lib/action_dispatch/testing/assertion_response.rb +6 -6
  117. data/lib/action_dispatch/testing/assertions.rb +4 -4
  118. data/lib/action_dispatch/testing/assertions/response.rb +8 -3
  119. data/lib/action_dispatch/testing/assertions/routing.rb +11 -11
  120. data/lib/action_dispatch/testing/integration.rb +47 -138
  121. data/lib/action_dispatch/testing/test_process.rb +2 -2
  122. data/lib/action_dispatch/testing/test_request.rb +16 -16
  123. data/lib/action_dispatch/testing/test_response.rb +1 -1
  124. data/lib/action_pack.rb +2 -2
  125. data/lib/action_pack/gem_version.rb +3 -3
  126. data/lib/action_pack/version.rb +1 -1
  127. metadata +20 -12
  128. data/lib/action_dispatch/middleware/params_parser.rb +0 -46
@@ -8,20 +8,20 @@ module ActionDispatch
8
8
  config.action_dispatch.show_exceptions = true
9
9
  config.action_dispatch.tld_length = 1
10
10
  config.action_dispatch.ignore_accept_header = false
11
- config.action_dispatch.rescue_templates = { }
12
- config.action_dispatch.rescue_responses = { }
11
+ config.action_dispatch.rescue_templates = {}
12
+ config.action_dispatch.rescue_responses = {}
13
13
  config.action_dispatch.default_charset = nil
14
14
  config.action_dispatch.rack_cache = false
15
- config.action_dispatch.http_auth_salt = 'http authentication'
16
- config.action_dispatch.signed_cookie_salt = 'signed cookie'
17
- config.action_dispatch.encrypted_cookie_salt = 'encrypted cookie'
18
- config.action_dispatch.encrypted_signed_cookie_salt = 'signed encrypted cookie'
15
+ config.action_dispatch.http_auth_salt = "http authentication"
16
+ config.action_dispatch.signed_cookie_salt = "signed cookie"
17
+ config.action_dispatch.encrypted_cookie_salt = "encrypted cookie"
18
+ config.action_dispatch.encrypted_signed_cookie_salt = "signed encrypted cookie"
19
19
  config.action_dispatch.perform_deep_munge = true
20
20
 
21
21
  config.action_dispatch.default_headers = {
22
- 'X-Frame-Options' => 'SAMEORIGIN',
23
- 'X-XSS-Protection' => '1; mode=block',
24
- 'X-Content-Type-Options' => 'nosniff'
22
+ "X-Frame-Options" => "SAMEORIGIN",
23
+ "X-XSS-Protection" => "1; mode=block",
24
+ "X-Content-Type-Options" => "nosniff"
25
25
  }
26
26
 
27
27
  config.eager_load_namespaces << ActionDispatch
@@ -39,8 +39,6 @@ module ActionDispatch
39
39
  config.action_dispatch.always_write_cookie = Rails.env.development? if config.action_dispatch.always_write_cookie.nil?
40
40
  ActionDispatch::Cookies::CookieJar.always_write_cookie = config.action_dispatch.always_write_cookie
41
41
 
42
- ActionDispatch::Reloader.default_reloader = app.reloader
43
-
44
42
  ActionDispatch.test_app = app
45
43
  end
46
44
  end
@@ -1,4 +1,4 @@
1
- require 'rack/session/abstract/id'
1
+ require "rack/session/abstract/id"
2
2
 
3
3
  module ActionDispatch
4
4
  class Request
@@ -53,7 +53,7 @@ module ActionDispatch
53
53
  }
54
54
  end
55
55
 
56
- def []=(k,v); @delegate[k] = v; end
56
+ def []=(k, v); @delegate[k] = v; end
57
57
  def to_hash; @delegate.dup; end
58
58
  def values_at(*args); @delegate.values_at(*args); end
59
59
  end
@@ -85,7 +85,7 @@ module ActionDispatch
85
85
  end
86
86
 
87
87
  # Returns value of the key stored in the session or
88
- # nil if the given key is not found in the session.
88
+ # +nil+ if the given key is not found in the session.
89
89
  def [](key)
90
90
  load_for_read!
91
91
  @delegate[key.to_s]
@@ -124,7 +124,7 @@ module ActionDispatch
124
124
  # Returns the session as Hash.
125
125
  def to_hash
126
126
  load_for_read!
127
- @delegate.dup.delete_if { |_,v| v.nil? }
127
+ @delegate.dup.delete_if { |_, v| v.nil? }
128
128
  end
129
129
 
130
130
  # Updates the session with given Hash.
@@ -162,7 +162,7 @@ module ActionDispatch
162
162
  # :bar
163
163
  # end
164
164
  # # => :bar
165
- def fetch(key, default=Unspecified, &block)
165
+ def fetch(key, default = Unspecified, &block)
166
166
  load_for_read!
167
167
  if default == Unspecified
168
168
  @delegate.fetch(key.to_s, &block)
@@ -204,26 +204,26 @@ module ActionDispatch
204
204
 
205
205
  private
206
206
 
207
- def load_for_read!
208
- load! if !loaded? && exists?
209
- end
207
+ def load_for_read!
208
+ load! if !loaded? && exists?
209
+ end
210
210
 
211
- def load_for_write!
212
- load! unless loaded?
213
- end
211
+ def load_for_write!
212
+ load! unless loaded?
213
+ end
214
214
 
215
- def load!
216
- id, session = @by.load_session @req
217
- options[:id] = id
218
- @delegate.replace(stringify_keys(session))
219
- @loaded = true
220
- end
215
+ def load!
216
+ id, session = @by.load_session @req
217
+ options[:id] = id
218
+ @delegate.replace(stringify_keys(session))
219
+ @loaded = true
220
+ end
221
221
 
222
- def stringify_keys(other)
223
- other.each_with_object({}) { |(key, value), hash|
224
- hash[key.to_s] = value
225
- }
226
- end
222
+ def stringify_keys(other)
223
+ other.each_with_object({}) { |(key, value), hash|
224
+ hash[key.to_s] = value
225
+ }
226
+ end
227
227
  end
228
228
  end
229
229
  end
@@ -1,10 +1,20 @@
1
1
  module ActionDispatch
2
2
  class Request
3
3
  class Utils # :nodoc:
4
-
5
4
  mattr_accessor :perform_deep_munge
6
5
  self.perform_deep_munge = true
7
6
 
7
+ def self.each_param_value(params, &block)
8
+ case params
9
+ when Array
10
+ params.each { |element| each_param_value(element, &block) }
11
+ when Hash
12
+ params.each_value { |value| each_param_value(value, &block) }
13
+ when String
14
+ block.call params
15
+ end
16
+ end
17
+
8
18
  def self.normalize_encode_params(params)
9
19
  if perform_deep_munge
10
20
  NoNilParamEncoder.normalize_encode_params params
@@ -64,4 +74,3 @@ module ActionDispatch
64
74
  end
65
75
  end
66
76
  end
67
-
@@ -1,3 +1,5 @@
1
+ require "active_support/core_ext/string/filters"
2
+
1
3
  module ActionDispatch
2
4
  # The routing module provides URL rewriting in native Ruby. It's a way to
3
5
  # redirect incoming requests to controllers and actions. This replaces
@@ -89,7 +91,7 @@ module ActionDispatch
89
91
  #
90
92
  # Example:
91
93
  #
92
- # # In routes.rb
94
+ # # In config/routes.rb
93
95
  # get '/login' => 'accounts#login', as: 'login'
94
96
  #
95
97
  # # With render, redirect_to, tests, etc.
@@ -101,7 +103,7 @@ module ActionDispatch
101
103
  #
102
104
  # Use <tt>root</tt> as a shorthand to name a route for the root path "/".
103
105
  #
104
- # # In routes.rb
106
+ # # In config/routes.rb
105
107
  # root to: 'blogs#index'
106
108
  #
107
109
  # # would recognize http://www.example.com/ as
@@ -114,15 +116,15 @@ module ActionDispatch
114
116
  # Note: when using +controller+, the route is simply named after the
115
117
  # method you call on the block parameter rather than map.
116
118
  #
117
- # # In routes.rb
119
+ # # In config/routes.rb
118
120
  # controller :blog do
119
121
  # get 'blog/show' => :list
120
122
  # get 'blog/delete' => :delete
121
- # get 'blog/edit/:id' => :edit
123
+ # get 'blog/edit' => :edit
122
124
  # end
123
125
  #
124
126
  # # provides named routes for show, delete, and edit
125
- # link_to @article.title, show_path(id: @article.id)
127
+ # link_to @article.title, blog_show_path(id: @article.id)
126
128
  #
127
129
  # == Pretty URLs
128
130
  #
@@ -196,7 +198,7 @@ module ActionDispatch
196
198
  #
197
199
  # Rails.application.reload_routes!
198
200
  #
199
- # This will clear all named routes and reload routes.rb if the file has been modified from
201
+ # This will clear all named routes and reload config/routes.rb if the file has been modified from
200
202
  # last load. To absolutely force reloading, use <tt>reload!</tt>.
201
203
  #
202
204
  # == Testing Routes
@@ -1,5 +1,5 @@
1
- require 'delegate'
2
- require 'active_support/core_ext/string/strip'
1
+ require "delegate"
2
+ require "active_support/core_ext/string/strip"
3
3
 
4
4
  module ActionDispatch
5
5
  module Routing
@@ -33,11 +33,11 @@ module ActionDispatch
33
33
  end
34
34
 
35
35
  def controller
36
- parts.include?(:controller) ? ':controller' : requirements[:controller]
36
+ parts.include?(:controller) ? ":controller" : requirements[:controller]
37
37
  end
38
38
 
39
39
  def action
40
- parts.include?(:action) ? ':action' : requirements[:action]
40
+ parts.include?(:action) ? ":action" : requirements[:action]
41
41
  end
42
42
 
43
43
  def internal?
@@ -80,48 +80,48 @@ module ActionDispatch
80
80
 
81
81
  private
82
82
 
83
- def normalize_filter(filter)
84
- if filter.is_a?(Hash) && filter[:controller]
85
- { controller: /#{filter[:controller].downcase.sub(/_?controller\z/, '').sub('::', '/')}/ }
86
- elsif filter
87
- { controller: /#{filter}/, action: /#{filter}/, verb: /#{filter}/, name: /#{filter}/, path: /#{filter}/ }
83
+ def normalize_filter(filter)
84
+ if filter.is_a?(Hash) && filter[:controller]
85
+ { controller: /#{filter[:controller].downcase.sub(/_?controller\z/, '').sub('::', '/')}/ }
86
+ elsif filter
87
+ { controller: /#{filter}/, action: /#{filter}/, verb: /#{filter}/, name: /#{filter}/, path: /#{filter}/ }
88
+ end
88
89
  end
89
- end
90
90
 
91
- def filter_routes(filter)
92
- if filter
93
- @routes.select do |route|
94
- route_wrapper = RouteWrapper.new(route)
95
- filter.any? { |default, value| route_wrapper.send(default) =~ value }
91
+ def filter_routes(filter)
92
+ if filter
93
+ @routes.select do |route|
94
+ route_wrapper = RouteWrapper.new(route)
95
+ filter.any? { |default, value| route_wrapper.send(default) =~ value }
96
+ end
97
+ else
98
+ @routes
96
99
  end
97
- else
98
- @routes
99
100
  end
100
- end
101
101
 
102
- def collect_routes(routes)
103
- routes.collect do |route|
104
- RouteWrapper.new(route)
105
- end.reject(&:internal?).collect do |route|
106
- collect_engine_routes(route)
102
+ def collect_routes(routes)
103
+ routes.collect do |route|
104
+ RouteWrapper.new(route)
105
+ end.reject(&:internal?).collect do |route|
106
+ collect_engine_routes(route)
107
107
 
108
- { name: route.name,
109
- verb: route.verb,
110
- path: route.path,
111
- reqs: route.reqs }
108
+ { name: route.name,
109
+ verb: route.verb,
110
+ path: route.path,
111
+ reqs: route.reqs }
112
+ end
112
113
  end
113
- end
114
114
 
115
- def collect_engine_routes(route)
116
- name = route.endpoint
117
- return unless route.engine?
118
- return if @engines[name]
115
+ def collect_engine_routes(route)
116
+ name = route.endpoint
117
+ return unless route.engine?
118
+ return if @engines[name]
119
119
 
120
- routes = route.rack_app.routes
121
- if routes.is_a?(ActionDispatch::Routing::RouteSet)
122
- @engines[name] = collect_routes(routes.routes)
120
+ routes = route.rack_app.routes
121
+ if routes.is_a?(ActionDispatch::Routing::RouteSet)
122
+ @engines[name] = collect_routes(routes.routes)
123
+ end
123
124
  end
124
- end
125
125
  end
126
126
 
127
127
  class ConsoleFormatter
@@ -161,7 +161,7 @@ module ActionDispatch
161
161
 
162
162
  private
163
163
  def draw_section(routes)
164
- header_lengths = ['Prefix', 'Verb', 'URI Pattern'].map(&:length)
164
+ header_lengths = ["Prefix", "Verb", "URI Pattern"].map(&:length)
165
165
  name_width, verb_width, path_width = widths(routes).zip(header_lengths).map(&:max)
166
166
 
167
167
  routes.map do |r|
@@ -1,9 +1,9 @@
1
- require 'active_support/core_ext/hash/slice'
2
- require 'active_support/core_ext/enumerable'
3
- require 'active_support/core_ext/array/extract_options'
4
- require 'active_support/core_ext/regexp'
5
- require 'action_dispatch/routing/redirection'
6
- require 'action_dispatch/routing/endpoint'
1
+ require "active_support/core_ext/hash/slice"
2
+ require "active_support/core_ext/enumerable"
3
+ require "active_support/core_ext/array/extract_options"
4
+ require "active_support/core_ext/regexp"
5
+ require "action_dispatch/routing/redirection"
6
+ require "action_dispatch/routing/endpoint"
7
7
 
8
8
  module ActionDispatch
9
9
  module Routing
@@ -41,7 +41,7 @@ module ActionDispatch
41
41
  end
42
42
 
43
43
  def serve(req)
44
- return [ 404, {'X-Cascade' => 'pass'}, [] ] unless matches?(req)
44
+ return [ 404, { "X-Cascade" => "pass" }, [] ] unless matches?(req)
45
45
 
46
46
  @strategy.call @app, req
47
47
  end
@@ -54,7 +54,6 @@ module ActionDispatch
54
54
 
55
55
  class Mapping #:nodoc:
56
56
  ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
57
- OPTIONAL_FORMAT_REGEX = %r{(?:\(\.:format\)+|\.:format|/)\Z}
58
57
 
59
58
  attr_reader :requirements, :defaults
60
59
  attr_reader :to, :default_controller, :default_action
@@ -94,7 +93,7 @@ module ActionDispatch
94
93
  end
95
94
 
96
95
  def self.optional_format?(path, format)
97
- format != false && path !~ OPTIONAL_FORMAT_REGEX
96
+ format != false && !path.include?(":format") && !path.end_with?("/")
98
97
  end
99
98
 
100
99
  def initialize(set, ast, defaults, controller, default_action, modyoule, to, formatted, scope_constraints, blocks, via, options_constraints, anchor, options)
@@ -107,7 +106,7 @@ module ActionDispatch
107
106
  @ast = ast
108
107
  @anchor = anchor
109
108
  @via = via
110
- @internal = options[:internal]
109
+ @internal = options.delete(:internal)
111
110
 
112
111
  path_params = ast.find_all(&:symbol?).map(&:to_sym)
113
112
 
@@ -139,7 +138,7 @@ module ActionDispatch
139
138
  @defaults = formats[:defaults].merge(@defaults).merge(normalize_defaults(options))
140
139
 
141
140
  if path_params.include?(:action) && !@requirements.key?(:action)
142
- @defaults[:action] ||= 'index'
141
+ @defaults[:action] ||= "index"
143
142
  end
144
143
 
145
144
  @required_defaults = (split_options[:required_defaults] || []).map(&:first)
@@ -240,7 +239,7 @@ module ActionDispatch
240
239
  options[:controller] ||= /.+?/
241
240
  end
242
241
 
243
- if to.respond_to? :call
242
+ if to.respond_to?(:action) || to.respond_to?(:call)
244
243
  options
245
244
  else
246
245
  to_endpoint = split_to to
@@ -271,7 +270,7 @@ module ActionDispatch
271
270
  { requirements: { format: Regexp.compile(formatted) },
272
271
  defaults: { format: formatted } }
273
272
  else
274
- { requirements: { }, defaults: { } }
273
+ { requirements: {}, defaults: {} }
275
274
  end
276
275
  end
277
276
 
@@ -292,16 +291,14 @@ module ActionDispatch
292
291
  end
293
292
 
294
293
  def app(blocks)
295
- if to.is_a?(Class) && to < ActionController::Metal
294
+ if to.respond_to?(:action)
296
295
  Routing::RouteSet::StaticDispatcher.new to
296
+ elsif to.respond_to?(:call)
297
+ Constraints.new(to, blocks, Constraints::CALL)
298
+ elsif blocks.any?
299
+ Constraints.new(dispatcher(defaults.key?(:controller)), blocks, Constraints::SERVE)
297
300
  else
298
- if to.respond_to?(:call)
299
- Constraints.new(to, blocks, Constraints::CALL)
300
- elsif blocks.any?
301
- Constraints.new(dispatcher(defaults.key?(:controller)), blocks, Constraints::SERVE)
302
- else
303
- dispatcher(defaults.key?(:controller))
304
- end
301
+ dispatcher(defaults.key?(:controller))
305
302
  end
306
303
  end
307
304
 
@@ -334,7 +331,7 @@ module ActionDispatch
334
331
 
335
332
  def split_to(to)
336
333
  if to =~ /#/
337
- to.split('#')
334
+ to.split("#")
338
335
  else
339
336
  []
340
337
  end
@@ -569,7 +566,7 @@ module ActionDispatch
569
566
  # [:format]
570
567
  # Allows you to specify the default value for optional +format+
571
568
  # segment or disable it by supplying +false+.
572
- def match(path, options=nil)
569
+ def match(path, options = nil)
573
570
  end
574
571
 
575
572
  # Mount a Rack-based application to be used within the application.
@@ -615,7 +612,7 @@ module ActionDispatch
615
612
  target_as = name_for_action(options[:as], path)
616
613
  options[:via] ||= :all
617
614
 
618
- match(path, options.merge(:to => app, :anchor => false, :format => false))
615
+ match(path, options.merge(to: app, anchor: false, format: false))
619
616
 
620
617
  define_generate_prefix(app, target_as) if rails_app
621
618
  self
@@ -662,7 +659,7 @@ module ActionDispatch
662
659
  super(options)
663
660
  else
664
661
  prefix_options = options.slice(*_route.segment_keys)
665
- prefix_options[:relative_url_root] = ''.freeze
662
+ prefix_options[:relative_url_root] = "".freeze
666
663
  # we must actually delete prefix segment keys to avoid passing them to next url_for
667
664
  _route.segment_keys.each { |k| options.delete(k) }
668
665
  _routes.url_helpers.send("#{name}_path", prefix_options)
@@ -811,7 +808,7 @@ module ActionDispatch
811
808
  options = args.extract_options!.dup
812
809
  scope = {}
813
810
 
814
- options[:path] = args.flatten.join('/') if args.any?
811
+ options[:path] = args.flatten.join("/") if args.any?
815
812
  options[:constraints] ||= {}
816
813
 
817
814
  unless nested_scope?
@@ -835,7 +832,7 @@ module ActionDispatch
835
832
  end
836
833
 
837
834
  if options.key? :anchor
838
- raise ArgumentError, 'anchor is ignored unless passed to `match`'
835
+ raise ArgumentError, "anchor is ignored unless passed to `match`"
839
836
  end
840
837
 
841
838
  @scope.options.each do |option|
@@ -866,20 +863,11 @@ module ActionDispatch
866
863
  # controller "food" do
867
864
  # match "bacon", action: :bacon, via: :get
868
865
  # end
869
- def controller(controller, options = {})
870
- if options.empty?
871
- begin
872
- @scope = @scope.new(controller: controller)
873
- yield
874
- ensure
875
- @scope = @scope.parent
876
- end
877
- else
878
- ActiveSupport::Deprecation.warn "#controller with options is deprecated. If you need to pass more options than the controller name use #scope."
879
-
880
- options[:controller] = controller
881
- scope(options) { yield }
882
- end
866
+ def controller(controller)
867
+ @scope = @scope.new(controller: controller)
868
+ yield
869
+ ensure
870
+ @scope = @scope.parent
883
871
  end
884
872
 
885
873
  # Scopes routes to a specific namespace. For example:
@@ -991,7 +979,7 @@ module ActionDispatch
991
979
  # resources :iphones
992
980
  # end
993
981
  def constraints(constraints = {})
994
- scope(:constraints => constraints) { yield }
982
+ scope(constraints: constraints) { yield }
995
983
  end
996
984
 
997
985
  # Allows you to set default parameters for a route, such as this:
@@ -1007,65 +995,65 @@ module ActionDispatch
1007
995
  end
1008
996
 
1009
997
  private
1010
- def merge_path_scope(parent, child) #:nodoc:
998
+ def merge_path_scope(parent, child)
1011
999
  Mapper.normalize_path("#{parent}/#{child}")
1012
1000
  end
1013
1001
 
1014
- def merge_shallow_path_scope(parent, child) #:nodoc:
1002
+ def merge_shallow_path_scope(parent, child)
1015
1003
  Mapper.normalize_path("#{parent}/#{child}")
1016
1004
  end
1017
1005
 
1018
- def merge_as_scope(parent, child) #:nodoc:
1006
+ def merge_as_scope(parent, child)
1019
1007
  parent ? "#{parent}_#{child}" : child
1020
1008
  end
1021
1009
 
1022
- def merge_shallow_prefix_scope(parent, child) #:nodoc:
1010
+ def merge_shallow_prefix_scope(parent, child)
1023
1011
  parent ? "#{parent}_#{child}" : child
1024
1012
  end
1025
1013
 
1026
- def merge_module_scope(parent, child) #:nodoc:
1014
+ def merge_module_scope(parent, child)
1027
1015
  parent ? "#{parent}/#{child}" : child
1028
1016
  end
1029
1017
 
1030
- def merge_controller_scope(parent, child) #:nodoc:
1018
+ def merge_controller_scope(parent, child)
1031
1019
  child
1032
1020
  end
1033
1021
 
1034
- def merge_action_scope(parent, child) #:nodoc:
1022
+ def merge_action_scope(parent, child)
1035
1023
  child
1036
1024
  end
1037
1025
 
1038
- def merge_via_scope(parent, child) #:nodoc:
1026
+ def merge_via_scope(parent, child)
1039
1027
  child
1040
1028
  end
1041
1029
 
1042
- def merge_format_scope(parent, child) #:nodoc:
1030
+ def merge_format_scope(parent, child)
1043
1031
  child
1044
1032
  end
1045
1033
 
1046
- def merge_path_names_scope(parent, child) #:nodoc:
1034
+ def merge_path_names_scope(parent, child)
1047
1035
  merge_options_scope(parent, child)
1048
1036
  end
1049
1037
 
1050
- def merge_constraints_scope(parent, child) #:nodoc:
1038
+ def merge_constraints_scope(parent, child)
1051
1039
  merge_options_scope(parent, child)
1052
1040
  end
1053
1041
 
1054
- def merge_defaults_scope(parent, child) #:nodoc:
1042
+ def merge_defaults_scope(parent, child)
1055
1043
  merge_options_scope(parent, child)
1056
1044
  end
1057
1045
 
1058
- def merge_blocks_scope(parent, child) #:nodoc:
1046
+ def merge_blocks_scope(parent, child)
1059
1047
  merged = parent ? parent.dup : []
1060
1048
  merged << child if child
1061
1049
  merged
1062
1050
  end
1063
1051
 
1064
- def merge_options_scope(parent, child) #:nodoc:
1052
+ def merge_options_scope(parent, child)
1065
1053
  (parent || {}).merge(child)
1066
1054
  end
1067
1055
 
1068
- def merge_shallow_scope(parent, child) #:nodoc:
1056
+ def merge_shallow_scope(parent, child)
1069
1057
  child ? true : false
1070
1058
  end
1071
1059
 
@@ -1568,11 +1556,7 @@ module ActionDispatch
1568
1556
  options = path
1569
1557
  path, to = options.find { |name, _value| name.is_a?(String) }
1570
1558
 
1571
- if path.nil?
1572
- ActiveSupport::Deprecation.warn 'Omitting the route path is deprecated. '\
1573
- 'Specify the path with a String or a Symbol instead.'
1574
- path = ''
1575
- end
1559
+ raise ArgumentError, "Route path not specified" if path.nil?
1576
1560
 
1577
1561
  case to
1578
1562
  when Symbol
@@ -1601,63 +1585,6 @@ module ActionDispatch
1601
1585
  end
1602
1586
  end
1603
1587
 
1604
- def get_to_from_path(path, to, action)
1605
- return to if to || action
1606
-
1607
- path_without_format = path.sub(/\(\.:format\)$/, '')
1608
- if using_match_shorthand?(path_without_format)
1609
- path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1').tr("-", "_")
1610
- else
1611
- nil
1612
- end
1613
- end
1614
-
1615
- def using_match_shorthand?(path)
1616
- path =~ %r{^/?[-\w]+/[-\w/]+$}
1617
- end
1618
-
1619
- def decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) # :nodoc:
1620
- if on = options.delete(:on)
1621
- send(on) { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
1622
- else
1623
- case @scope.scope_level
1624
- when :resources
1625
- nested { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
1626
- when :resource
1627
- member { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
1628
- else
1629
- add_route(path, controller, options, _path, to, via, formatted, anchor, options_constraints)
1630
- end
1631
- end
1632
- end
1633
-
1634
- def add_route(action, controller, options, _path, to, via, formatted, anchor, options_constraints) # :nodoc:
1635
- path = path_for_action(action, _path)
1636
- raise ArgumentError, "path is required" if path.blank?
1637
-
1638
- action = action.to_s
1639
-
1640
- default_action = options.delete(:action) || @scope[:action]
1641
-
1642
- if action =~ /^[\w\-\/]+$/
1643
- default_action ||= action.tr('-', '_') unless action.include?("/")
1644
- else
1645
- action = nil
1646
- end
1647
-
1648
- as = if !options.fetch(:as, true) # if it's set to nil or false
1649
- options.delete(:as)
1650
- else
1651
- name_for_action(options.delete(:as), action)
1652
- end
1653
-
1654
- path = Mapping.normalize_path URI.parser.escape(path), formatted
1655
- ast = Journey::Parser.parse path
1656
-
1657
- mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
1658
- @set.add_route(mapping, ast, as, anchor)
1659
- end
1660
-
1661
1588
  # You can specify what Rails should route "/" to with the root method:
1662
1589
  #
1663
1590
  # root to: 'pages#main'
@@ -1674,7 +1601,7 @@ module ActionDispatch
1674
1601
  def root(path, options = {})
1675
1602
  if path.is_a?(String)
1676
1603
  options[:to] = path
1677
- elsif path.is_a?(Hash) and options.empty?
1604
+ elsif path.is_a?(Hash) && options.empty?
1678
1605
  options = path
1679
1606
  else
1680
1607
  raise ArgumentError, "must be called with a path and/or options"
@@ -1691,13 +1618,13 @@ module ActionDispatch
1691
1618
  end
1692
1619
  end
1693
1620
 
1694
- protected
1621
+ private
1695
1622
 
1696
- def parent_resource #:nodoc:
1623
+ def parent_resource
1697
1624
  @scope[:scope_level_resource]
1698
1625
  end
1699
1626
 
1700
- def apply_common_behavior_for(method, resources, options, &block) #:nodoc:
1627
+ def apply_common_behavior_for(method, resources, options, &block)
1701
1628
  if resources.length > 1
1702
1629
  resources.each { |r| send(method, r, options, &block) }
1703
1630
  return true
@@ -1730,48 +1657,48 @@ module ActionDispatch
1730
1657
  false
1731
1658
  end
1732
1659
 
1733
- def apply_action_options(options) # :nodoc:
1660
+ def apply_action_options(options)
1734
1661
  return options if action_options? options
1735
1662
  options.merge scope_action_options
1736
1663
  end
1737
1664
 
1738
- def action_options?(options) #:nodoc:
1665
+ def action_options?(options)
1739
1666
  options[:only] || options[:except]
1740
1667
  end
1741
1668
 
1742
- def scope_action_options #:nodoc:
1669
+ def scope_action_options
1743
1670
  @scope[:action_options] || {}
1744
1671
  end
1745
1672
 
1746
- def resource_scope? #:nodoc:
1673
+ def resource_scope?
1747
1674
  @scope.resource_scope?
1748
1675
  end
1749
1676
 
1750
- def resource_method_scope? #:nodoc:
1677
+ def resource_method_scope?
1751
1678
  @scope.resource_method_scope?
1752
1679
  end
1753
1680
 
1754
- def nested_scope? #:nodoc:
1681
+ def nested_scope?
1755
1682
  @scope.nested?
1756
1683
  end
1757
1684
 
1758
- def with_scope_level(kind)
1685
+ def with_scope_level(kind) # :doc:
1759
1686
  @scope = @scope.new_level(kind)
1760
1687
  yield
1761
1688
  ensure
1762
1689
  @scope = @scope.parent
1763
1690
  end
1764
1691
 
1765
- def resource_scope(resource) #:nodoc:
1766
- @scope = @scope.new(:scope_level_resource => resource)
1692
+ def resource_scope(resource)
1693
+ @scope = @scope.new(scope_level_resource: resource)
1767
1694
 
1768
1695
  controller(resource.resource_scope) { yield }
1769
1696
  ensure
1770
1697
  @scope = @scope.parent
1771
1698
  end
1772
1699
 
1773
- def nested_options #:nodoc:
1774
- options = { :as => parent_resource.member_name }
1700
+ def nested_options
1701
+ options = { as: parent_resource.member_name }
1775
1702
  options[:constraints] = {
1776
1703
  parent_resource.nested_param => param_constraint
1777
1704
  } if param_constraint?
@@ -1779,27 +1706,27 @@ module ActionDispatch
1779
1706
  options
1780
1707
  end
1781
1708
 
1782
- def shallow_nesting_depth #:nodoc:
1709
+ def shallow_nesting_depth
1783
1710
  @scope.find_all { |node|
1784
1711
  node.frame[:scope_level_resource]
1785
1712
  }.count { |node| node.frame[:scope_level_resource].shallow? }
1786
1713
  end
1787
1714
 
1788
- def param_constraint? #:nodoc:
1715
+ def param_constraint?
1789
1716
  @scope[:constraints] && @scope[:constraints][parent_resource.param].is_a?(Regexp)
1790
1717
  end
1791
1718
 
1792
- def param_constraint #:nodoc:
1719
+ def param_constraint
1793
1720
  @scope[:constraints][parent_resource.param]
1794
1721
  end
1795
1722
 
1796
- def canonical_action?(action) #:nodoc:
1723
+ def canonical_action?(action)
1797
1724
  resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s)
1798
1725
  end
1799
1726
 
1800
- def shallow_scope #:nodoc:
1801
- scope = { :as => @scope[:shallow_prefix],
1802
- :path => @scope[:shallow_path] }
1727
+ def shallow_scope
1728
+ scope = { as: @scope[:shallow_prefix],
1729
+ path: @scope[:shallow_path] }
1803
1730
  @scope = @scope.new scope
1804
1731
 
1805
1732
  yield
@@ -1807,7 +1734,7 @@ module ActionDispatch
1807
1734
  @scope = @scope.parent
1808
1735
  end
1809
1736
 
1810
- def path_for_action(action, path) #:nodoc:
1737
+ def path_for_action(action, path)
1811
1738
  return "#{@scope[:path]}/#{path}" if path
1812
1739
 
1813
1740
  if canonical_action?(action)
@@ -1817,23 +1744,23 @@ module ActionDispatch
1817
1744
  end
1818
1745
  end
1819
1746
 
1820
- def action_path(name) #:nodoc:
1747
+ def action_path(name)
1821
1748
  @scope[:path_names][name.to_sym] || name
1822
1749
  end
1823
1750
 
1824
- def prefix_name_for_action(as, action) #:nodoc:
1751
+ def prefix_name_for_action(as, action)
1825
1752
  if as
1826
1753
  prefix = as
1827
1754
  elsif !canonical_action?(action)
1828
1755
  prefix = action
1829
1756
  end
1830
1757
 
1831
- if prefix && prefix != '/' && !prefix.empty?
1832
- Mapper.normalize_name prefix.to_s.tr('-', '_')
1758
+ if prefix && prefix != "/" && !prefix.empty?
1759
+ Mapper.normalize_name prefix.to_s.tr("-", "_")
1833
1760
  end
1834
1761
  end
1835
1762
 
1836
- def name_for_action(as, action) #:nodoc:
1763
+ def name_for_action(as, action)
1837
1764
  prefix = prefix_name_for_action(as, action)
1838
1765
  name_prefix = @scope[:as]
1839
1766
 
@@ -1845,7 +1772,7 @@ module ActionDispatch
1845
1772
  end
1846
1773
 
1847
1774
  action_name = @scope.action_name(name_prefix, prefix, collection_name, member_name)
1848
- candidate = action_name.select(&:present?).join('_')
1775
+ candidate = action_name.select(&:present?).join("_")
1849
1776
 
1850
1777
  unless candidate.empty?
1851
1778
  # If a name was not explicitly given, we check if it is valid
@@ -1859,7 +1786,7 @@ module ActionDispatch
1859
1786
  end
1860
1787
  end
1861
1788
 
1862
- def set_member_mappings_for_resource
1789
+ def set_member_mappings_for_resource # :doc:
1863
1790
  member do
1864
1791
  get :edit if parent_resource.actions.include?(:edit)
1865
1792
  get :show if parent_resource.actions.include?(:show)
@@ -1871,77 +1798,121 @@ module ActionDispatch
1871
1798
  end
1872
1799
  end
1873
1800
 
1874
- def api_only?
1801
+ def api_only? # :doc:
1875
1802
  @set.api_only?
1876
1803
  end
1877
1804
 
1878
- private
1805
+ def path_scope(path)
1806
+ @scope = @scope.new(path: merge_path_scope(@scope[:path], path))
1807
+ yield
1808
+ ensure
1809
+ @scope = @scope.parent
1810
+ end
1879
1811
 
1880
- def path_scope(path)
1881
- @scope = @scope.new(path: merge_path_scope(@scope[:path], path))
1882
- yield
1883
- ensure
1884
- @scope = @scope.parent
1885
- end
1812
+ def map_match(paths, options)
1813
+ if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
1814
+ raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
1815
+ end
1816
+
1817
+ if @scope[:to]
1818
+ options[:to] ||= @scope[:to]
1819
+ end
1886
1820
 
1887
- def map_match(paths, options)
1888
- if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
1889
- raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
1821
+ if @scope[:controller] && @scope[:action]
1822
+ options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}"
1823
+ end
1824
+
1825
+ controller = options.delete(:controller) || @scope[:controller]
1826
+ option_path = options.delete :path
1827
+ to = options.delete :to
1828
+ via = Mapping.check_via Array(options.delete(:via) {
1829
+ @scope[:via]
1830
+ })
1831
+ formatted = options.delete(:format) { @scope[:format] }
1832
+ anchor = options.delete(:anchor) { true }
1833
+ options_constraints = options.delete(:constraints) || {}
1834
+
1835
+ path_types = paths.group_by(&:class)
1836
+ path_types.fetch(String, []).each do |_path|
1837
+ route_options = options.dup
1838
+ if _path && option_path
1839
+ raise ArgumentError, "Ambigous route definition. Both :path and the route path where specified as strings."
1840
+ end
1841
+ to = get_to_from_path(_path, to, route_options[:action])
1842
+ decomposed_match(_path, controller, route_options, _path, to, via, formatted, anchor, options_constraints)
1843
+ end
1844
+
1845
+ path_types.fetch(Symbol, []).each do |action|
1846
+ route_options = options.dup
1847
+ decomposed_match(action, controller, route_options, option_path, to, via, formatted, anchor, options_constraints)
1848
+ end
1849
+
1850
+ self
1890
1851
  end
1891
1852
 
1892
- if @scope[:to]
1893
- options[:to] ||= @scope[:to]
1853
+ def get_to_from_path(path, to, action)
1854
+ return to if to || action
1855
+
1856
+ path_without_format = path.sub(/\(\.:format\)$/, "")
1857
+ if using_match_shorthand?(path_without_format)
1858
+ path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1').tr("-", "_")
1859
+ else
1860
+ nil
1861
+ end
1894
1862
  end
1895
1863
 
1896
- if @scope[:controller] && @scope[:action]
1897
- options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}"
1864
+ def using_match_shorthand?(path)
1865
+ path =~ %r{^/?[-\w]+/[-\w/]+$}
1898
1866
  end
1899
1867
 
1900
- controller = options.delete(:controller) || @scope[:controller]
1901
- option_path = options.delete :path
1902
- to = options.delete :to
1903
- via = Mapping.check_via Array(options.delete(:via) {
1904
- @scope[:via]
1905
- })
1906
- formatted = options.delete(:format) { @scope[:format] }
1907
- anchor = options.delete(:anchor) { true }
1908
- options_constraints = options.delete(:constraints) || {}
1868
+ def decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints)
1869
+ if on = options.delete(:on)
1870
+ send(on) { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
1871
+ else
1872
+ case @scope.scope_level
1873
+ when :resources
1874
+ nested { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
1875
+ when :resource
1876
+ member { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
1877
+ else
1878
+ add_route(path, controller, options, _path, to, via, formatted, anchor, options_constraints)
1879
+ end
1880
+ end
1881
+ end
1909
1882
 
1910
- path_types = paths.group_by(&:class)
1911
- path_types.fetch(String, []).each do |_path|
1912
- route_options = options.dup
1913
- if _path && option_path
1914
- ActiveSupport::Deprecation.warn <<-eowarn
1915
- Specifying strings for both :path and the route path is deprecated. Change things like this:
1883
+ def add_route(action, controller, options, _path, to, via, formatted, anchor, options_constraints)
1884
+ path = path_for_action(action, _path)
1885
+ raise ArgumentError, "path is required" if path.blank?
1916
1886
 
1917
- match #{_path.inspect}, :path => #{option_path.inspect}
1887
+ action = action.to_s
1918
1888
 
1919
- to this:
1889
+ default_action = options.delete(:action) || @scope[:action]
1920
1890
 
1921
- match #{option_path.inspect}, :as => #{_path.inspect}, :action => #{_path.inspect}
1922
- eowarn
1923
- route_options[:action] = _path
1924
- route_options[:as] = _path
1925
- _path = option_path
1891
+ if action =~ /^[\w\-\/]+$/
1892
+ default_action ||= action.tr("-", "_") unless action.include?("/")
1893
+ else
1894
+ action = nil
1926
1895
  end
1927
- to = get_to_from_path(_path, to, route_options[:action])
1928
- decomposed_match(_path, controller, route_options, _path, to, via, formatted, anchor, options_constraints)
1929
- end
1930
1896
 
1931
- path_types.fetch(Symbol, []).each do |action|
1932
- route_options = options.dup
1933
- decomposed_match(action, controller, route_options, option_path, to, via, formatted, anchor, options_constraints)
1934
- end
1897
+ as = if !options.fetch(:as, true) # if it's set to nil or false
1898
+ options.delete(:as)
1899
+ else
1900
+ name_for_action(options.delete(:as), action)
1901
+ end
1935
1902
 
1936
- self
1937
- end
1903
+ path = Mapping.normalize_path URI.parser.escape(path), formatted
1904
+ ast = Journey::Parser.parse path
1938
1905
 
1939
- def match_root_route(options)
1940
- name = has_named_route?(name_for_action(:root, nil)) ? nil : :root
1941
- args = ["/", { as: name, via: :get }.merge!(options)]
1906
+ mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
1907
+ @set.add_route(mapping, ast, as, anchor)
1908
+ end
1942
1909
 
1943
- match(*args)
1944
- end
1910
+ def match_root_route(options)
1911
+ name = has_named_route?(name_for_action(:root, nil)) ? nil : :root
1912
+ args = ["/", { as: name, via: :get }.merge!(options)]
1913
+
1914
+ match(*args)
1915
+ end
1945
1916
  end
1946
1917
 
1947
1918
  # Routing Concerns allow you to declare common routes that can be reused
@@ -2049,6 +2020,120 @@ match #{option_path.inspect}, :as => #{_path.inspect}, :action => #{_path.inspec
2049
2020
  end
2050
2021
  end
2051
2022
 
2023
+ module CustomUrls
2024
+ # Define custom url helpers that will be added to the application's
2025
+ # routes. This allows you to override and/or replace the default behavior
2026
+ # of routing helpers, e.g:
2027
+ #
2028
+ # direct :homepage do
2029
+ # "http://www.rubyonrails.org"
2030
+ # end
2031
+ #
2032
+ # direct :commentable do |model|
2033
+ # [ model, anchor: model.dom_id ]
2034
+ # end
2035
+ #
2036
+ # direct :main do
2037
+ # { controller: "pages", action: "index", subdomain: "www" }
2038
+ # end
2039
+ #
2040
+ # The return value from the block passed to `direct` must be a valid set of
2041
+ # arguments for `url_for` which will actually build the url string. This can
2042
+ # be one of the following:
2043
+ #
2044
+ # * A string, which is treated as a generated url
2045
+ # * A hash, e.g. { controller: "pages", action: "index" }
2046
+ # * An array, which is passed to `polymorphic_url`
2047
+ # * An Active Model instance
2048
+ # * An Active Model class
2049
+ #
2050
+ # NOTE: Other url helpers can be called in the block but be careful not to invoke
2051
+ # your custom url helper again otherwise it will result in a stack overflow error
2052
+ #
2053
+ # You can also specify default options that will be passed through to
2054
+ # your url helper definition, e.g:
2055
+ #
2056
+ # direct :browse, page: 1, size: 10 do |options|
2057
+ # [ :products, options.merge(params.permit(:page, :size)) ]
2058
+ # end
2059
+ #
2060
+ # In this instance the `params` object comes from the context in which the the
2061
+ # block is executed, e.g. generating a url inside a controller action or a view.
2062
+ # If the block is executed where there isn't a params object such as this:
2063
+ #
2064
+ # Rails.application.routes.url_helpers.browse_path
2065
+ #
2066
+ # then it will raise a `NameError`. Because of this you need to be aware of the
2067
+ # context in which you will use your custom url helper when defining it.
2068
+ #
2069
+ # NOTE: The `direct` method can't be used inside of a scope block such as
2070
+ # `namespace` or `scope` and will raise an error if it detects that it is.
2071
+ def direct(name, options = {}, &block)
2072
+ unless @scope.root?
2073
+ raise RuntimeError, "The direct method can't be used inside a routes scope block"
2074
+ end
2075
+
2076
+ @set.add_url_helper(name, options, &block)
2077
+ end
2078
+
2079
+ # Define custom polymorphic mappings of models to urls. This alters the
2080
+ # behavior of `polymorphic_url` and consequently the behavior of
2081
+ # `link_to` and `form_for` when passed a model instance, e.g:
2082
+ #
2083
+ # resource :basket
2084
+ #
2085
+ # resolve "Basket" do
2086
+ # [:basket]
2087
+ # end
2088
+ #
2089
+ # This will now generate "/basket" when a `Basket` instance is passed to
2090
+ # `link_to` or `form_for` instead of the standard "/baskets/:id".
2091
+ #
2092
+ # NOTE: This custom behavior only applies to simple polymorphic urls where
2093
+ # a single model instance is passed and not more complicated forms, e.g:
2094
+ #
2095
+ # # config/routes.rb
2096
+ # resource :profile
2097
+ # namespace :admin do
2098
+ # resources :users
2099
+ # end
2100
+ #
2101
+ # resolve("User") { [:profile] }
2102
+ #
2103
+ # # app/views/application/_menu.html.erb
2104
+ # link_to "Profile", @current_user
2105
+ # link_to "Profile", [:admin, @current_user]
2106
+ #
2107
+ # The first `link_to` will generate "/profile" but the second will generate
2108
+ # the standard polymorphic url of "/admin/users/1".
2109
+ #
2110
+ # You can pass options to a polymorphic mapping - the arity for the block
2111
+ # needs to be two as the instance is passed as the first argument, e.g:
2112
+ #
2113
+ # resolve "Basket", anchor: "items" do |basket, options|
2114
+ # [:basket, options]
2115
+ # end
2116
+ #
2117
+ # This generates the url "/basket#items" because when the last item in an
2118
+ # array passed to `polymorphic_url` is a hash then it's treated as options
2119
+ # to the url helper that gets called.
2120
+ #
2121
+ # NOTE: The `resolve` method can't be used inside of a scope block such as
2122
+ # `namespace` or `scope` and will raise an error if it detects that it is.
2123
+ def resolve(*args, &block)
2124
+ unless @scope.root?
2125
+ raise RuntimeError, "The resolve method can't be used inside a routes scope block"
2126
+ end
2127
+
2128
+ options = args.extract_options!
2129
+ args = args.flatten(1)
2130
+
2131
+ args.each do |klass|
2132
+ @set.add_polymorphic_mapping(klass, options, &block)
2133
+ end
2134
+ end
2135
+ end
2136
+
2052
2137
  class Scope # :nodoc:
2053
2138
  OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
2054
2139
  :controller, :action, :path_names, :constraints,
@@ -2069,6 +2154,14 @@ match #{option_path.inspect}, :as => #{_path.inspect}, :action => #{_path.inspec
2069
2154
  scope_level == :nested
2070
2155
  end
2071
2156
 
2157
+ def null?
2158
+ @hash.nil? && @parent.nil?
2159
+ end
2160
+
2161
+ def root?
2162
+ @parent.null?
2163
+ end
2164
+
2072
2165
  def resources?
2073
2166
  scope_level == :resources
2074
2167
  end
@@ -2119,8 +2212,7 @@ match #{option_path.inspect}, :as => #{_path.inspect}, :action => #{_path.inspec
2119
2212
 
2120
2213
  def each
2121
2214
  node = self
2122
- loop do
2123
- break if node.equal? NULL
2215
+ until node.equal? NULL
2124
2216
  yield node
2125
2217
  node = node.parent
2126
2218
  end
@@ -2133,7 +2225,7 @@ match #{option_path.inspect}, :as => #{_path.inspect}, :action => #{_path.inspec
2133
2225
 
2134
2226
  def initialize(set) #:nodoc:
2135
2227
  @set = set
2136
- @scope = Scope.new({ :path_names => @set.resources_path_names })
2228
+ @scope = Scope.new(path_names: @set.resources_path_names)
2137
2229
  @concerns = {}
2138
2230
  end
2139
2231
 
@@ -2143,6 +2235,7 @@ match #{option_path.inspect}, :as => #{_path.inspect}, :action => #{_path.inspec
2143
2235
  include Scoping
2144
2236
  include Concerns
2145
2237
  include Resources
2238
+ include CustomUrls
2146
2239
  end
2147
2240
  end
2148
2241
  end